[{"data":1,"prerenderedAt":357},["ShallowReactive",2],{"\u002Fprojects\u002Fdevextractor":3,"posts":255},{"id":4,"title":5,"body":6,"description":191,"extension":192,"featured":193,"images":194,"links":214,"meta":221,"navigation":193,"order":222,"path":223,"seo":224,"stack":225,"stats":238,"stem":251,"tag":252,"url":253,"__hash__":254},"projects\u002Fprojects\u002Fdevextractor.md","DevExtractor",{"type":7,"value":8,"toc":179},"minimark",[9,14,94,98,103,107,111,122,126,141,145],[10,11,13],"h2",{"id":12},"qué-hace","Qué hace",[15,16,17,30,44,50,56,62,76,82,88],"ul",{},[18,19,20,24,25,29],"li",{},[21,22,23],"strong",{},"Medios."," Encuentra las imágenes (también las de ",[26,27,28],"code",{},"\u003Cpicture>","), los SVG inline, los vídeos, el audio y las imágenes de fondo de CSS de la página. Se filtran por tipo, dominio, URL y tamaño, y se ven en cuadrícula o en lista.",[18,31,32,35,36,39,40,43],{},[21,33,34],{},"SVG inline como archivos."," Cada ",[26,37,38],{},"\u003Csvg>"," incrustado en el HTML sale como un ",[26,41,42],{},".svg"," descargable, sin copiar y pegar desde el inspector.",[18,45,46,49],{},[21,47,48],{},"Descargas."," Sueltas o todas juntas en un ZIP, con una barra de progreso.",[18,51,52,55],{},[21,53,54],{},"Sustituir en vivo."," Cambia una imagen, un SVG o un fondo de la página por un marcador o por una imagen tuya, y lo deshace con un clic. También ocultar elementos y aplicar filtros CSS.",[18,57,58,61],{},[21,59,60],{},"Por elemento."," Localizarlo en la página, copiar su URL o su CSS y sacar su paleta de colores.",[18,63,64,67,68,71,72,75],{},[21,65,66],{},"Enlaces."," Todos los de la página, internos y externos, con búsqueda, copia y exportación a ",[26,69,70],{},".txt"," o ",[26,73,74],{},".json",".",[18,77,78,81],{},[21,79,80],{},"Fuentes."," Qué tipografías usa la página, en cuántos elementos, con vista previa editable, resaltado en la página y descarga de los archivos de las fuentes web.",[18,83,84,87],{},[21,85,86],{},"Instagram."," Botones para descargar historias, posts, reels y fotos de perfil.",[18,89,90,93],{},[21,91,92],{},"Popup y panel de DevTools",", tema claro u oscuro, en español e inglés. Nada sale del navegador.",[10,95,97],{"id":96},"cómo-está-hecho","Cómo está hecho",[99,100,102],"h3",{"id":101},"una-extensión-manifest-v3-con-wxt","Una extensión Manifest V3 con WXT",[104,105,106],"p",{},"WXT genera el manifiesto y los puntos de entrada: el service worker, los content scripts, el popup, el panel de DevTools y un documento offscreen. El popup y el panel montan los mismos componentes de Vue 3.",[99,108,110],{"id":109},"descargas-desde-un-documento-offscreen","Descargas desde un documento offscreen",[104,112,113,114,117,118,121],{},"El service worker no puede crear blobs, así que las descargas y el ZIP (con JSZip) se hacen en un documento offscreen. Para las webs que exigen su ",[26,115,116],{},"Referer",", una regla temporal de ",[26,119,120],{},"declarativeNetRequest"," se aplica solo a las peticiones de la propia extensión.",[99,123,125],{"id":124},"un-motor-de-reemplazo-que-se-puede-deshacer","Un motor de reemplazo que se puede deshacer",[104,127,128,129,132,133,136,137,140],{},"Antes de tocar un elemento se guarda su estado original: ",[26,130,131],{},"src"," y ",[26,134,135],{},"srcset",", fondos, máscaras, pseudo-elementos y el contenido de los SVG. Un ",[26,138,139],{},"MutationObserver"," aplica el cambio a lo que aparece después y, al navegar dentro de una SPA, todo se restaura.",[10,142,144],{"id":143},"problemas-que-resolví","Problemas que resolví",[15,146,147,157,167,173],{},[18,148,149,152,153,156],{},[21,150,151],{},"Reglas de red que se salían de su sitio."," La primera versión usaba reglas persistentes que calculaban mal el dominio (",[26,154,155],{},"co.uk",") y podían quedarse tocando las cabeceras de la navegación normal en ese dominio. Ahora son reglas de sesión, solo para el host exacto del archivo y solo para peticiones que no salen de una pestaña.",[18,158,159,162,163,166],{},[21,160,161],{},"Descargas que se pisaban."," Dos descargas a la vez compartían el progreso. Cada una lleva ahora su id, y la pestaña que la pidió queda guardada en ",[26,164,165],{},"storage.session"," para sobrevivir a un reinicio del service worker a mitad de un ZIP largo.",[18,168,169,172],{},[21,170,171],{},"Una barra de progreso que no se iba."," Un aviso de \"terminado con errores\" se quedaba fijo y parecía que la extensión se había colgado. Ahora se limpia sola y el popup recupera el estado real al reabrirse.",[18,174,175,178],{},[21,176,177],{},"Instagram cambia de idioma y de clases."," Los botones se colocan detectando la estructura de la página, no el texto, y los datos de las historias se leen también del HTML inicial.",{"title":180,"searchDepth":181,"depth":181,"links":182},"",2,[183,184,190],{"id":12,"depth":181,"text":13},{"id":96,"depth":181,"text":97,"children":185},[186,188,189],{"id":101,"depth":187,"text":102},3,{"id":109,"depth":187,"text":110},{"id":124,"depth":187,"text":125},{"id":143,"depth":181,"text":144},"Extensión de Chrome para quien hace webs. Saca todas las imágenes, SVG inline, vídeos, fondos CSS, enlaces y fuentes de una página, los descarga sueltos o en un ZIP y permite sustituir imágenes en vivo para enseñar alternativas. Todo en local, desde el popup o desde un panel de DevTools.","md",true,[195,200,205,210],{"src":196,"alt":197,"width":198,"height":199},"\u002Fimages\u002Fposts\u002Fdevextractor\u002Fportada.webp","El panel de DevExtractor filtrando solo los SVG inline de una web, con los cinco seleccionados y la descarga en ZIP completada",1105,1120,{"src":201,"alt":202,"width":203,"height":204},"\u002Fimages\u002Fposts\u002Fdevextractor\u002Freemplazo-despues.webp","Una web de demostración con el logo y la imagen de portada sustituidos en vivo por otros",1440,600,{"src":206,"alt":207,"width":208,"height":209},"\u002Fimages\u002Fposts\u002Fdevextractor\u002Ffuentes-destacado.webp","La web de demostración con los textos que usan la fuente Hubot Sans recuadrados en verde",1425,820,{"src":211,"alt":212,"width":198,"height":213},"\u002Fimages\u002Fposts\u002Fdevextractor\u002Fenlaces.webp","La pestaña de enlaces con los enlaces internos y externos de la página y los botones para copiarlos o exportarlos",1110,[215,218],{"label":216,"url":217},"Cómo se usa","\u002Fposts\u002Fdevextractor-svg-imagenes-y-fuentes-en-un-clic",{"label":219,"url":220},"Por dentro","\u002Fposts\u002Fdevextractor-por-dentro",{},9,"\u002Fprojects\u002Fdevextractor",{"title":5,"description":191},[226,227,228,229,230,231,232,120,139,233,234,235,236,237],"WXT","Manifest V3","Vue 3","TypeScript","Tailwind CSS","JSZip","Offscreen API","vue-i18n","Vitest","happy-dom","Playwright","GitHub Actions",[239,242,245,248],{"value":240,"label":241},"0","servidores",{"value":243,"label":244},"5","tipos de medio",{"value":246,"label":247},"2","interfaces (popup y DevTools)",{"value":249,"label":250},"486","tests (Vitest)","projects\u002Fdevextractor","devextractor","https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fdevextractor\u002Flkgndoalgpfelahmogbddekpbhcdinjb","YtMV_LraSjYe_m_o_Xeb3G3vTh9Qwz9JCLbezomk7fI",[256,267,276,285,290,299,307,312,319,324,332,338,344,350],{"title":257,"description":258,"date":259,"path":260,"tags":261,"type":266},"Simple TTS: escucha cualquier artículo de la web en voz alta, sin salir de tu navegador","Una extensión gratuita para Chrome y Edge que lee en voz alta lo que selecciones o el artículo entero con las voces del propio navegador. Sin cuentas, sin servidores y sin APIs externas. Qué hace y cómo se usa.","2026-10-23","\u002Fposts\u002Fsimple-tts-escucha-cualquier-web",[262,263,264,265],"simple-tts","privacidad","productividad","proyectos","guia",{"title":268,"description":269,"date":259,"path":270,"tags":271,"type":275},"Simple TTS por dentro: mi primera extensión, de JavaScript a WXT con Vue","Cómo pasó Simple TTS de tres archivos de JavaScript a una extensión Manifest V3 con WXT, Vue 3 y TypeScript. Qué me hizo cambiar en cada etapa, cómo se mantiene vivo un service worker durante una lectura larga, por qué leía la frase del día anterior y cómo se consigue que el resaltado vaya a la par de la voz.","\u002Fposts\u002Fsimple-tts-por-dentro",[262,272,273,274],"arquitectura","typescript","vue","tecnico",{"title":277,"description":278,"date":279,"path":280,"tags":281,"type":275},"TranscribeLocal por dentro: Whisper en el navegador y cómo dejó de inventarse frases","Cómo ejecuta TranscribeLocal Whisper, la separación de hablantes y un modelo de lenguaje dentro del navegador con Transformers.js, por qué Whisper se inventaba frases en los silencios, qué hace falta para transcribir audios largos y por qué un service worker cortaba las descargas a los cinco minutos.","2026-10-22","\u002Fposts\u002Ftranscribe-local-por-dentro",[282,283,272,273,284],"transcribe-local","ia","nuxt",{"title":286,"description":287,"date":279,"path":288,"tags":289,"type":266},"TranscribeLocal: transcribe y saca subtítulos de cualquier vídeo, sin salir de tu navegador","Una app web que convierte audio y vídeo en texto y subtítulos con Whisper, distingue quién habla y hace resúmenes, todo en tu ordenador. Sin subir nada a ningún servidor, sin límites de duración y gratis. Qué hace y cómo se usa.","\u002Fposts\u002Ftranscribe-local-subtitulos-en-tu-navegador",[282,283,263,265],{"title":291,"description":292,"date":293,"path":294,"tags":295,"type":266},"PokeStore: un tema de WordPress a medida para tiendas de cartas coleccionables","Un tema de WordPress y WooCommerce hecho desde cero para tiendas de Pokémon y One Piece TCG. Un recorrido por la tienda como la ve quien compra, de la portada a la lista de deseos, y por lo que puede ajustar quien la lleva.","2026-10-21","\u002Fposts\u002Fpokestore-tema-wordpress-para-tiendas-tcg",[296,297,298,265],"pokestore","wordpress","woocommerce",{"title":300,"description":301,"date":302,"path":303,"tags":304,"type":266},"YouTube Like Shortcut: da like con una tecla, como en TikTok","Una extensión de Chrome que da like al vídeo o Short de YouTube que estás viendo al pulsar la L. Cómo se instala, qué opciones tiene y por qué no se dispara mientras escribes.","2026-10-20","\u002Fposts\u002Fyoutube-like-shortcut-like-con-una-tecla",[305,306,264,265],"youtube-like-shortcut","youtube",{"title":308,"description":309,"date":302,"path":310,"tags":311,"type":275},"YouTube Like Shortcut por dentro: pulsar un botón en una web que cambia sin avisar","Cómo una extensión de una sola función llega antes que el reproductor de YouTube, encuentra el botón de like entre variantes ocultas y layouts que cambian con el ancho de la ventana, y se asegura de que el like ha entrado aunque YouTube esté reconstruyendo la página.","\u002Fposts\u002Fyoutube-like-shortcut-por-dentro",[305,306,272,273],{"title":313,"description":314,"date":315,"path":316,"tags":317,"type":266},"AI Upscaler: amplía imágenes y quita fondos con IA, sin salir de tu navegador","Una extensión de Chrome que amplía imágenes ×2 o ×4 y elimina fondos con inteligencia artificial, todo en tu equipo. Sin subir nada a ningún servidor, sin cuentas y gratis. Qué hace y cómo se usa.","2026-10-19","\u002Fposts\u002Fai-upscaler-imagenes-con-ia-en-tu-navegador",[318,283,263,265],"ai-upscaler",{"title":320,"description":321,"date":315,"path":322,"tags":323,"type":275},"AI Upscaler por dentro: dos redes neuronales en una extensión de Chrome","Cómo ejecuta AI Upscaler Real-ESRGAN y U²-Net dentro del navegador con ONNX Runtime Web, por qué la imagen se procesa en mosaicos con margen, qué hubo que parchear en el modelo de quitar fondos y qué obstáculos pone una extensión Manifest V3 a los workers y al WebAssembly.","\u002Fposts\u002Fai-upscaler-por-dentro",[318,283,272,273,274],{"title":325,"description":326,"date":327,"path":328,"tags":329,"type":266},"ABR Cipher: cifrado seguro, completamente en tu navegador","Una suite de cifrado que no envía nada a ningún servidor. Cifrar textos y archivos, compartir secretos con un enlace que caduca, guardar contraseñas y destruir archivos. Qué hace y cómo se usa.","2026-10-18","\u002Fposts\u002Fabr-cipher-cifrado-en-tu-navegador",[330,331,263,265],"cipher","seguridad",{"title":333,"description":334,"date":327,"path":335,"tags":336,"type":275},"ABR Cipher por dentro: criptografía sin servidor, de la URL al disco","Por qué ABR Cipher solo usa la Web Crypto API, cómo viaja un secreto en el fragmento de una URL, cómo se cifra un archivo de varios GB en el navegador y qué hace falta para destruir un archivo de verdad, con un compañero en Rust que lee NTFS.","\u002Fposts\u002Fabr-cipher-por-dentro",[330,331,272,273,337],"rust",{"title":339,"description":340,"date":327,"path":341,"tags":342,"type":275},"ABReader por dentro: el lector que no encontré, construido a mi medida","Voces neuronales que se ejecutan en el navegador, sincronización entre dispositivos sin servidor y once formatos detrás de un mismo contrato. Las decisiones técnicas de ABReader y por qué las tomé.","\u002Fposts\u002Fabreader-por-dentro",[343,272,273,284],"abreader",{"title":345,"description":346,"date":327,"path":347,"tags":348,"type":266},"ABReader: tu biblioteca de libros electrónicos en el navegador","Un lector de libros electrónicos que funciona en el navegador, sin cuentas ni servidores. Qué hace, cómo se usa y cómo probarlo en un minuto.","\u002Fposts\u002Fabreader-tu-biblioteca-en-el-navegador",[343,349,265],"lectura",{"title":351,"description":352,"date":327,"path":353,"tags":354,"type":275},"Cómo construí este blog: estático, sin servidor y sin coste de hosting","Por qué elegí Nuxt y Markdown en lugar de WordPress, cómo separo el código privado del sitio publicado y qué se rompió en el primer despliegue.","\u002Fposts\u002Farquitectura-de-este-blog",[284,274,355,356],"devops","meta",1792718564290]