Diligentia et artes perfectionem pariunt v8.4
• ¡Por fin parió la burra! Lo que tenéis delante de vosotros es una aplicación web que limpia el código HTML de los edictos. Sin más dilación, os cuento cómo funciona. Lo primero que hacemos es clicar sobre Codi font (no hace falta pasarle el Sense margins), seleccionáis (Ctrl+A) y copiáis (Ctrl+C) todo el batiburrillo que veréis dentro, y lo pegáis (Ctrl+V) en la caja izquierda HTML sucio (Origen) de Limae Labor.
• Hecho esto, pulsáis sobre 🔥 Procesar edicto y en el panel de la derecha, HTML limpio (Maquetación base), os aparecerá el código limpio de polvo y paja. Clicad en 📋 Copiar código, volvéis al editor del BOIB y lo pegáis (Ctrl+V). Salid pulsando sobre Codi font y et voilà!, el edicto habrá pasado por una intensa sesión de maquillaje.
• Tiene más funcionalidades. En el menú central podéis cambiar el ancho de las tablas (del 1 al 100 %), el relleno de las celdas (de 0 a 10), podéis escoger si forzar las cabeceras o disolver tablas de una sola celda y extraer su contenido, eliminar las filas vacías, o bien cargarse las notas de traducción (tipo <Fundamentos/Cimientos>) que encontréis. Si os pegan varias tablas casi idénticas (misma cabecera, distinto contenido) y salen desalineadas porque cada una calcula su ancho de columna a su bola, podéis igualarlas entre sí gracias a la casilla Alinear las serpientes. También he añadido la posibilidad de cambiar de tema: modo oscuro, claro, medianoche, estilos ciberpunk... y tenéis la posibilidad de añadir vuestro propio fondo de pantalla.
• La aplicación tiene un rastreador de filtros que detecta DNI/NIE, notas de traducción, cortes de líneas minúsculas, fallos de estructura, número de tablas, casillas de verificación... También incluye un previsualizador que simula un folio A4. Si no hemos procesado el código, nos mostrará el edicto sucio; si lo hemos hecho, el limpio. Lo chulo es que si el rastreador ha encontrado algo, el visor nos lleva directamente a la alerta y podemos navegar entre los errores con las flechitas. Además, se abrirá automáticamente si detecta algún DNI/NIE, para que no se nos pase por alto con las prisas.
• También he añadido el botón ✏️ Editar texto, que nos permite modificar el edicto directamente antes de copiar el código. Está pensado sobre todo para detectar dónde están las líneas cortadas y dejarnos arreglarlas al vuelo. Una vez hecho esto, clicamos sobre 💾 Guardar texto y copiamos el código como siempre con su botón correspondiente. Puede pasar que le deis sin querer a Limpiar entrada, así que he añadido el botón ↩️ Recuperar borrado que os devuelve el código último. Y el botón 🕘 Recientes guarda los últimos diez edictos sucios que habéis procesado, por si queréis recuperar alguno. ¡Nunca se sabe!
• Para terminar, lo he testeado hasta la extenuación, pero es inevitable que se pueda haber colado algún error o que falten mejoras (plausibles) por implementar. Estaré atento a vuestras sugerencias. Con esto el gorrinete se puede tomar unas merecidas vacaciones, pero si algún edicto se pone muy farruco, una combinación ganadora es pasarle Limpia, fija y da esplendor y luego Limae Labor, con esto quedará perfecto; aunque esto no quita que tengamos que seguir picando piedra, pero menos. ¡Espero que os guste!
• Filtro de prelavado y normalización: La herramienta elimina de entrada caracteres invisibles de control (como el "zero-width space") y unifica los espaciados redundantes o tabulaciones procedentes del portapapeles de Microsoft Word. Corrige también las erratas de traducción más comunes y las firmas digitales automatizadas.
• Desacoplamiento de estilos estáticos: Toda la carga gráfica, variables de color de la interfaz, temas visuales y animaciones han sido extraídos por completo del esqueleto estructural e integrados en un archivo de estilos independiente. Esto optimiza la velocidad de renderizado del navegador y la fluidez del editor de texto.
• Aislamiento de la raya y el guion medio: Se implementa un detector táctico perimetral que intercepta los caracteres de raya larga (—) y guion medio (–). El sistema frena la conversión accidental de estos elementos en viñetas estándar de lista, manteniendo el carácter original y unificando el texto flotante colindante.
• Inyección de sangría justificada: A todos los párrafos libres que arrancan con estos guiones específicos se les aplica de forma automática un estilo optimizado con un margen izquierdo estricto de 40 píxeles, garantizando la homogeneidad visual con el resto del cuerpo del edicto maquetado.
• Censo por columna, no por celda: Cada celda de una columna emite un único voto (izquierda, centro o derecha) según su naturaleza: importes y formatos europeos votan a la derecha, fechas y celdas con un 30% o más de cifras votan al centro, y el texto descriptivo vota a la izquierda. Gana la opción más votada de toda la columna, y esa alineación se aplica a TODAS las celdas de esa columna por igual, sin excepciones ni celdas "rebeldes".
• Coherencia visual garantizada: Este sistema evita el efecto "zigzag" que producían las versiones anteriores, donde una celda con un nombre corto podía acabar centrada mientras el resto de la columna quedaba a la izquierda. Ahora la columna decide de una vez, como un consejo de vecinos bien avenido.
• Preservación de estructuras anidadas: El algoritmo de conversión analiza el árbol de elementos utilizando selectores de ámbito directo. Cuando identifica una lista secundaria anidada dentro del elemento de una lista principal, el motor rescata y extrae de forma independiente la sublista antes de proceder con la limpieza del nodo contenedor.
• Blindaje contra la pérdida de información: Esta extracción controlada evita que el borrado físico de una lista superior destruya por accidente los sub-apartados o temas inferiores, garantizando que el bucle procese la información en su totalidad y de manera consecutiva.
• Cerebro secuencial de errores: El visor integra un rastreador en tiempo real que localiza fallos críticos de seguridad (como números de documento de identidad expuestos), expresiones de traducción cruzadas, casillas rotas del procesador de textos y cortes huérfanos de párrafo.
• Mapeado de teclado físico: Se implementa un escuchador de eventos que permite saltar de forma ágil entre las alertas del visor utilizando las flechas de dirección del teclado (flecha derecha para avanzar, flecha izquierda para retroceder) y realizar el cierre inmediato de la previsualización mediante la tecla de escape.
• Vista de lectura, no de código: El botón ✏️ Editar texto sustituye el código HTML por su apariencia real: el texto tal y como se vería impreso, sin etiquetas de por medio. Es la misma diferencia que hay entre leer una partitura y escuchar la música.
• Flechas fantasma: Si el rastreador ha detectado líneas cortadas (las que empiezan por minúscula, normalmente fruto de un salto de línea manual en el documento original), aparecen dos flechas semitransparentes flotando sobre el texto, ◀ y ▶, que llevan el cursor directamente al corte anterior o siguiente. También se puede navegar con Ctrl + flecha izquierda/derecha (o arriba/abajo) sin tocar el ratón.
• Guardado consciente: Al pulsar 💾 Guardar texto los cambios se vuelcan de nuevo al código y a la previsualización en folio A4, así que el visor refleja exactamente lo último que se ha tecleado. Si en su lugar se pulsa Limpiar entrada o 🔥 Procesar edicto estando en modo edición, los cambios sin guardar se descartan automáticamente, igual que pasaría con cualquier editor de texto si cierras sin guardar.
Este bloque lo lee sobre todo el asistente al empezar una conversación nueva: está escrito para que sirva de punto de partida sin redescubrir nada. Orden: guía, mapa del motor, bancos de datos, trampas conocidas, pendientes y, al final, el historial de versiones compactado. La v8.3 conserva el historial original completo, sin compactar, por si hiciera falta algún detalle.
Qué es. LIMAE LABOR limpia el HTML sucio de los edictos (normalmente exportado de Word, Office Online o SharePoint) para pegarlo en el editor de destino del boletín (BOIB). Textos en catalán y castellano. Todo el trabajo lo hace procesarHTML(): lee #html-input, escribe #html-output, actualiza el rastreador (DNI, notas de traducción, casillas rotas, cortes, tablas) y alimenta la previsualización tipo folio A4. La salida va en spans Times New Roman de 12px.
Principios que no hay que romper. Un único archivo HTML sin recursos externos. Ninguna llamada de red de ningún tipo: los edictos contienen datos personales y la app tiene que funcionar siempre, también sin conexión. Solo usa localStorage (diseño/tema y Recientes). Se publica como página estática: el archivo se sube renombrado a index.html. El favicon es el emoji 🪶 como texto dentro de un SVG incrustado, con un PNG de 32px de respaldo renderizado desde Noto Color Emoji (Apache 2.0); no incrustar iconos de sets de terceros.
Versiones y entrega. En cada cambio se actualizan dos líneas, <title>LIMAE LABOR vX</title> y <p>Diligentia et artes perfectionem pariunt vX</p>, y se añade una entrada arriba del historial. Cambio pequeño: sube el número de detrás (7.9 → 7.10 → 7.11). Cambio importante o de arquitectura: sube el de delante (7.x → 8). Al usuario no le gustan los saltos grandes: retirar una función no justifica subir el número principal (una v9 hecha solo para quitar algo se rehízo como v8.2). El archivo se entrega como Limae_Labor_vX_Y.html. En la época v5 se usaban sufijos de letra (v5.9a, v5.10b...).
Peticiones típicas. Casi siempre son entradas nuevas en listas. Antes de añadir, comprobar que no existe ya y que de verdad no se reconoce (prueba antes y después). FRASES_FIRMA es la línea de fecha ("Ciudad, frase de firma") y CARGOS_CONOCIDOS la línea del cargo: el usuario a veces dice "cargos" refiriéndose a una frase de fecha (pasó en v7.2); se pone en la lista correcta y se le dice. Las dos listas se comparan sin distinguir tildes en las vocales (escaparConAcentoInsensible), así que una variante que solo cambia ó/ò/o no necesita entrada nueva; cualquier cambio de palabra sí ("de" o "a", con o sin "la", "firma" o "signatura"). Se guarda la grafía correcta del idioma. Se responde en castellano y en tono cercano.
Cómo verificar (método usado desde v6). Cargar el archivo real con jsdom (runScripts: "dangerously", pretendToBeVisual: true), poner el HTML sucio en #html-input, llamar a window.procesarHTML() y leer #html-output. En cada cambio: 1) reproducir el caso que falla y comprobar que deja de fallar; 2) casos de control de lo que ya funcionaba; 3) comparar la salida de la versión anterior contra la nueva en una batería (listas mixtas, firmas, tablas con rowspan, ordinales, notas de traducción con la opción marcada y desmarcada...) y confirmar que solo cambia lo previsto; 4) invariante fuerte: el texto ni se pierde ni se reordena (así apareció el bug de v7). Para cambios que no tocan el motor, comprobar además que fuera de la zona tocada el archivo es idéntico.
Limitaciones de jsdom. No implementa scrollTo ni scrollIntoView (errores inofensivos al abrir el editor o el visor) ni medidas reales (getBoundingClientRect devuelve 0), así que "Alinear las serpientes" solo se puede verificar en un navegador real; en la época v5 se usó Playwright/Chromium. El portapapeles hay que simularlo. Los errores dentro de procesarHTML() se capturan (console.error y aviso "Error crítico"): en jsdom hay que reenviar window.console, o la salida simplemente queda vacía sin explicación (pasó en v5.17).
Formato de este historial. Los temas de la app solo dan estilo a <h3> y <p> dentro de .help-section; usar solo esas etiquetas más <strong>, <code> y <br> para que se lea bien en todos los temas.
Cada pasada lleva su comentario de cabecera en el código; conviene buscarlas por el nombre, porque los números de línea cambian.
1. Entrada. Texto a NFC → <br> literales escapados → capas <div> de Office Online/SharePoint y <div> de Word con border:none → notas de traducción <A[...]> (se borran si "Borrar notas de traducción" está marcado; si no, quedan ocultas con display:none) → casillas rotas (regexCasillas → □).
2. Estructura básica. "Sense margins" ×2 automatizado → EDICTO/EDICTE/ANUNCIO/ANUNCI suelto al principio → espacio tras marcador numérico pegado → cabeceras estructurales ANEXO/TÍTULO/CAPÍTULO (REGEX_CABECERA_ESTRUCTURAL).
3. Limpieza de formato. Spans de Word (se les quita el estilo si lleva font-family, font-size, line-height o display:inline-block, y se desenvuelven si se quedan sin estilo ni clase) → colores y fondos (sin colorines en tablas) → márgenes de Word en <p> → value inválidos en <li>.
4. Motor de listas (bucle sobre listasDeWord, instantánea tomada antes del bucle): <ol class="circle"> como <ul>, duplicado de <ol class="circle"> anidada, rescate de <li> con varios <p>, <ol> doblemente anidada, extracción en su sitio en <ul> mixtas (extraerEnSuSitio), rescate de sublistas.
5. Texto. Guiones largos/medios (respetando esFraseExceptuada()) → desenvolver <div> estructurales → <sup>/<sub> vacíos → blindaje de abreviaturas ante el separador "a.Texto".
6. Tablas. Tabla especial "Margarita Prohens"/"Antoni Costa" → desenvolver tablas-envoltorio de una celda → eliminar filas vacías → celdas combinadas en cabecera → filas de cabecera por rowspan → índice de columna real → votación por columna → columnas de puntuación centradas → cabeceras inteligentes → alinear las serpientes.
7. Párrafos. Rótulos presupuestarios en mayúsculas y negrita → guion/punto a <ul> → fusión de títulos con su subtítulo (data-merged-title) → detección de cortes (data-corte) → rótulos de sección (Antecedentes/Fets/Hechos...) → ordinales en negrita → guion corto aislado → sangrado a 40px de marcadores de lista (regexNumComplejo) → float en <img> → roturas de numeración a 80px → herencia de margin-left:40px en <ul> → limpieza final de spans de cada <p> (protegiendo las notas ocultas) → agrupación de <ul> consecutivas → • redundante en <li> → interletraje expandido.
8. Firmas. Definición de listas (CIUDADES_FIRMA, FRASES_FIRMA...) → banco de firmas por nombre (estándar, fija, incrustada, todo en uno) → firma sin ciudad → mecanismo general (fecha + cargo + nombre, con nombre incrustado o en párrafo aparte) → firma con fecha explícita, con el caso temporal del Administrador tributari en el mismo tramo.
9. Salida. Viñetas explícitas por nivel → retirada de marcadores internos → finalHtml a NFC → volcado en #html-output → aviso de truncamiento (umbral 0,98).
Fuera de procesarHTML(). openPreviewModal() (visor A4 y resaltado de DNI, con su propio regexDNI), toggleEditOutput() (editor WYSIWYG: "Editar texto" / "Guardar texto"), copyToClipboard() y fallbackCopyToClipboard(), panel de Recientes (guardarEdictoReciente(), renderRecientes()...), temas y diseño (cambiarTemaPredefinido()...).
**Configuración global, al principio del <script>:** FRASES_EXCEPTUADAS_BASE (guiones que no deben convertirse en lista; se comparan tras normalizarGuionInicial(), que unifica el guion inicial y los apóstrofes tipográficos) y ORDINALES_PRINCIPALES_50.
Dentro de procesarHTML(), tramo de firmas: CIUDADES_FIRMA (los 67 municipios de Balears e Ibiza), FRASES_FIRMA, FRASES_FIRMA_SIN_CIUDAD, MESES_FIRMA (para REGEX_FECHA_EXPLICITA), CARGOS_CONOCIDOS (genera REGEX_CARGO_CONOCIDO), BANCO_DE_FIRMAS, BANCO_FIRMAS_FIJAS, BANCO_FIRMAS_INCRUSTADAS y BANCO_FIRMAS_TODO_EN_UNO.
Otros: PALABRAS_MAYUSCULAS_NEGRITA (rótulos presupuestarios), PALABRAS_INTERLETREADAS (interletraje expandido), PALABRAS_PUNTUACION (columnas de puntuación), REGEX_ACORD_ACUERDO, REGEX_ROTULOS_SECCION, REGEX_ORDINAL_INICIO, regexOrdinalesInicialesPlanos, ORDINALES_CAB_CA y ORDINALES_CAB_ES, regexCasillas, FACTOR_MIN_APRIETO (serpientes, 0,6) y regexDNI, que está DUPLICADO (en openPreviewModal() y en el contador de procesarHTML()): cualquier cambio, en los dos sitios.
**\b en JavaScript solo entiende ASCII.** Falla junto a ñ y vocales con tilde, y también después de un signo como el punto de "acctal.". Ha causado bugs en v5.4, v5.10b y v6 (cuatro veces). Usar límites manuales como (?![A-Za-zÀ-ÿ]). Lo mismo con [a-z], que no incluye la ñ (v6, filtro de cortes).
ñ y NFD. En NFD la ñ se descompone en n + tilde combinante (U+0303), que cae en el rango que se borra al quitar tildes (\u0300-\u036f). Nunca quitar tildes a una cadena entera si hay que conservar la ñ: escaparConAcentoInsensible va carácter a carácter (v6). La entrada y la salida se normalizan a NFC (v7.3).
**querySelector en singular donde puede haber varios** descarta el resto en silencio: así se perdía texto de <li> con varios <p> (v5.1). Comprobar siempre con querySelectorAll.
Sacar elementos de una lista conserva el orden solo si se parte la lista. En <ul> mixtas se usa extraerEnSuSitio() (v7); la rama <ol> extrae todos los ítems y borra la lista, así que no tiene el problema. El bucle exterior recorre la instantánea listasDeWord: las listas creadas durante el bucle no se reprocesan ahí (no lo necesitan; el pase de viñetas por nivel hace una consulta nueva). Extraer sublistas al DOM padre reordena el contenido (v3.1).
**Reasignar innerHTML con una sublista dentro crea un clon** y duplica texto: extraer antes la sublista (v4.0).
Un regex que quita etiquetas sueltas una a una no sabe a qué elemento pertenece un cierre (</span> no lleva atributos). Para conservar un elemento entero, protegerlo antes con un marcador y restaurarlo después (notas ocultas, v7.12). El mismo truco del marcador temporal sirve para que algo sobreviva a una limpieza posterior (%%ESPACIADOR-FIRMA%%, v4.2).
Los regex que detectan una "etiqueta suelta" deben anclarse al final. Si no, se tragan párrafos completos: fusión de títulos (v6) y subtítulo de REGEX_CABECERA_ESTRUCTURAL, limitado a 150 caracteres y excluido dentro de <li> (v5.12).
**[style*="color"] también encuentra background-color**, pero removeProperty('color') no lo quita: son propiedades distintas (v4.8).
Spans con display. La limpieza de spans vacía display:inline-block (v7.11), pero NUNCA display:none: así se ocultan las notas de traducción cuando la opción de borrarlas está desmarcada (v7.12).
Marcadores internos. data-corte se queda a propósito en #html-output porque el editor lo necesita para resaltar y navegar entre cortes; se quita solo al copiar (limpiarMarcadoresInternos(), v7.13). data-trozo-lista, data-tabla-firma-especial y data-merged-title se retiran antes de la salida. Al escribir dentro de una zona resaltada, algunos navegadores convierten el fondo CSS en style="background-color:..." real: limpiarResaltadosDeEdicion() lo quita al guardar (v8.1).
DNI/NIE. La clase de la letra de control debe excluir solo I, O y U; la letra se valida con esLetraDNIValida() (módulo 23; X/Y/Z del NIE valen 0/1/2). Dos copias de regexDNI (ver bancos de datos).
Firmas. Lo que sigue al cargo en su mismo párrafo es el nombre incrustado salvo que empiece por "(" (es una anotación, normalmente la fecha; v7.6). Los cargos sin negrita solo se aceptan tras confirmar la fecha de firma (v5.7). El párrafo del nombre es opcional (v5.5f). El <strong> del cargo puede venir dentro de un span extra: trabajar con sus hermanos reales, no con los hijos del <p> (v5.16).
Orden de declaración. Usar una función o constante antes de su declaración da un ReferenceError que el try/catch de procesarHTML() convierte en salida vacía (v5.17).
Tablas. La columna real se calcula con la rejilla de ocupación indicesRealesPorFila (rowspan/colspan, v3.8e). El bloque de cabecera se calcula por ocupación de rowspan (filasCabeceraCompuesta); una fila de datos con su propio span no es cabecera (v5.16). Las celdas con una tabla anidada se saltan en el motor general (v4.6). esFilaTotalesOVacia no excluye una fila TOTAL que tenga números (v5.5a).
Caso temporal del Administrador tributari (v5.9a). Bloque autocontenido para la firma de Justo Alberto Roibal Hernández. Hay que quitarlo cuando dejen de llegar esos edictos: preguntar al usuario de vez en cuando.
Firma con prefijo y "Sense margins" (v5.16). Con "Sense margins" aplicado, el caso de firma con prefijo ("Per delegació del president,") quedaba como cinco párrafos sueltos, porque el mecanismo espera el cargo justo después de la fecha. Desde v5.15 "Sense margins" se aplica siempre, así que puede ser el camino normal; no se ha vuelto a comprobar. Consultar con el usuario antes de abordarlo.
Firma doble. Con dos firmantes seguidos, solo se formatea el primer bloque; el segundo queda en párrafos justificados (visto en la auditoría de v7). No abordado; consultar con el usuario si quiere que se formatee también.
• v8.4 — Historial reorganizado como guía de mantenimiento:
- Añadidos la guía, el mapa del motor, los bancos de datos, las trampas conocidas y los pendientes. Historial compactado: fuera la narración repetida de verificaciones; se conservan las decisiones y su porqué. Sin cambios de código.
• v8.3 — Favicon: el emoji 🪶 como texto en un SVG incrustado (cada sistema lo dibuja con su propia fuente de emojis) y PNG de respaldo renderizado desde Noto Color Emoji (Apache 2.0). Se descartó incrustar una imagen de un set de emojis de terceros que pasó el usuario.
• v8.2 — Favicon intermedio (pluma crema con contorno y fondo transparente, sustituido en v8.3). Frase "data a la signatura electrònica".
• v8.1 — Fondo del resaltado de cortes convertido en estilo real al editar:
- Al escribir dentro de una línea resaltada como corte, algunos navegadores convierten el fondo CSS del resaltado en style="background-color: rgba(239, 68, 68, 0.15)" sobre el texto tocado (le pasaba a un compañero y no al usuario, con el mismo documento). limpiarResaltadosDeEdicion() quita background, background-color y outline del estilo en línea al pulsar "Guardar texto", sin tocar el resto del estilo; también se aplica a nivel de texto en limpiarMarcadoresInternos() al copiar.
• v7.14 — Frase "con fecha de la firma electrónica".
• v7.13 — data-corte en el código copiado:
- data-corte="true" se queda a propósito en #html-output (el editor lo necesita para resaltar y navegar con ◀ ▶), pero nunca se quitaba del código que se copia para el boletín. limpiarMarcadoresInternos() lo quita solo al copiar, tanto con navigator.clipboard como con el respaldo de execCommand, sin tocar la caja de texto: si se vuelve a editar, la navegación sigue funcionando.
• v7.12 — Nota de traducción oculta que se volvía visible:
- Con "Borrar notas de traducción" desmarcado, la nota (<span style="display:none">) perdía la ocultación en la limpieza final de spans de cada <p>, que quita etiquetas sueltas una a una (solo conserva las de display:block, los espaciadores de firma) y deja el contenido.
- No vale añadir display:none a la excepción: un </span> no lleva atributos, así que se conservaría la apertura sin su cierre y el span oculto se tragaría el resto del párrafo. Solución: cada span display:none completo se guarda en notasOcultasProtegidas y se sustituye por %%NOTA-OCULTA-n%% antes de la limpieza; después se restaura tal cual.
• v7.11 — <span style="display:inline-block"> como único contenido de un <li> descuadraba la viñeta (el punto salía en la línea siguiente), porque la limpieza de spans solo vaciaba el estilo con font-family, font-size o line-height. Añadido display:inline-block (exactamente esa, no display a secas, para no tocar display:none).
• v7.10 — Primer favicon: pluma de fénix sobre el azul de la app (sustituido en v8.2/v8.3). Antes se probó y se descartó una lima de herrero.
• v7.9 — Validación de la letra del DNI/NIE y bug de NIE:
- esLetraDNIValida(): un DNI/NIE solo se cuenta si su letra cuadra (número, con X/Y/Z del NIE como 0/1/2, módulo 23 sobre TRWAGMYFPDXBNJZSQVHLCKE). Evita falsos positivos como el expediente "2026/00008766Q" (le correspondería la A) y no puede descartar un DNI real.
- Bug de paso, un falso negativo: la clase de letra del NIE era [A-HJNP-TV-Z] (faltaba un guion) y excluía K, L y M; igualada a la del DNI, que solo excluye I, O y U. Comprobadas las 23 letras.
- Cargo "l'alcaldessa".
• v7.8 — Cargos "el consejero", "la consejera".
• v7.7 — "▢" (U+25A2) añadido a regexCasillas.
• v7.6 — Fecha entre paréntesis pegada al cargo tomada por nombre:
- El mecanismo de nombre incrustado (v5.6c, v5.16) tomaba como nombre todo lo que siguiera al <strong> del cargo. Con la fecha pegada al cargo ("El conseller executiu d'Hisenda... (15 de setembre de 2026)"), ignoraba el nombre real del párrafo siguiente y limpiarYUnirLineas partía "(", la fecha y ")" en tres líneas.
- Ahora, si lo que sigue al cargo empieza por "(" (esAnotacionEntreParentesis), no es nombre: se reconstruye sin espaciadores y rellena el "()" de la línea de fecha.
• v7.5 — "Alinear las serpientes" marcada por defecto (checked en opt-align-snakes; ninguna persistencia lo sobrescribe).
• v7.4 — Frase "signat electrònicament" (sin "document"). Se pidió con "ó"; da igual para el reconocimiento, pero se guarda con la grafía catalana.
• v7.3 — Entrada y salida normalizadas a NFC:
- html.normalize('NFC') al leer y finalHtml.normalize('NFC') al final. Un texto en NFD (letra + tilde combinante) se ve igual pero rompe las comparaciones por carácter del motor, y algunos generadores de PDF pintan la tilde flotando. Surgió por tildes flotantes en PDF de edictos de Calvià; su HTML resultó estar ya en NFC, así que el fallo venía del generador de PDF del usuario (ajeno a esta app). La normalización es preventiva.
• v7.2 — Frase "a data de la firma electrònica" (se pidió como cargo; es una frase de fecha).
• v7.1 — Cargos "el consejero ejecutivo", "la consejera ejecutiva".
• v7 — Reordenación de contenido en <ul> mixtas (grave):
- Las dos ramas que sacan un <li> fuera de la lista (el que tiene varios <p> y el que empieza por raya) insertaban siempre delante de toda la lista, así que un ítem en medio o al final saltaba por delante de los anteriores: se publicaba la "Base segunda" antes que la "Base primera", sin perder ni una palabra.
- extraerEnSuSitio() parte la lista: [ul con los ítems previos] [contenido extraído] [ul nueva con el resto, mismos atributos]. Los trozos llevan data-trozo-lista para que la herencia de margin-left:40px no los sangre distinto (se retira antes de la salida), y se eliminan los trozos vacíos (de paso desaparece un <ul></ul> vacío que ya quedaba antes). La rama <ol> no estaba afectada.
• v6 — Auditoría a fondo: cuatro bugs:
- Fusión de títulos: regexArticulosYDisposiciones y regexCabecerasMayores no tenían ancla de fin, así que un artículo completo seguido de un párrafo con negrita (por ejemplo, el número en negrita del artículo siguiente) se lo tragaba y fusionaba dos artículos. Ahora solo cuenta como etiqueta suelta "Article N." sin más texto ("Disposición..." acotada en longitud); las cabeceras mayores tienen una versión estricta (dispara la fusión) y otra amplia (solo para el guardián "el siguiente ya es cabecera").
- regexNumComplejo usaba \bñ, así que "ñ." y "ñ)" no funcionaban desde v5.2; quitado el \b. Añadida la ñ al filtro de cortes ([a-zñ]).
- iniciaPorEstructuraClave: \b tras ordinales en -è (cinquè... desè) impedía centrar TÍTOL/SECCIÓ con esos ordinales; cambiado a (?![A-Za-zÀ-ÿ]).
- escaparConAcentoInsensible hacía NFD sobre la frase entera y convertía la ñ en n; ahora va carácter a carácter.
• v5.17 — tieneCeldasCombinadas vuelve a mirar solo la primera fila (una combinada en la segunda no descuadra la cabecera). Columnas cuya cabecera es Puntuació/Puntuación/Punts/Puntos, centradas siempre (una puntuación con coma decimal votaba como importe). Al hacerlo, normalizarParaComparar se usó antes de su declaración y dio salida vacía; se resolvió con una normalización local.
• v5.16 — Ordinales, importes, cabecera compuesta y firma duplicada:
- Ordinales de regexOrdinalesInicialesPlanos del 6 al 12 (ca/es, masculino y femenino). Cargos técnico, tècnic, técnica, tècnica. Importes en euros con 1 o 2 decimales (con 1 votaban center).
- Cabecera compuesta (v5.5a): una fila de datos con su propio rowspan/colspan se tomaba por cabecera; ahora una fila posterior a la primera solo es cabecera si hereda ocupación de rowspan.
- Firma duplicada: si un span extra envolvía el párrafo del cargo, el <strong> no era hijo directo del <p> y su contenido se capturaba dos veces. Ahora se usan los hermanos reales del <strong>, se excluyen los espaciadores, se admite un prefijo antes del cargo ("Per delegació del president,") como línea propia y varias líneas se unen con espaciador. Queda un pendiente (ver Pendientes).
• v5.15 — "Sense margins" ×2 automatizado como primera pasada: se quitan las declaraciones margin-* de cada <p> (y el atributo style si queda vacío), lo mismo que hacían los dos clics manuales antes de pegar. Los margin-left:40px finales vienen de pasadas posteriores.
• v5.14 — Margen de 10px arriba y abajo en cada <ul> anidada (no en la de nivel 0 ni en cada <li>) para separar nivel y subnivel. Cargos "el batlle", "la batllessa", "la alcaldessa". Frase "en la fecha indicada en la firma electrónica".
• v5.13 — list-style-type explícito en línea según la profundidad (disc → circle → square → disc...) y un pequeño margen entre <li>, porque el editor de destino no aplica el ciclo implícito y mostraba el mismo disco, muy apretado, en todos los niveles. Los <ol> no se tocan.
• v5.12 — REGEX_CABECERA_ESTRUCTURAL tiene un subtítulo voraz hasta el final del párrafo y convertía en cabecera párrafos enteros de lista ("Anexo. Modelo de solicitud..."). Se excluyen los párrafos dentro de <li> y el subtítulo se limita a 150 caracteres.
• v5.11 — Cargos teniente de alcalde, tinent de batle, administrador tributario, administrador tributari, primer tinent de batle, primer teniente de alcalde, primera tinenta de batle, primera teniente de alcalde (sin artículo). Frases "en data de signatura electrònica", "en fecha de firma electrónica". Se eliminan los <sup>/<sub> sin contenido visible; los que tienen contenido ("1r") se conservan.
• v5.10b — Cargos batle, alcalde, batlessa y alcaldesa con "acctal." y con "accidental" (sin artículo). REGEX_CARGO_CONOCIDO acababa en \b, que falla tras el punto de "acctal."; cambiado a (?![A-Za-zÀ-ÿ]).
• v5.10a — "Ibiza" en CIUDADES_FIRMA (junto a Eivissa). Cargos "el batle-president", "la batlessa-presidenta" (con guion).
• v5.10 — Firma sin ciudad: "(Signat electrònicament: )" o "(Firmado electrónicamente: )" solo, en su propio párrafo (FRASES_FIRMA_SIN_CIUDAD). Reutiliza la lógica de cargo y nombre del mecanismo general; el disparador se centra en cursiva tal cual, sin "()".
• v5.9c y v5.9b — Cargos "el síndic major", "el síndico mayor", "el vicepresident", "el vicepresidente", "la vicepresidenta".
• v5.9a — Caso temporal (ver Pendientes): bloque autocontenido para la firma de Justo Alberto Roibal Hernández (fecha explícita + "L'Administrador tributari" o "El Administrador tributario" + nombre + nota opcional "(Per suplència" / "(Por suplencia"), sustituida por el bloque exacto pedido, con saltos de línea manuales. No generaliza a otras notas.
• v5.9 — Espacio que falta tras un marcador numérico de 1 a 3 dígitos pegado al texto ("1.Texto", típico de tramos con formato distinto), recorriendo nodos de texto. Solo si sigue una letra (no rompe "1.234 euros") y sin duplicar el espacio.
• v5.8 — Ordinales en negrita aunque vengan repartidos en varios nodos de texto ("PRIMER" + "O" en spans distintos): se consumen nodos hasta completar ordinal y punto, con varios <strong> seguidos si hace falta.
• v5.7 — CARGOS_CONOCIDOS: el cargo de la firma se reconoce también sin negrita, pero solo después de haber confirmado la fecha de firma (evita falsos positivos). Aplica al mecanismo general y al de fecha explícita.
• v5.6c — Frases "document signat electrònicament", "documento firmado electrónicamente", "a la data de signatura electrònica". Cargo y nombre en el mismo <p> (nombre incrustado) en el mecanismo general y el de fecha explícita. Marcadores de letra doble (aa., bb)...) válidos para el detector de cortes. Se quita el punto final sobrante de fecha, cargo y nombre en las firmas.
• v5.6b — Ayuda ("Guiaburros"): cerrados dos <p>, corregidas erratas y añadidas Alinear las serpientes, Eliminar filas vacías, Recuperar borrado y Recientes.
• v5.6a — Frases "en la data de signatura electrònica", "en la fecha de firma electrónica".
• v5.6 — Celdas de 2 o 3 letras todo en mayúsculas ("PO", "CC", "CCE") votan center, como códigos.
• v5.5h y v5.5g — Frases "amb data de la signatura electrònica", "con fecha de firma electrónica", "a data de la signatura electrònica", "a fecha de la firma electrónica".
• v5.5f — Párrafo del nombre opcional en el mecanismo general y el de fecha explícita: fecha + cargo solos forman un bloque de 2 líneas.
• v5.5e — FRASES_FIRMA sin distinguir tildes en las vocales (cada vocal acepta agudo, grave o ninguna; la salida conserva la grafía original). Frases "a data de signatura electrònica", "a fecha de firma electrónica".
• v5.5, v5.5b y v5.5c — Alinear las serpientes:
- Casilla en Propiedades de tablas. Agrupa tablas con la misma forma de cabecera combinada (número de columnas y patrón de colspan/rowspan, sin mirar el texto), mide con el layout del navegador el ancho natural de cada columna y aplica un ancho de consenso.
- El consenso fue primero el máximo (v5.5: un caso extremo inflaba a todas), luego la mediana con límite de aprieto FACTOR_MIN_APRIETO = 0.6 (v5.5b: dispersaba columnas con variación continua) y por último la valla de Tukey (v5.5c): se excluyen los valores por encima de Q3 + 1,5×RIC y el consenso es el máximo de los restantes; los atípicos se aprietan hasta el consenso sin bajar del 60% de lo que necesitan.
• v5.5a — Cabecera combinada a dos filas: esPrimeraFilaCombinada solo excluía la fila 0 y la fila 1 votaba como datos. Sustituido por filasCabeceraCompuesta, el bloque de cabecera calculado por ocupación de rowspan.
• v5.4 — BANCO_FIRMAS_INCRUSTADAS: José Luis Camps Pons (Es Castell), ca/es; el mecanismo salta ya cualquier número de <p> vacíos intermedios. Interletraje expandido ampliado a APLICACIÓ/APLICACIÓN, Crèdit/Crédito, Import/Importe y Previsions/Previsiones, identificados por el texto exacto (no por la longitud, porque varias coinciden) y con límites manuales en vez de \b. El "()" recordatorio pasa de <i> a <em>.
• v5.3 — Firma con fecha explícita ("Ciudad, [a] día de mes de año", año 19xx o 20xx): quita la "a " inicial, conserva "de"/"d'" y fusiona cargo y nombre como el resto. Frases "en la data de la signatura electrònica/digital", "en la fecha de la firma digital".
• v5.2 — Tres tipos más de banco de firmas: fija (sin fecha, con una línea "(Signat electrònicament: )"; Paz Andrade), incrustada (ciudad y fecha dentro del último párrafo, "Signat as Mercadal en la data..."; Joan Palliser; la búsqueda ignora el artículo salat "Es") y todo en uno (todo en una sola frase; Antonia Camps). "Vaciar recientes" fijo en la parte de abajo del panel. "ll." y "ñ)" como marcadores de lista (la ñ no funcionó hasta v6).
• v5.1 — Pérdida de texto en <li> con varios <p> (grave):
- En cuatro sitios del motor de listas se tomaba solo el primer <p> con querySelector(':scope > p') y se descartaba el resto. Ahora el primero se queda en el <li> y los demás salen como párrafos tras la lista.
- Recientes: sin la "✕" de cada tarjeta y con una vista previa de 320 caracteres recortada a 3 líneas. Umbral de truncamiento de 0,80 a 0,98, descontando el texto de las notas de traducción borradas.
• v5.0 — Botón Recientes: los 10 últimos edictos sucios procesados con éxito, en localStorage (sin repetir el primero), recuperables con un clic. Al abrirse oculta el fondo, como Ayuda e Historial, para que Ctrl+F no busque detrás.
• v4.9 — REGEX_ORDINAL_INICIO con sufijo de género opcional ("Segon.", "Tercer.") y una comilla de apertura opcional antes del ordinal (fuera de la negrita).
• v4.8 — Sin colorines en tablas: se quitan background, background-color, background-image y bgcolor de la tabla y de todo su contenido.
• v4.7 — Una tabla exterior de una sola celda que solo envuelve otra tabla se sustituye por la interior (siempre, independiente de "Extraer tablas 1x1"); las celdas se cuentan con :scope.
• v4.6 y v4.6a — Palabra suelta inicial que se elimina: EDICTO (solo si es el primer párrafo con contenido), EDICTE, ANUNCIO y ANUNCI. Tabla especial de firma ampliada a "Antoni Costa". Guion/punto a lista reescrito: solo tandas de 2 o más consecutivos con el mismo marcador, conservando negrita y cursiva. Rectificación: Debe decir, On hi diu, Hi ha de dir. Las celdas con una tabla anidada se saltan en el motor general y la interior se procesa aparte. Quitada otra cursiva de cabeceras (font-style:italic) y arreglada una concatenación de estilos sin punto y coma.
• v4.5 — BANCO_DE_FIRMAS: correcciones de redacción por persona, reconocida por el nombre normalizado, en cualquier orden y con líneas intermedias; el rango a sustituir se amplía al tamaño esperado del bloque. Óscar Portas Juan, Catalina Rosa Pons Taberner y Francisca M. Matas Escobar (ca/es). Tres frases de firma nuevas. Cabeceras sin cursiva.
• v4.4 y v4.4a — Aviso de truncamiento: se decodifican las entidades ( , &...) antes de comparar longitudes. "()" en cursiva si falta la fecha en la línea de firma.
• v4.3 — CIUDADES_FIRMA con los 67 municipios de Balears; se exige la coma tras el nombre completo, así que "Sant Joan" no choca con "Sant Joan de Labritja".
• v4.2 — Bloque de firma electrónica: "Ciudad, en data/fecha de la signatura/firma..." + cargo en negrita + nombre → separador, fecha centrada y cargo y nombre fusionados con un salto visual. El separador usa el marcador %%ESPACIADOR-FIRMA%% para sobrevivir a una limpieza de espacios posterior. El corte tras paréntesis, corchete o comilla respeta enlaces y correos.
• v4.1 — PALABRAS_MAYUSCULAS_NEGRITA (DESPESES, INGRESSOS, CRÈDIT EXTRAORDINARI...): mayúsculas y negrita cuando van solas en un párrafo, con o sin ":".
• v4.0 — <ol class="circle"> con <li> fantasma duplicaba texto al reasignar innerHTML con la sublista dentro: se extrae la sublista antes. "Balears"/"Baleares" al inicio de párrafo, como corte. REGEX_ACORD_ACUERDO amplía a RESOLC/RESUELVO.
• v3.9 a v3.9f — Quitado el salto de página automático de anexos (v3.9). Tabla "Margarita Prohens" reconstruida antes del motor general de tablas (v3.9a). La nota de traducción oculta se ve resaltada en el visor; al abrir un modal se oculta .container para que Ctrl+F no busque detrás; fuera align de las tablas (v3.9b). Fondo de los modales con degradado del tema (v3.9c, v3.9d). Casilla "Eliminar filas vacías" (no toca tablas con celdas combinadas); corte tras "(", "[" o "«" seguidos de minúscula; enlaces exceptuados también en tablas; Donde dice / Tiene que decir; un <u> con una URL pura pierde el subrayado sin pasar a negrita (v3.9e). Casillas de tabla sin línea de puntos (v3.9f).
• v3.8 a v3.8f — ACORD/ACUERDO en negrita, recomponiendo el interletraje con TreeWalker (v3.8); salto de página automático de anexos (v3.8a y v3.8b, quitado en v3.9). <div> con <p> y <table> mezclados: se desenvuelve si tiene al menos un hijo de bloque (v3.8c). Botón "Guardar texto"; celdas combinadas mirando dos filas (vuelto a una en v5.17); rótulo Proposta/Propuesta de resolució(n) (v3.8d). Abreviaturas p.d./p.o. que rompía un regex anterior; columna real con rowspan mediante indicesRealesPorFila (v3.8e). esFraseExceptuada() en los guiones largos/medios (v3.8f).
• v3.7 a v3.7b — Alineación de tablas por votación: cada celda vota (monetario, fecha, 30% o más de dígitos, texto) y gana la mayoría de la columna; flechas fantasma para navegar entre cortes (v3.7). TOTAL ya no se centra a la fuerza; float en <img> neutralizado (v3.7a). autocomplete="off" en Ancho, Relleno y Cabeceras (v3.7b).
• v3.6 a v3.6f — Editor del HTML limpio dentro del panel de salida (WYSIWYG), con los cortes resaltados (v3.6). Limpiar entrada y Procesar edicto cancelan la edición; Copiar avisa si hay cambios sin guardar; fusión de "Article 2" + título con vacíos intermedios (v3.6d). Apóstrofes tipográficos normalizados antes de comparar excepciones (v3.6e). Un tercer bloque de conversión a lista ignoraba esFraseExceptuada() (v3.6f).
• v3.5 y v3.5b — Capas <div style="border:none..."> de Word; • redundante en <li>; truncamiento medido sobre texto plano. ORDINALES_PRINCIPALES_50 hasta cinquantè, con formas compuestas y femeninas.
• v3.4 — Aviso de truncamiento; agrupación de <ul> consecutivas; rótulo Interposició/Interposición de recursos; ordinales en negrita al inicio de párrafo; excepción "- Informe d".
• v3.3 — Cabeceras inteligentes en tablas sin <thead> (fila en negrita y sin dígitos); fila TOTAL; rescate de <li> con varios párrafos.
• v3.2 — Filas de totales y vacías fuera del voto de columna; capas <div class="BCX..."> de Office Online/SharePoint.
• v3.1 — Guion corto aislado sin sangría; <ul> anidadas procesadas en su sitio (extraerlas reordenaba el contenido).
• v3.0 — Error DOMException: Node.insertBefore con padres distintos; detección de NIE; apertura automática del visor si hay DNI/NIE.
• v2.9b — <u> pasa a <strong>; excepción p.d./p.o.; márgenes de Word; badges unificadas.
• v2.8 — REGEX_CABECERA_ESTRUCTURAL unificado: ANEXO/TÍTULO/CAPÍTULO con ordinales romanos, arábigos y textuales, en castellano y catalán.
• v2.7 — Blindaje XSS (atributos sin comillas, javascript:); carrusel de errores circular; bug «Guadiana»: getAbsoluteOffsetTop() en vez de getBoundingClientRect().
• v2.6 — Scrollbars de 5px; orden de data-forced-center (Resolució no se centraba); Escape cierra los modales.
• v2.5 — Fuera line-height en los spans; <ol class="circle"> pasa a <ul>; rótulos de sección (Antecedentes/Fets/Hechos); sistema FRASES_EXCEPTUADAS.
• v2.0.0 — Rescate de sublistas anidadas en <ol> con :scope > ol, :scope > ul (desaparecía texto).
• v1.9.9 — Navegación con flechas y Escape en el visor; alineación predictiva de tablas por número de caracteres.
• v1.9.0 — Rastreador con contadores (DNI, notas de traducción, casillas rotas); personalización estética persistente en localStorage.
Todavía no hay ningún edicto guardado. En cuanto proceses uno, aparecerá aquí.