Diligentia et artes perfectionem pariunt v7.10
• ¡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.
• v7.10 — Icono de pestaña (favicon):
- Añadido un icono de lima de herrero (guiño al "limae labor": el trabajo de pulir con la lima) sobre el azul corporativo de la app, con hoja plateada de picado en diagonal y mango ámbar. Incrustado en línea en el <head> —SVG para navegadores modernos y PNG de 32 px de respaldo— para que la app siga siendo un único archivo sin dependencias externas. Diseñado y comprobado a tamaño real de pestaña (16 y 32 px) sobre fondos claro y oscuro. Sin cambios en el motor: comparada la salida contra v7.9 en la batería de regresión, idéntica.
• v7.9 — Validación matemática de la letra del DNI/NIE (evita falsos positivos) y arreglo de un bug real que causaba falsos negativos con el NIE:
- Pedido por el usuario: regexDNI solo comprobaba la FORMA (8 dígitos + letra válida, o X/Y/Z + 7 dígitos + letra) y nunca la letra de control en sí, así que cualquier número de 8 dígitos + letra con esa forma se marcaba como DNI aunque no lo fuera — p. ej. un nº de expediente como "2026/00008766Q" (la letra que le correspondería a 00008766 es "A", no "Q"; no puede ser un DNI real). Añadida esLetraDNIValida(), que calcula la letra por el algoritmo oficial (número, con el prefijo X/Y/Z del NIE sustituido por 0/1/2, módulo 23, indexado en la tabla fija de 23 letras) y descarta las coincidencias cuya letra no cuadre. Un DNI/NIE real SIEMPRE cumple este cálculo por definición, así que esta validación nunca puede dejar de detectar uno genuino.
- Bug real encontrado de paso (más grave que el anterior, un FALSO NEGATIVO): al construir los casos de prueba para lo anterior con NIEs reales de control, se descubrió que a regexDNI le faltaba un guion en la clase de caracteres de la letra del NIE — tenía [A-HJNP-TV-Z...] en vez de [A-HJ-NP-TV-Z...] (que sí tenía correcto la rama del DNI de 8 dígitos, un poco más abajo en el mismo regex) — y por esa falta de guion excluía K, L y M además de I, O, U, cuando la tabla oficial de letras de control solo excluye I, O, U. Un NIE real acabado en K, L o M —perfectamente posible y válido— pasaba completamente desapercibido para el detector. Arreglado igualando ambas ramas.
- Verificado exhaustivamente: generadas las 23 letras de control posibles matemáticamente válidas (antes solo se cubrían 20, con K/L/M ausentes) y las 23 se detectan correctamente; el caso del expediente reportado ya no cuenta como DNI; comparada la salida contra v7.8 en toda la batería de regresión, idéntica en todos los casos salvo el cargo nuevo (l'alcaldessa, ver abajo).
- Añadido "l'alcaldessa" a CARGOS_CONOCIDOS (forma catalana con apóstrofo elidido, junto a "la alcaldessa" ya existente sin elidir). Verificado que se reconoce.
• v7.8 — Añadidos 2 cargos a CARGOS_CONOCIDOS:
- "el consejero", "la consejera" (equivalente castellano de "el conseller"/"la consellera", ya existentes, y forma corta de "el consejero ejecutivo"/"la consejera ejecutiva", añadidas en v7.1). Verificado que se reconocen y que la variante "ejecutivo/ejecutiva" ya existente sigue funcionando igual; sin cambios en el resto de la batería de regresión.
• v7.7 — Añadido 1 carácter a regexCasillas:
- U+25A2 "▢" (WHITE SQUARE WITH ROUNDED CORNERS) no estaba en la lista de glifos de casilla rota que se normalizan a "□" (U+25A1). Añadido junto a los ya existentes (\u00FF y los tres del rango privado de Wingdings/Symbol, más ☐ U+2610). Verificado que "▢" pasa a "□" y que los seis glifos que ya funcionaban siguen intactos; comparada la salida contra v7.6 en toda la batería de regresión, idéntica salvo el caso nuevo.
• v7.6 — Bug real: una fecha entre paréntesis pegada al cargo se confundía con el nombre incrustado:
- El mecanismo de "nombre incrustado en la misma línea que el cargo" (v5.6c/v5.16) asumía que CUALQUIER contenido tras el <strong> del cargo, en el mismo párrafo, era el nombre del firmante. Con un original tipo <strong>El conseller executiu...</strong><span>(<em>15 de setembre de 2026</em>)</span> —la fecha de firma pegada al cargo en vez de en la línea de ciudad, patrón real de un edicto de Calvià—, esto tenía dos efectos: 1) el nombre real del firmante, que sí venía en el párrafo siguiente, se ignoraba y quedaba suelto sin fusionar; 2) limpiarYUnirLineas mete un espaciador de línea entre cada nodo —pensado para reunir líneas REALES distintas del original—, pero aquí los nodos eran solo texto-elemento-texto normal ("(", el <em> de la fecha, ")"), partiendo la fecha en tres líneas visuales sueltas en vez de una. No se perdía texto, pero la salida quedaba estructuralmente rota.
- Arreglo: se distingue por delante un contenido que empieza por "(" —ningún nombre real empieza así— y, si lo hace, no se trata como nombre incrustado: se reconstruye tal cual (sin espaciadores de por medio) y se usa para rellenar el "()" que de otro modo quedaría vacío en la línea de ciudad/fecha, mientras el nombre real del párrafo siguiente sigue su camino normal.
- Verificado con el caso real reportado (texto íntegro, orden conservado, fecha reubicada en su sitio habitual, nombre real recuperado) y con tres casos de control sin tocar: nombre genuino incrustado sin paréntesis, prefijo + cargo + nombre incrustados, y una anotación entre paréntesis que no es fecha (también se reubica bien). Comparada la salida contra v7.5 en toda la batería de regresión: idéntica en todos los casos salvo el corregido.
• v7.5 — "Alinear las serpientes" activada por defecto:
- El checkbox opt-align-snakes ahora nace marcado (checked); no había persistencia en localStorage ni otro sitio que lo sobreescribiera, así que ha bastado con el atributo. Verificado que la casilla llega marcada, que el código se activa sin errores, y comparada la salida contra v7.4 en toda la batería de regresión: idéntica en todos los casos (incluido uno con dos tablas gemelas). Aviso honesto: esta función mide anchos reales con getBoundingClientRect() sobre una tabla oculta, algo que el entorno de pruebas headless (jsdom) no simula de verdad —siempre da 0—, así que el resultado visual pixel a pixel no se ha podido reverificar aquí; el mecanismo en sí ya quedó verificado con Playwright/Chromium (navegador real) en v5.5b/v5.5c.
• v7.4 — Añadida 1 frase a FRASES_FIRMA:
- "signat electrònicament" (sin la palabra "document" delante; ya existía "document signat electrònicament"). Se pidió con tilde "electrónicament" (accento castellano); no hacía falta corregirlo porque la insensibilidad a acentos ya trata ò/ó/o como intercambiables —comprobado que ambas grafías generan el mismo patrón—, pero se guarda con la tilde catalana correcta para mantener la lista consistente. Verificado: "Calvià, signat electrònicament" no se reconocía antes y ahora sí; comparada la salida contra v7.3 en toda la batería de regresión, idéntica salvo en este caso nuevo.
• v7.3 — Normalización a NFC del texto, entrada y salida (caso de las tildes flotantes en PDF de Calvià):
- Unicode permite escribir una letra acentuada de dos formas: precompuesta ("ó" como un único carácter, NFC) o descompuesta ("o" + tilde combinante suelta, NFD). Se ven igual en el editor —el navegador las recompone al pintar— pero muchos generadores de PDF caseros no hacen ese "shaping" y la tilde se queda flotando por encima en vez de sobre la letra. Si el texto de origen llega en NFD también podía fallar en silencio cualquier comparación por carácter del motor (clases [a-záéíóúñüç], topónimos, cargos, frases de firma...), ya que "à" en NFC y "à" en NFD no son la misma cadena aunque se vean igual.
- Se añade html.normalize('NFC') nada más leer la entrada (para que todo el motor trabaje siempre sobre texto compuesto) y otra vez sobre finalHtml justo antes de la salida, como cinturón de seguridad. No cambia ni una letra visible.
- Verificado: se generó una copia de un edicto real con las tildes descompuestas a propósito (simulando el caso reportado) y la salida sale perfectamente compuesta, sin marcas sueltas, y con el bloque de firma reconocido igual que con la entrada normal. Comparada la salida de v7.2 contra esta versión en los 25 casos de la batería de regresión: IDÉNTICA byte a byte en todos — el cambio es invisible salvo cuando la entrada viene en NFD.
• v7.2 — Añadida 1 frase a FRASES_FIRMA:
- "a data de la firma electrònica" (sin "la" antes de "data" y con "firma" en vez de "signatura"; ya existían "a data de la signatura electrònica" y "a la data de la firma electrònica", pero no esta combinación exacta). Se pidió añadirla a CARGOS_CONOCIDOS, pero es una frase de la línea de fecha, no un cargo — añadida en su sitio correcto. Verificado: "Calvià, a data de la firma electrònica" no se reconocía antes (bloque de firma completo sin procesar) y ahora sí; las variantes ya existentes y el resto de la batería de regresión, sin cambios.
• v7.1 — Añadidos 2 cargos a CARGOS_CONOCIDOS:
- "el consejero ejecutivo", "la consejera ejecutiva" (equivalente castellano de "el conseller executiu"/"la consellera executiva", ya existentes). Verificado con jsdom: se reconoce igual que el resto de cargos conocidos en el mecanismo de firma, y no afecta a ningún caso de la batería de regresión.
• v7 — Bug grave de REORDENACIÓN de contenido en listas <ul> mixtas:
- Bug real (grave): en el motor de listas, las dos ramas que sacan contenido fuera de un <ul> —el <li> "complejo" (varios <p> dentro) y el <li> que empieza por raya (— o –)— insertaban ese contenido con lista.parentNode.insertBefore(nodo, lista), es decir SIEMPRE delante de la lista entera. Eso solo es correcto si el ítem extraído es el PRIMERO: si estaba en medio o al final, su contenido saltaba por delante de todos los ítems anteriores, que se quedaban dentro del <ul>. En un edicto real esto publicaba, por ejemplo, la "Base segunda" (con sus párrafos) ANTES que la "Base primera". No se perdía ni una palabra —solo cambiaba el orden—, que es justo lo que lo hacía difícil de detectar en una revisión visual rápida. Curiosamente, el comentario de la rama de viñeta normal, justo debajo, ya advertía del peligro ("Extraerlas al DOM padre rompería el orden de los ítems") mientras la rama de arriba hacía exactamente eso.
- Alcance acotado con casos de control: la rama <ol> NO estaba afectada (allí se extraen TODOS los ítems en orden y luego se borra la lista, así que nunca se mezclan extraídos con no extraídos) y se deja intacta. Un <ul> homogéneo tampoco estaba afectado. El fallo era exclusivo del <ul> MIXTO, precisamente por ser selectivo: unos ítems salen y otros se quedan.
- Arreglo: nueva función extraerEnSuSitio() que PARTE la lista en vez de sacar el contenido al principio. El contenido extraído se coloca justo después de la lista que conserva los ítems anteriores, y los ítems posteriores se trasladan a un <ul> nuevo (con los mismos atributos) que va a continuación: [ul: ítems previos] [contenido extraído] [ul: resto]. Funciona igual con varios ítems extraídos en la misma lista (se parte tantas veces como haga falta) y en cualquier posición: primero, en medio o último.
- La partición es invisible: los trozos generados se marcan con data-trozo-lista para que la regla de herencia de margin-left:40px (v2.5) no los indente por ir detrás del párrafo extraído — si no, la cola de una lista partida se vería con distinto sangrado que su cabeza. La marca se retira antes de la salida, junto a la de data-tabla-firma-especial.
- Extra: al partir se limpian los trozos que se queden sin ningún <li>. Esto arregla de paso un resto cosmético que ya existía antes: una lista de la que se extraían todos los ítems (p. ej. todos de guion) dejaba un <ul></ul> vacío en la salida.
- Verificación: el bug se encontró comprobando el invariante "el texto no se pierde ni se reordena" con jsdom sobre una batería de edictos realistas, no por lectura. Tras el arreglo se comparó la salida de v6 contra la de v7 en 25 casos: cambian SOLO los que tienen un <ul> mixto afectado (más el del <ul> vacío); salen IDÉNTICOS los de <ol>, <ul> homogéneo, sublistas de tres niveles, fusión de <ul> consecutivos, guiones, tablas con rowspan/cabecera compuesta, firmas, párrafos vacíos, ordinales y los cuatro arreglos de v6. En particular sale idéntico el caso del <li> complejo en PRIMERA posición, que ya funcionaba bien y se conserva byte a byte.
• v6 — Cuatro bugs reales de \b/acentos y anclaje de regex, encontrados en auditoría a fondo:
- Bug real (el más grave): en la fusión de títulos, regexArticulosYDisposiciones y regexCabecerasMayores no tenían ancla de fin ni tope de longitud, así que no distinguían una etiqueta SUELTA ("Article 2." esperando su subtítulo en el párrafo siguiente) de un artículo YA COMPLETO ("Article 1. Les infraccions es classifiquen en..."). Un artículo completo seguido —tras saltar vacíos— de CUALQUIER párrafo con negrita, incluida la numeración en negrita del artículo siguiente (patrón habitual en exportaciones de Word), se tragaba ese párrafo entero y fusionaba dos artículos independientes en un único bloque forzado a negrita. Ahora regexArticulosYDisposiciones exige que tras el número/palabra no quede más que un punto opcional (y lo mismo, acotado en longitud, para "Disposición..."); regexCabecerasMayores se separa en una versión estricta (solo etiqueta suelta, la que dispara la fusión) y una amplia que se mantiene solo para el guardián "¿el siguiente párrafo ya es cabecera por su cuenta?", donde pecar de amplia es seguro (nunca causa una fusión mala, como mucho evita una buena). Verificado: artículos consecutivos con numeración en negrita ya no se fusionan; "TÍTOL III..." con texto propio deja de tragarse el artículo anterior; la fusión legítima ("Article 2." suelto + subtítulo en negrita, "Disposició addicional primera." + régim transitori, "TÍTOL III" suelto + subtítulo) sigue funcionando igual, y dos cabeceras mayores consecutivas siguen sin fusionarse entre sí.
- Bug real: regexNumComplejo usaba \bñ\. / \bñ\) para los marcadores de lista "ñ." y "ñ)" (añadidos en v5.2). En JavaScript, \b solo reconoce como letra el rango ASCII ([A-Za-z0-9_]); la "ñ" queda fuera, así que ese límite nunca se cumplía y esos marcadores no han funcionado nunca desde que se añadieron. Quitado el \b en esos dos casos (redundante de todos modos: el grupo ya va anclado con ^).
- Bug real relacionado, encontrado al verificar el anterior: arreglado el marcador, un párrafo "ñ)" bien indentado seguía marcándose además como "corte" (línea sospechosa) porque el filtro de exclusión de cortes usaba [a-z] (ASCII) para los marcadores de una letra, sin incluir la ñ. Añadida.
- Bug real: mismo problema de \b + letra acentuada en iniciaPorEstructuraClave: los ordinales catalanes acabados en "-è" (cinquè, sisè, setè, vuitè, novè, desè) nunca cerraban el límite de palabra final, así que un "TÍTOL DESÈ..." o "SECCIÓ NOVÈ..." nativo en negrita y mayúsculas se quedaba sin centrar como cabecera (a diferencia de CAPÍTOL, que ya tenía su propio patrón correcto con (\.|\s|$) en vez de \b). Sustituido el \b final por (?![A-Za-zÀ-ÿ]), el mismo patrón que ya usa REGEX_CARGO_CONOCIDO desde v5.10b.
- Bug real (bomba de relojería): escaparConAcentoInsensible quitaba las tildes de TODA la frase de golpe (NFD + borrar \u0300-\u036f) antes de trocearla letra a letra. Como la "ñ" se descompone en NFD como "n" + tilde combinante (U+0303), que cae dentro de ese mismo rango que se borra, se convertía silenciosamente en "n" antes de llegar al mapeo de vocales. El regex resultante dejaba de matchear la "ñ" real del original y solo matcheaba la "n" pelada — justo lo contrario de "insensible a acentos". Ningún cargo/frase de CARGOS_CONOCIDOS/FRASES_FIRMA lleva ñ hoy, así que no se notaba, pero habría roto la primera entrada nueva que la llevara (p. ej. "año", "compañ..."). Ahora se normaliza carácter a carácter, conservando el original cuando no es una vocal (a/e/i/o/u).
- Los cuatro bugs se encontraron primero por lectura y se confirmaron después ejecutando el motor real (no solo leyéndolo) contra casos de prueba con jsdom; cada arreglo se verificó igual, contrastando el caso que fallaba antes y después, y comprobando que los casos ya correctos seguían sin cambios.
• v5.17 — Límite de cabecera combinada solo a la primera fila; columnas de "Puntuación" siempre centradas:
- Revertido el límite de tieneCeldasCombinadas (v3.8d) de "dos primeras filas" a solo la primera: según confirma el usuario, la cabecera solo se descuajeringa si la celda combinada está en la primera fila (la que se convierte en <thead><th>); una celda combinada en la segunda fila no afecta al forzado de cabeceras.
- Nueva detección: si la cabecera de una columna dice "Puntuació", "Puntuación", "Punts" o "Puntos" (sin mirar mayúsculas/acentos), esa columna se fuerza a centrado, sin importar el voto numérico. Motivo: una puntuación con coma decimal ("0,25", "1,75") es indistinguible de un importe en euros para el sistema de votación, que la alineaba a la derecha como si fuera dinero. Al implementarlo se coló un bug real de referencia temporal (normalizarParaComparar se usaba antes de su propia declaración, más abajo en el archivo — daba ReferenceError en tiempo de ejecución, silenciado en la consola, dejando la salida vacía); corregido con una normalización propia local en vez de depender del orden de declaración. Verificado con las tablas reales del usuario (inglés y francés, 7 filas cada una): columna "Puntuación" centrada en las 7; regresión completa de tablas de euros normales (siguen a la derecha), tablas con serpientes y firma del edicto real sin duplicar, todo sin cambios.
• v5.16 — Ordinales hasta el 12, cargo técnico, importes con 1 decimal, bug real de cabecera combinada en fila de datos, y bug real de duplicación de firma:
- Ampliados los ordinales de regexOrdinalesInicialesPlanos del 6 al 12, en castellano y catalán, masculino y femenino.
- Añadidos "técnico", "tècnic", "técnica" y "tècnica" a CARGOS_CONOCIDOS.
- Bug real: el reconocimiento de importes en euros exigía exactamente 2 decimales (,\d{2}$); una tabla con cifras de 1 decimal ("8.200,0", "132,0"...) no lo cumplía, esas celdas no votaban "right" y arrastraban la columna a "center". Ahora acepta 1 o 2 decimales.
- Bug real: la detección del bloque de cabecera compuesta (v5.5a) trataba CUALQUIER fila con su propio colspan/rowspan como parte de la cabecera, aunque fuera una fila de DATOS legítima (p. ej. una celda de categoría fusionada con rowspan a lo largo de varias filas de datos). Eso la forzaba a centrado y la sacaba de la votación normal. Ahora una fila > 0 solo se considera cabecera si hereda ocupación de rowspan de una fila anterior, no por tener su propio span.
- Bug real (duplicación de firma): cuando el original no había pasado por "Sense margins" primero, una pasada anterior podía envolver todo el contenido del párrafo de cargo en un <span> extra, dejando el <strong> anidado un nivel más adentro de lo esperado. El código asumía que era hijo directo del <p>, así que capturaba el mismo contenido dos veces (una como "cargo", otra como "nombre"). Ahora se buscan los hermanos reales del <strong> (su padre efectivo, sea cual sea), se excluyen los espaciadores propios del original, se soporta un prefijo antes del cargo (tipo "Per delegació del president,") como línea propia, y varias líneas encontradas se unen con el espaciador en vez de concatenarse sin más. Verificado con Playwright sobre el edicto real completo del usuario (antes: firma duplicada; ahora: correcta) y regresión completa de tablas, firma normal, firma temporal y ordinales sin cambios.
- Pendiente, no abordado hoy: con "Sense margins" aplicado (flujo normal), este mismo caso queda como 5 párrafos sueltos en vez de uno con saltos internos, y el mecanismo actual no lo reconstruye porque espera el cargo justo después de la fecha, sin ningún párrafo-prefijo de por medio. Consultar con el usuario antes de abordarlo.
• v5.15 — "Sense margins" ×2 automatizado, lo primero que hace el motor:
- Antes de pegar en Limae Labor, el flujo manual era: seleccionar todo en el editor de destino y pulsar "Sense margins" dos veces (la primera normaliza cualquier margin-* del <p> a los cuatro lados en 0px; la segunda quita esas declaraciones por completo, dejando el espaciado natural). Analizado con diffchecker sobre un edicto real (original / primer clic / segundo clic) para confirmar el comportamiento exacto, incluidas las sangrías (margin-left), que también desaparecen del todo.
- Implementado como una única pasada al principio de todo (justo tras parsear el documento, antes de cualquier otra cosa): se quitan las declaraciones margin-* del style de cada <p> (en cm, px, lo que sea), y si no queda nada más en el style, se quita el atributo entero — el resultado neto de los dos clics manuales, en un solo paso. Verificado con Playwright sobre el edicto real completo del usuario: cero restos de márgenes heredados de Word tras esta pasada; los margin-left:40px que sí sobreviven en el resultado final vienen de la sangría de guiones (mecanismo posterior que fija su propio margen, sin conflicto). Regresión completa de tablas, firma, ordinales fragmentados y lista anidada sin cambios.
• v5.14 — Separación tipo "salto de línea" entre nivel y subnivel de lista; batlle/batllessa/alcaldessa; frase de firma nueva:
- Margen adicional (10px arriba y abajo) en cada <ul> ANIDADA (no en cada <li>), para separar visualmente un nivel de su subnivel al entrar y al volver — sin afectar al espaciado entre elementos del mismo nivel, que sigue tan junto como siempre. La <ul> de nivel 0 no lleva este margen (no hay ningún nivel padre del que "entrar"). Verificado con Playwright y captura de pantalla comparando con la referencia del usuario: coincide.
- Añadidos "el batlle", "la batllessa" y "la alcaldessa" a CARGOS_CONOCIDOS.
- Añadida "en la fecha indicada en la firma electrónica" a FRASES_FIRMA. Regresión completa de tablas y lista simple sin anidar (sin margen extra en nivel 0) sin cambios.
• v5.13 — Estilo de viñeta explícito por nivel (efecto "pegote" al pegar en el editor de destino):
- El navegador aplica por defecto el ciclo disco→círculo→cuadrado en <ul> anidadas sin que haga falta decirlo explícitamente, pero el editor de destino donde se pega el código limpio no respeta ese comportamiento implícito y mostraba el mismo disco negro en todos los niveles, con las líneas muy pegadas entre sí ("efecto pegote").
- Ahora se fuerza el list-style-type explícito (inline, en el propio <ul>) según la profundidad real de anidamiento — disc → circle → square → disc... — y se añade un pequeño margen entre <li> para separar visualmente los puntos. Los <ol> no se tocan (su numeración ya es explícita por naturaleza). Verificado con Playwright y capturas de pantalla: incluso forzando un CSS "hostil" que resetea list-style-type a disc en todos los niveles (simulando el comportamiento plano del editor de destino), la lista sigue mostrando •, ○, ▪ correctamente porque el estilo inline gana por especificidad. Regresión completa de tablas, lista simple sin anidar y <ol> sin cambios.
• v5.12 — Bug de cabecera estructural (ANEXO/TÍTULO/CAPÍTULO) tragándose párrafos enteros de lista:
- El grupo de subtítulo de REGEX_CABECERA_ESTRUCTURAL es voraz ((?:\.\s*.+)?$, hasta el final del párrafo), pensado para títulos cortos tipo "ANEXO I. Disposicions generals". Sin exclusión de listas, un punto de lista que empezara igual (p. ej. "Anexo. Modelo de solicitud de la subvención. Será cumplimentado...", un párrafo entero) también hacía match y se convertía por completo en mayúsculas + negrita + centrado, rompiendo la homogeneidad visual del resto de la lista.
- Arreglado con dos capas: (1) se excluyen los párrafos dentro de <li>, ya que un título estructural real nunca es un punto de lista; (2) límite de seguridad de 150 caracteres en el subtítulo capturado, por si acaso, fuera de listas. Verificado con Playwright y capturas de pantalla antes/después sobre un caso real de lista anidada a 3 niveles del usuario, y confirmado que una cabecera real fuera de lista sigue centrándose y poniéndose en mayúsculas como siempre. Regresión completa de tablas sin cambios.
• v5.11 — Cargos y frases nuevos, y limpieza de <sup>/<sub> vacíos:
- Añadidos 8 cargos a CARGOS_CONOCIDOS: "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, tal cual se pidieron).
- Añadidas "en data de signatura electrònica" y "en fecha de firma electrónica" a FRASES_FIRMA (sin ningún "la").
- Nueva limpieza: un <sup>/<sub> sin contenido visible (vacío o con solo un espacio/ ) se elimina — residuo típico de Word (p. ej. una referencia o nota al pie borrada que deja el formato vacío detrás) que, al ser un elemento con desplazamiento de línea propio, podía introducir un pequeño desajuste visual aunque no mostrara ningún carácter. Un <sup>/<sub> con contenido real (p. ej. un ordinal "1r") no se toca. Verificado con Playwright los tres cambios, y confirmado que el caso con contenido real se conserva; regresión completa de tablas sin cambios.
• v5.10b — 8 cargos "acctal./accidental" en CARGOS_CONOCIDOS, y bug de \b tras punto literal:
- Añadidos "batle acctal.", "alcalde acctal.", "batlessa acctal.", "alcaldesa acctal.", "batle accidental", "alcalde accidental", "batlessa accidental" y "alcaldesa accidental" (sin artículo, tal cual se pidieron).
- Al añadirlos se detectó que REGEX_CARGO_CONOCIDO terminaba en \b, que falla justo después de un carácter no alfabético como el punto de "acctal." (mismo tipo de bug que tuvimos con los acentos en v5.5e): con punto y luego coma/espacio/fin de cadena, ambos lados de la posición son "no palabra" y \b nunca encuentra frontera. Sustituido por un límite manual (?![A-Za-zÀ-ÿ]) que no tiene ese problema. Verificado con los 8 cargos nuevos, un control de falso positivo (una palabra que solo empieza igual no se activa), y regresión completa de tablas, cargos ya existentes y la firma sin ciudad (v5.10).
• v5.10a — Ibiza en CIUDADES_FIRMA; batle-president/batlessa-presidenta con guion:
- Añadido "Ibiza" (junto a "Eivissa", que ya estaba) a la lista de ciudades. Añadidos "el batle-president" y "la batlessa-presidenta" (con guion) al banco de cargos conocidos, junto a las variantes ya existentes con espacio. Verificado que los tres se reconocen correctamente.
• v5.10 — Firma sin ciudad: "(Signat electrònicament: )" / "(Firmado electrónicamente: )" en su propio párrafo:
- Nuevo mecanismo para cuando el disparador de firma electrónica no va pegado a una ciudad ("Ciudad, frase...", que exige REGEX_FIRMA_ELECTRONICA) sino que aparece SOLO, en su propio párrafo, entre paréntesis. No encajaba en FRASES_FIRMA por eso.
- Reutiliza la misma lógica de cargo/nombre del mecanismo general (nombre incrustado, nombre opcional, banco de cargos conocidos), pero el párrafo del disparador se centra y se pone en cursiva tal cual, sin ciudad que anteponer ni "()" de recordatorio (ya trae sus propios paréntesis). Verificado con los dos casos reales del usuario (catalán y castellano) y regresión completa, incluido el caso temporal del Administrador tributari (v5.9a), sin cambios.
• v5.9c — Síndic major/síndico mayor en CARGOS_CONOCIDOS:
- Añadidos "el síndic major" y "el síndico mayor" al banco de cargos conocidos. Verificado que se reconocen sin negrita en los dos idiomas.
• v5.9b — Vicepresidente/a en CARGOS_CONOCIDOS:
- Añadidos "el vicepresident", "el vicepresidente" y "la vicepresidenta" al banco de cargos conocidos. Verificado que se reconocen sin negrita en los dos idiomas.
• v5.9a — ⏳ CASO TEMPORAL: firma "Administrador tributari" (Justo Alberto Roibal Hernández):
- Lote concreto de edictos de agosto de 2026 con una firma que no encajaba en los mecanismos generales: capitalización distinta del cargo ("L'Administrador"→"L'administrador") y una nota de suplencia larga que cuelga del nombre con saltos de línea manuales que no se corresponden con ningún ancho medible (comprobado con Playwright que ni siquiera el ancho real de la página A4 los reproduce).
- Bloque autocontenido y marcado como temporal: detecta fecha explícita + cargo "L'Administrador tributari"/"El Administrador tributario" + nombre "Justo Alberto Roibal Hernández" + nota opcional que empieza por "(Per suplència"/"(Por suplencia", y lo sustituye por el bloque exacto pedido (cargo en minúsculas salvo la inicial, nota con los saltos manuales tal cual se pidieron). Quitar este bloque cuando dejen de llegar este tipo de edictos — no es un mecanismo general, no generaliza a otras notas de longitud distinta. Verificado con los dos casos reales (catalán y castellano) y regresión completa sin cambios.
• v5.9 — Espacio tras marcador numérico pegado al texto:
- A veces "1." (u otro número de lista de 1 a 3 dígitos) queda pegado directamente al texto siguiente sin espacio, normalmente porque el marcador y el texto están en tramos con formato distinto (p. ej. "1." en negrita+cursiva y el texto solo en cursiva, sin espacio de por medio en el HTML de origen). Nueva pasada temprana e independiente que detecta el caso y añade el espacio que falta, recorriendo los nodos de texto por si el propio marcador viniera repartido entre varios (igual que con los ordinales textuales de la v5.8).
- Exige que tras el marcador venga una LETRA, no un dígito, para no romper números con separador de miles al inicio de frase (p. ej. "1.234 euros..." no se toca). Tampoco duplica el espacio si ya lo había. Verificado con el caso real del usuario y varios controles (número con miles, ya espaciado, año de 4 dígitos, dos dígitos pegados); confirmado además que el caso del año de 4 dígitos ya se comportaba así ANTES de este cambio (no es cosa de esta pasada). Regresión completa de tablas y ordinales sin cambios.
• v5.8 — Ordinales en negrita: soporte para ordinal repartido en varios nodos de texto:
- El envoltorio en negrita de "Primero.", "Segundo.", etc. al inicio de párrafo solo miraba el PRIMER nodo de texto del párrafo y comprobaba si él solo ya contenía el ordinal completo. Cuando el origen fragmenta el ordinal en varios tramos, cada uno en su propio <span> anidado (p. ej. "PRIMER" y "O" en spans distintos — típico de exportaciones de Word/Google Docs), el primer nodo de texto solo tenía "PRIMER" y la comprobación fallaba: no se ponía en negrita, sin avisar de nada.
- Ahora se recorren los nodos de texto en orden y se consume cada uno hasta completar la longitud del ordinal+punto, envolviendo en <strong> cada tramo en su sitio (varios <strong> adyacentes si hace falta, que renderizan igual que uno solo). Verificado con Playwright y captura de pantalla sobre el documento real del usuario: PRIMERO, SEGUNDO y TERCERO (los tres repartidos en varios spans) se ponen en negrita correctamente.
• v5.7 — Banco de cargos conocidos, alternativa a la negrita:
- El cargo de un bloque de firma se reconocía solo si estaba en <strong>; si el original no lo ponía en negrita, el bloque entero se quedaba sin reconocer aunque la fecha ya se hubiera identificado con confianza. Añadido CARGOS_CONOCIDOS (alcalde/alcaldesa, batle/batlessa, president/a, secretari/a, interventor/a, regidor/a, conseller/a, gerent/a, director/a, cap de servei, tresorer/a...), consultado como vía ALTERNATIVA a la negrita — no la sustituye, se suma. Solo se consulta después de haber confirmado ya la fecha de firma, así que el riesgo de falso positivo es bajo: un párrafo suelto que empiece por "El president" sin una fecha de firma justo antes no activa nada. Aplicado a los dos mecanismos (general y fecha explícita). Verificado con Playwright: cargo sin negrita en catalán y castellano, con y sin nombre después, control de falso positivo sin fecha delante, y regresión completa sin cambios.
• v5.6c — Tres frases nuevas, nombre incrustado, viñetas dobles y punto final sobrante:
- Añadidas "document signat electrònicament", "documento firmado electrónicamente" y "a la data de signatura electrònica" a FRASES_FIRMA.
- Generalizados el mecanismo general de firma electrónica y el de fecha explícita (v5.3) para reconocer también el caso en que cargo y nombre ya vienen fusionados en el mismo <p> (<strong>Cargo</strong><span>Nombre</span>, sin párrafo aparte), en vez de solo buscar el nombre como párrafo siguiente. Antes esto hacía que el nombre se descartara sin más.
- Añadido el reconocimiento de marcadores de letra doble (aa., aa), bb., bb)...) como estructura válida de lista en el rastreador de cortes de línea, para que no se marquen como posible corte.
- Quitado el punto final sobrante (si lo hay) en la fecha, el cargo y el nombre de los bloques de firma electrónica general y de fecha explícita. Verificado con Playwright los cuatro puntos por separado, con el caso real del usuario en cada uno, y regresión completa de tablas y firmas sin cambios.
• v5.6b — Repaso del Guiaburros:
- Cerrados dos <p> de la introducción que se quedaban sin etiqueta de cierre (el navegador lo disimulaba solo, pero no era HTML válido). Corregidas dos erratas ("posiblidad"→"posibilidad", "mostrara"→"mostrará"). Añadida mención de las funcionalidades que existían pero no aparecían citadas en ningún párrafo: "Alinear las serpientes", "Eliminar filas vacías", "Recuperar borrado" y "🕘 Recientes".
• v5.6a — Dos frases nuevas en FRASES_FIRMA:
- Añadidas "en la data de signatura electrònica" y "en la fecha de firma electrónica" (con "la" antes de data/fecha pero no antes de signatura/firma; no eran duplicado de ninguna entrada existente). Verificado con Playwright que ambas activan el bloque de firma.
• v5.6 — Códigos cortos en mayúsculas votan centrado:
- Una celda con 2 o 3 letras todas mayúsculas, sin dígitos ni minúsculas (p. ej. "PO", "CC", "CCE" en columnas de código de tablas presupuestarias) antes votaba left por tener letras, como si fuera texto normal. Ahora vota center, igual que un número o una fecha — el patrón típico de estas columnas de código. Si alguna vez coincide con una palabra real de 2-3 letras (p. ej. "EVA"), el resto de celdas de esa columna sigue decidiendo la votación como de costumbre. Verificado con la tabla real pasada por el usuario (columna "C. O." con "PO") y con la regresión completa de tablas, sin cambios en el resto.
• v5.5h — Variantes con "amb"/"con" en FRASES_FIRMA:
- Añadidas "amb data de la signatura electrònica" y "con fecha de firma electrónica" (formato muy habitual con "amb"/"con" en vez de "a"/"en", que faltaba en la lista). Verificado con Playwright sobre los dos casos reales pasados por el usuario, catalán y castellano; de paso confirmado que sigue funcionando bien la combinación con el banco de firmas conocidas (expansión del cargo completo).
• v5.5g — Dos frases nuevas en FRASES_FIRMA:
- Añadidas "a data de la signatura electrònica" y "a fecha de la firma electrónica" (con "la" antes de signatura/firma pero no antes de data/fecha; no eran duplicado de ninguna entrada existente). Verificado con Playwright que ambas activan el bloque de firma.
• v5.5f — Párrafo de nombre opcional en los bloques de firma electrónica general y de fecha explícita:
- Ambos mecanismos exigían siempre tres párrafos seguidos (fecha, cargo en negrita, nombre sin negrita); si el original solo traía fecha + cargo, sin una línea de nombre propio detrás (p. ej. cuando el firmante no se identifica por nombre en ese edicto en concreto), el bloque entero se quedaba sin reconocer y sin tocar. Comprobado que NO era un problema de las tildes de v5.5e (la frase del caso real ya existía tal cual, sin cambios).
- Ahora el párrafo de nombre es opcional en los dos mecanismos: si el siguiente párrafo tras el cargo no es un <p> sin negrita válido, se trata como bloque de 2 líneas (fecha + cargo solo, cargo centrado en negrita sin fusionar con nada). El caso de 3 párrafos con nombre sigue funcionando exactamente igual que antes. Verificado con Playwright: caso real de 2 párrafos, caso normal de 3 párrafos (sin regresión) y fecha explícita sin nombre, los tres correctos; regresión completa de tablas sin cambios.
• v5.5e — FRASES_FIRMA insensible a acentos, y dos frases nuevas sin artículo:
- Añadidas "a data de signatura electrònica" y "a fecha de firma electrónica" a FRASES_FIRMA (variantes sin "la").
- Las tildes equivocadas en el original (p. ej. "electrónica" en un texto catalán, donde debería ser "electrònica", o directamente sin tilde) hacían que la frase no se reconociera. En vez de triplicar cada entrada de FRASES_FIRMA por cada combinación de acento, cada vocal se convierte en una clase de caracteres que acepta agudo, grave o ninguno (normalizando primero la propia frase de origen a vocal "pelada", da igual con qué tilde esté escrita en la lista). El texto original se conserva tal cual en la salida — el reconocimiento es más permisivo, no se corrige la ortografía del boletín. Verificado con Playwright: tilde correcta, tilde equivocada y sin tilde ninguna, las tres se reconocen igual.
• v5.5c — "Alinear las serpientes": consenso por detección de atípicos (IQR), en vez de mediana o percentil fijo:
- La mediana (v5.5b) protegía bien los atípicos claros (p. ej. un párrafo largo en una columna vacía en el resto de tablas) pero dispersaba columnas con variación normal y continua entre muchas tablas (p. ej. nombres de entidad de longitud muy distinta pero sin ningún caso realmente atípico): varias tablas superaban el umbral de aprieto y cada una quedaba en un ancho distinto en vez de converger. Un percentil fijo alto (probado como paso intermedio) corregía la convergencia pero, en grupos pequeños, un solo atípico real arrastraba el percentil hacia arriba e inflaba de más a las tablas normales.
- Solución definitiva: valla de Tukey / rango intercuartílico. Se identifican los valores que se disparan claramente por encima del resto (más de 1.5×RIC sobre el tercer cuartil) y se excluyen del cálculo del consenso, que pasa a ser el MÁXIMO de los valores restantes. Sin atípicos reales, el consenso es el máximo de todas las tablas (alineación perfecta). Con un atípico real, éste se excluye del cálculo, el consenso vuelve a ser el máximo de las tablas "normales" (que no se inflan de más), y el atípico se aprieta hacia ese consenso con el límite de FACTOR_MIN_APRIETO de siempre. Verificado con Playwright sobre los dos casos: 8 tablas con nombres de entidad muy dispares (sin atípicos) convergen en un único ancho, y 4 tablas normales + 1 con un párrafo largo mantienen su ancho natural las normales mientras la atípica se aprieta justo al límite.
• v5.5b — "Alinear las serpientes": consenso por mediana con límite de aprieto:
- El algoritmo usaba el ANCHO MÁXIMO necesario entre todas las tablas del grupo para cada columna, lo que hacía que un único caso extremo (p. ej. un párrafo largo en "Observaciones" cuando el resto de tablas la tiene vacía) obligara a todas las demás a heredar ese ancho enorme.
- Ahora el ancho de consenso de cada columna es la MEDIANA de lo que pide cada tabla, no el máximo, así un caso extremo no arrastra al resto. A las tablas que necesitan menos se las ensancha sin problema hasta el consenso. A las que necesitan mucho más se las aprieta hacia el consenso, pero con un límite (FACTOR_MIN_APRIETO = 0.6): nunca se las aprieta por debajo del 60% de lo que esa tabla en concreto necesita. Verificado con Playwright: en un caso de 5 tablas con "Observaciones" vacía en 4 y un párrafo largo en la quinta, las 4 quedan alineadas en su consenso natural y la quinta se aprieta justo hasta el 60% de su ancho sin restricción, en vez de heredar el ancho de las demás o el suyo propio sin límite.
• v5.5a — Bug de votación de columna con cabecera combinada a dos filas:
- esPrimeraFilaCombinada solo excluía la fila 0 de la votación por columna y del forzado a centrado. Con cabeceras de rowspan=2 (p. ej. ENTIDAD/DNI/APOYO con rowspan=2 + TRANSPORTE con colspan=3 en la fila 0, y C.DE DIA/RURAL/ADAPTADO en la fila 1), la fila 1 se colaba como si fuera de datos: su texto (p. ej. "RURAL") votaba left en el sistema de alineación por columna, y en columnas vacías en el resto de la tabla ese único voto de la propia cabecera decidía la alineación de toda la columna — visible en la fila TOTAL, que sí participa en la votación pese al intento de excluirla (esFilaTotalesOVacia falla en cuanto la fila tiene números reales, no solo la palabra "TOTAL"). Sustituido por filasCabeceraCompuesta, un cálculo del bloque real de filas de cabecera por ocupación de rowspan, igual que en el resto del motor. Verificado con Playwright/Chromium sobre un caso real con 8 tablas: cero celdas mal alineadas tras el arreglo, sin afectar al resto del comportamiento (cabeceras, "Alinear las serpientes").
• v5.5 — Nueva funcionalidad: "Alinear las serpientes":
- Nueva casilla en "Propiedades de tablas". Cuando varias tablas pegadas comparten la misma cabecera combinada (mismo nº de columnas y mismo patrón de colspan/rowspan, sin mirar el texto — así "ENTIDAD"/"ENTITAT" cuentan como la misma forma), cada una calculaba su ancho de columna por separado y el conjunto se veía "serpenteante" en vez de alineado en vertical.
- La pasada agrupa las tablas por la forma de su cabecera, mide con el propio motor de layout del navegador el ancho natural que pediría cada columna en cada tabla (sin restricciones, para no partir ninguna palabra) y aplica a todas las tablas del grupo el máximo necesario por columna — el mismo criterio que se seguía a mano. Verificado con Playwright/Chromium: los bordes de columna coinciden en píxeles exactos entre tablas con contenido distinto, y sin marcar la casilla no se aplica ningún ancho (comportamiento normal intacto).
• v5.4 — José Luis Camps Pons, "incrustada" con vacíos intermedios, e interletraje de rótulos presupuestarios:
- Nueva entrada en BANCO_FIRMAS_INCRUSTADAS: José Luis Camps Pons (Es Castell), ca/es. Su original trae uno o más <p> vacíos de espaciado entre el nombre y el párrafo de ciudad+fecha, cosa que el motor "incrustada" no toleraba (exigía adyacencia directa). Generalizado para saltar cualquier número de párrafos vacíos intermedios y borrarlos junto al resto del bloque, sin afectar al caso ya existente (Joan Palliser, sin vacíos de por medio).
- El bloque de normalización de interletraje expandido (antes solo "A C O R D"/"A C U E R D O"/"R E S O L C"/"R E S U E L V O") amplía con rótulos presupuestarios: APLICACIÓ/APLICACIÓN, Crèdit/Crédito, Import/Importe, Previsions/Previsiones. El mapeo por número de letras (PALABRA_POR_LONGITUD) dejó de servir porque varias palabras nuevas comparten longitud con palabras ya existentes (RESOLC/Crèdit/Import, las tres de 6 letras); sustituido por identificación por el texto exacto de la palabra. Límites de palabra manuales en vez de \b, porque \b no reconoce vocales acentuadas como límite (fallaba en el lado derecho de "APLICACIÓ").
- El paréntesis vacío recordatorio de fecha pasa de <i>()</i> a <em>()</em> en los cinco sitios del bloque de firmas donde aparecía, para que coincida con lo que produce el editor real al pegar.
• v5.3 — Bloque de firma con fecha explícita ("Ciudad, día de mes de año"):
- Nuevo mecanismo, independiente del banco de firmas por nombre: detecta "Ciudad, [a] día de mes de año" (p. ej. "Palma, a 1 de julio de 2026", "Maó, a 12 d'octubre de 2027"), quita el "a " inicial si lo traía (conservando tal cual el separador "de"/"d'" original, sin forzarlo), y aplica el mismo mecanismo de fusión cargo+nombre que el resto de bloques de firma. Año acotado a 19xx/20xx para no disparar con cualquier número de cuatro cifras. Tres frases nuevas añadidas a FRASES_FIRMA de paso: "en la data de la signatura electrònica/digital", "en la fecha de la firma digital".
• v5.2 — Tres tipos nuevos de banco de firmas, botón "Vaciar recientes" fijo, y viñetas ll./ñ):
- BANCO_DE_FIRMAS pasa a tener tres formas distintas de detección, además de la "estándar" (Ciudad, en data... + cargo + nombre): "fija" (sin fecha real en el original, solo cargo+nombre, con una línea de firma fija tipo "(Signat electrònicament: )" — Paz Andrade), "incrustada" (la ciudad y la fecha van dentro del último párrafo sin la coma habitual, tipo "Signat as Mercadal en la data..." — Joan Palliser) y "todo en uno" (cargo+nombre+ciudad+fecha mezclados en una sola frase — Antonia Camps). Detalle: los topónimos con artículo salat ("Es Mercadal", "Es Migjorn Gran") a veces aparecen en el texto original sin el "Es" (contraído en la preposición "as"), así que la búsqueda ignora el artículo aunque la salida sí lo mantenga.
- El botón "Vaciar recientes" ahora es fijo (position:sticky) en la parte de abajo del panel, siempre visible aunque haya scroll — antes había que bajar del todo para verlo.
- regexNumComplejo amplía con "ll." y "ñ)" (y sus variantes en mayúsculas) como marcadores de lista válidos.
• v5.1 — Bug de pérdida de texto en <li> con varios <p>, panel Recientes afinado, y umbral de truncamiento a 0.98:
- Bug de pérdida de texto (grave): en CUATRO sitios distintos del motor de listas, el código sacaba el contenido de un <li> con li.querySelector(':scope > p') — que devuelve solo la PRIMERA coincidencia — y lo usaba como todo el contenido del <li>. Cuando un HTML mal exportado mete DOS párrafos directos dentro del mismo <li> (el contenido real de la viñeta + un encabezado "atrapado" que en realidad era el siguiente punto de la lista, p. ej. "d. Pagament..."), el segundo párrafo se descartaba en silencio sin ningún aviso. Solo uno de los cuatro sitios (la rama <ul> con "esLiComplejo") ya tenía el rescate correcto; los otros tres no. Arreglado replicando el mismo patrón de rescate en los cuatro: el primer párrafo se queda en el <li>, el resto se saca como párrafos propios justo después de la lista. Lección para el futuro: querySelector (singular) sobre algo que podría tener más de una coincidencia es un olor a bug — comprobar siempre con querySelectorAll si hay más de uno antes de descartar el resto.
- Panel "Recientes": quitada la "✕" de cada tarjeta (ocupaba casi todo el recuadro y era fácil pulsarla por error); para borrar solo queda "Vaciar recientes". La vista previa pasa de 90 a 320 caracteres, pero usando -webkit-line-clamp para recortar visualmente a 3 líneas — así se ve más texto de referencia sin que las tarjetas crezcan de alto. Se mantienen los 10 edictos.
- Umbral de truncamiento: subido de 0.80 a 0.98. De paso, se descuenta del lado "sucio" el texto de las notas de traducción cuando se activa "Borrar notas de traducción" — antes ese texto deliberadamente eliminado contaba como "pérdida" en la comparación, y con un umbral tan ajustado como 0.98 eso bastaba para disparar el aviso incluso sin ningún error real.
• v5.0 — Botón "Recientes": últimos 10 edictos sucios recuperables:
- Nuevo botón junto a Ayuda/Historial que abre un modal con los últimos 10 códigos sucios procesados con éxito, guardados en localStorage (más reciente primero, con fecha y una vista previa del texto). Se guarda automáticamente tras cada "Procesar edicto" que termine bien, sin duplicar si el texto es idéntico al que ya está guardado en primer lugar (evita ensuciar la lista si se pulsa el botón varias veces seguidas sobre lo mismo). Cada tarjeta permite recuperar ese edicto de un clic (lo vuelca en el campo de código sucio) o quitarlo individualmente de la lista; también hay un botón para vaciarla entera. Reutiliza el mismo mecanismo de ocultar el fondo al abrir el modal (ocultarFondoParaBusqueda()) que ya usan Ayuda e Historial, para que Ctrl+F tampoco busque detrás de este.
• v4.9 — Ordinales catalanes sin sufijo, y comillas de apertura antes del ordinal:
- REGEX_ORDINAL_INICIO exigía un sufijo de género obligatorio para todos los ordinales, pero los masculinos catalanes ("Primer", "Segon", "Tercer", "Quart") no llevan sufijo, a diferencia de los castellanos ("Primero", "Segundo"...) — así que "Segon." o "Tercer." solos nunca coincidían. El sufijo pasa a ser opcional.
- Se admite además una comilla de apertura cualquiera (recta, simple, «», curva doble, curva simple) justo antes del ordinal, típica al citar un apartado textualmente; la comilla se deja fuera de la negrita, solo el ordinal+punto entra en el <strong>.
• v4.8 — Sin colorines en tablas:
- Se elimina background/background-color/background-image (y el atributo bgcolor) de la propia <table> y de cualquier elemento dentro de ella (td, th, span...). La limpieza de color de texto ya existente no lo cubría: aunque el selector [style*="color"] también "pillaba" los elementos con background-color (la cadena "color" está incluida dentro de "background-color"), el.style.removeProperty('color') solo quita la propiedad color, no background-color — son propiedades CSS distintas pese al nombre parecido.
• v4.7 — Desenvolver tablas-envoltorio de una sola celda:
- Cuando una tabla exterior existe solo para envolver OTRA tabla completa dentro de su única celda (típico de pegados de Word/SharePoint), ahora se detecta y se sustituye la exterior por la interior directamente, siempre activo (no depende de la casilla "Extraer tablas 1x1", que hace algo distinto: esa mete el contenido en un <p>, válido solo si la celda tiene texto — con una tabla dentro sería HTML inválido). Contaba mal el número de celdas de la tabla exterior porque querySelectorAll('td, th') también cuenta las de la tabla interior; ahora se usan selectores :scope para contar solo las celdas directas. Efecto colateral bueno: el font-weight:bold del <th> exterior (cabecera normal) se heredaba en CSS a todo el contenido de la tabla interior, haciendo que se viera todo en negrita en el previsualizador — al desenvolver, desaparece solo.
• v4.6a — EDICTE, ANUNCIO y ANUNCI añadidos a la palabra suelta inicial:
- Junto a "EDICTO", ahora también se elimina si el primer párrafo del documento es literalmente "EDICTE", "ANUNCIO" o "ANUNCI" (con o sin punto final).
• v4.6 — Antoni Costa, "EDICTO" suelto, listas guion/punto sin fusionar, tablas anidadas:
- Tabla especial de firma amplía a "Antoni Costa". "EDICTO" solo se elimina si es el primer párrafo con contenido de todo el documento.
- Conversión de guion/punto a lista reescrita: antes se fusionaban en una misma lista párrafos con marcador "-" y "·" simplemente por ir seguidos, y un guion suelto (sin otro guion/punto junto a él) se convertía en una lista de un solo elemento — juntando cosas que eran distintas. Ahora una tanda solo se convierte a lista si tiene ≥2 elementos consecutivos del MISMO marcador; además se conserva el formato interno (negrita/cursiva) en vez de usar solo texto plano.
- "Debe decir", "On hi diu", "Hi ha de dir" añadidos a la lista de rectificación.
- Tablas anidadas (una <table> completa dentro de una celda de otra tabla) se rompían: el motor general envolvía el contenido de la celda en spans de estilo como si fuera texto, metiendo la tabla interior dentro de un <span> (anidación inválida) y dejándola sin procesar. Ahora cualquier celda que contenga una tabla anidada se salta por completo en el motor general (ni volteado ni alineación), dejando que esa tabla interior se procese de forma independiente y limpia en su propia pasada. De paso, encontrado un segundo sitio que ponía las cabeceras en cursiva vía style="font-style:italic" (aparte del <em> ya quitado antes) — eliminado también; y arreglada una concatenación de estilos sin punto y coma que podía invalidar silenciosamente un text-align previo al añadir font-weight justo detrás.
• v4.5 — Banco de firmas conocidas, y cabeceras sin cursiva:
- Cabeceras (normales y las de "cabeceras inteligentes" en tablas combinadas) ya no se ponen en cursiva.
- Nuevo BANCO_DE_FIRMAS: casos manuales de personas concretas cuyo cargo necesita una corrección de redacción (anteponer "La", feminizar "Jefe"→"jefa"...) que no se puede deducir por regla general. Se reconoce por el NOMBRE (sin acentos/mayúsculas/puntuación final), sea cual sea el orden real de fecha/cargo/nombre en el documento (a veces la fecha va al final, no al principio) y aunque haya líneas intermedias (p.d., BOIB...). Detalle técnico: el rango de párrafos a sustituir no se puede acotar solo entre "fecha" y "nombre", porque ninguno de los dos es siempre el extremo real del bloque (el cargo a veces queda fuera); se extiende el rango hasta el tamaño esperado (fecha+cargo+extras+nombre) antes de aceptar la sustitución. FRASES_FIRMA también amplía con 3 variantes de fecha nuevas encontradas en estos casos reales. Añadidos: Óscar Portas Juan, Catalina Rosa Pons Taberner, Francisca M. Matas Escobar (ca/es cada uno). Ampliable con más entradas en el futuro.
• v4.4a — Paréntesis vacíos en cursiva si falta la fecha en el bloque de firma:
- Si el párrafo "Ciudad, en data/fecha de la signatura/firma electrònica/electrónica" no trae ya un paréntesis de apertura (falta la fecha entre paréntesis), se añade <i>()</i> al final como recordatorio visual de que hay que rellenarla.
• v4.4 — Falso positivo del aviso de truncamiento con sin decodificar:
- La comparación de longitud sucio/limpio no decodificaba entidades HTML antes de contar caracteres: cada del texto sucio (todavía sin procesar) contaba como 6 caracteres, mientras que en el texto limpio ya se había convertido en un espacio normal (1 carácter). En edictos que abusan de en vez de espacios normales, esto infla el recuento del sucio y dispara el aviso de truncamiento sin pérdida real de texto. Añadida la decodificación de /&/</>/"/' antes de comparar.
• v4.3 — Los 67 municipios de Balears en CIUDADES_FIRMA, e historial condensado:
- CIUDADES_FIRMA pasa de solo "Palma" a los 67 municipios de las Illes Balears. Probado con casos de solapamiento de nombres ("Sant Joan" vs "Sant Joan de Labritja") sin falsos positivos, gracias a que el patrón exige la coma inmediatamente después del nombre completo.
- Historial de v3.7 a v4.2 condensado a formato compacto (1-3 líneas por versión), manteniendo la causa técnica de cada bug pero recortando ejemplos y prosa repetida.
• v4.2 — Bloque de firma electrónica y limpieza del historial:
- Formateo automático de firma: "Ciudad, en data/fecha de la signatura/firma electrònica/electrónica" + cargo (negrita) + nombre → separador en blanco, fecha y cargo+nombre centrados y fusionados con salto visual entre ambos. Lista CIUDADES_FIRMA ampliable (67 municipios de Balears). El espacio del separador necesitó un marcador temporal para sobrevivir a una limpieza de espacios en blanco posterior en el pipeline.
- Historial reordenado (entrada de v3.9b duplicada eliminada). Corte tras paréntesis/corchete/comilla ahora respeta enlaces y correos justo detrás.
• v4.1 — Rótulos presupuestarios en mayúsculas y negrita:
- Lista propia (PALABRAS_MAYUSCULAS_NEGRITA: DESPESES, INGRESSOS, CRÈDIT EXTRAORDINARI...) que fuerza mayúsculas + negrita cuando aparecen solas en un párrafo, con o sin ":" final.
• v4.0 — Duplicado en listas anidadas, Balears/Baleares como corte, RESOLC/RESUELVO:
- <ol class="circle"> con <li> fantasma duplicaba texto: reasignar innerHTML con la sublista todavía dentro crea un clon. Arreglado extrayendo la sublista antes de tocar el innerHTML. Corte ahora detecta "Balears"/"Baleares" solas al inicio de párrafo. REGEX_ACORD_ACUERDO amplía a RESOLC/RESUELVO.
• v3.9f — Casillas de tabla sin línea de puntos entre ellas.
• v3.9e — Filas vacías, corte tras paréntesis, enlaces exceptuados, subrayado de URL:
- Casilla "Eliminar filas vacías" (no toca tablas con celdas combinadas). Corte tras (/[/« + minúscula. Enlaces/correos exceptuados también en celdas de tabla. "Donde dice"/"Tiene que decir" en la lista de rectificación. <u> con URL pura pierde el subrayado sin pasar a negrita.
• v3.9d — Intensidad del degradado del modal subida (22→45%, 8→20%).
• v3.9c — Backdrop del modal con degradado del tema activo (color-mix()) en vez de negro plano.
• v3.9b — Nota de traducción fantasma, Ctrl+F y align eliminado:
- Nota <A[...]> oculta ahora se resalta visible en el previsualizador. Al abrir cualquier modal se oculta .container con display:none para que Ctrl+F no busque detrás. align="left"/"right" eliminado de las tablas.
• v3.9a — Tabla "Margarita Prohens" reconstruida ANTES del motor general de tablas (parchear el estilo después de que el motor general ya la hubiera convertido en cabecera no bastaba).
• v3.9 — Quitado el salto de página automático de anexos de solicitud (daba problemas, poco ahorro real).
• v3.8f — esFraseExceptuada() añadida al bloque de guiones largos/medios (se saltaba las excepciones configuradas).
• v3.8e — Abreviaturas partidas y voto de columna con rowspan:
- p.d./p.o./etc. se rompían por un regex anterior en el pipeline que las separaba antes de que su propia excepción pudiera reconocerlas. El motor de alineación de tablas usaba la posición del <td> en su fila como columna real; con rowspan de por medio eso desplaza el índice. Arreglado con indicesRealesPorFila, una rejilla de ocupación que calcula la columna real fila a fila.
• v3.8d — Botón "Guardar texto", cabeceras combinadas y tabla "Margarita Prohens" (versión inicial):
- Detección de celdas combinadas mirando las dos primeras filas (no solo la primera). Rótulo Proposta de resolució/Propuesta de resolución añadido.
• v3.8c — Bug crítico: <div> con <p> y <table> mezclados se desenvolvía mal y metía todo el contenido en un único <p>. Arreglado generalizando la condición de desenvuelto a "al menos un hijo de bloque".
• v3.8/v3.8a/v3.8b — ACORD/ACUERDO en negrita (interletraje expandido recompuesto vía TreeWalker); salto de anexos añadido y luego revertido en v3.9.
• v3.7b — autocomplete="off" en Ancho/Relleno/Cabeceras para que no sobrevivan a un F5 (no era cosa de localStorage).
• v3.7a — TOTAL ya no centra a la fuerza; float en <img> neutralizado para que el centrado de imágenes funcione siempre.
• v3.7 — Tablas por mayoría simple, editor reubicado y botones normalizados:
- Motor de alineación reescrito: cada celda emite un voto (formato monetario/fecha/≥30% dígitos/texto) y la opción más votada gana la columna entera. Botón de edición reubicado a la fila principal; flechas fantasma para navegar entre cortes; alturas de botón unificadas.
• v3.6f — Excepciones de guion: bug del tercer bloque oculto:
- Localizado un tercer bloque de conversión a <ul><li> que operaba de forma completamente independiente al sistema de esFraseExceptuada(), ignorando silenciosamente todas las excepciones de guion configuradas.
• v3.6e — Apóstrofes tipográficos y layout de panel:
- Normalización de apóstrofes tipográficos (’) a ASCII (') antes de comparar frases exceptuadas, resolviendo falsos negativos en D'acord amb l'article. Corrección de doble espacio en normalizarGuionInicial().
• v3.6d — Editor WYSIWYG: comportamiento ante otras acciones:
- Limpiar entrada y Procesar edicto cancelan el modo edición sin guardar. Copiar código avisa si hay cambios sin guardar. Sincronización con la previsualización al guardar. Corrección de fusión de cabeceras tipo Article 2 + título con párrafos vacíos intermedios.
• v3.6 — Editor inline del HTML limpio:
- Primera implementación del editor de texto renderizado dentro del panel de salida, con detección de cortes resaltados y posicionamiento automático del cursor.
• v3.5b — Ordinales mallorquines hasta cincuenta:
- Ampliación de ORDINALES_PRINCIPALES_50 con variantes catalanas y mallorquinas desde vintè hasta cinquantè, formas compuestas con guion y formas femeninas.
• v3.5 — Capas div de Word y detección de truncamiento mejorada:
- Eliminación de capas <div style="border:none..."> con márgenes negativos. Limpieza del carácter • redundante en <li>. Detección de truncamiento sobre texto plano, umbral al 80%.
• v3.4 — Truncamiento, listas de viñetas, rótulos y ordinales:
- Detección de texto truncado con aviso. Agrupación de <ul> consecutivas de puntos gruesos. Interposició/Interposición de recursos añadido a rótulos. Ordinales en negrita al inicio de párrafo (Primero., Segona....). Excepciones de guion consolidadas (- Informe d unifica variantes).
• v3.3 — Cabeceras inteligentes, fila TOTAL y rescate de <li> multipárrafo:
- En tablas sin <thead>, filas con todas sus celdas en negrita y sin dígitos se centran y ponen en cursiva. Filas con TOTAL centran todas sus celdas pese al colspan. Rescate de <li> con múltiples párrafos que antes perdían contenido al hacer lista.remove().
• v3.2 — Filas de totales excluidas del voto, div de Office Online:
- Exclusión de filas de totales/vacías del cómputo de votos de columna. Eliminación de capas <div class="BCX..."> de Office Online/SharePoint en pre-parseo.
• v3.1 — Guion corto aislado y <ul> anidadas sin reordenar:
- Un párrafo con guion corto - sin otro guion en el párrafo siguiente ya no se sangra. Las <ul> anidadas dentro de <li> se procesan in-situ en lugar de extraerse al DOM padre (lo que reordenaba el contenido).
• v3.0 — NIE, apertura automática, votación de tablas y error crítico:
- Corrección del error crítico DOMException: Node.insertBefore al insertar separadores con padres distintos. Detección de NIE (X/Y/Z + 7 dígitos + letra). Apertura automática del previsualizador con DNI/NIE detectado.
• v2.9b — Subrayados a negrita, excepción p.d. y márgenes Word:
- <u> convierte a <strong>. Excepción p.d./p.o.. Eliminación de margin-* exportados por Word. Badges del rastreador unificadas.
• v2.8 — Sistema unificado de cabeceras estructurales:
- Fusión en un único sistema REGEX_CABECERA_ESTRUCTURAL que detecta ANEXO/TÍTULO/CAPÍTULO en cualquier grafía, con ordinales romanos, arábigos y textuales en castellano y catalán.
• v2.7 — Seguridad reforzada y previsualizador circular:
- Blindaje XSS ampliado (atributos sin comillas, javascript:). Navegación circular en el carrusel de errores. Corrección del bug «Guadiana» (getAbsoluteOffsetTop() sustituye a getBoundingClientRect()).
• v2.6 — Scrollbars elegantes y corrección de centrado:
- Scrollbars personalizadas de 5px. Corrección del orden de data-forced-center que impedía centrar Resolució y similares. Cierre de modales con Escape.
• v2.5 — Motor de listas ampliado y normalización de rótulos:
- Eliminación de line-height en spans Word. Conversión de <ol class="circle"> a <ul>. Normalización de rótulos de sección (Antecedentes/Fets/Hechos). Sistema FRASES_EXCEPTUADAS.
• v2.0.0 — Blindaje estructural de sublistas en <ol>:
- Rescate de sublistas anidadas con :scope > ol, :scope > ul, eliminando el bug de desaparición de texto en edictos con listas complejas.
• v1.9.9 — Mapeado ergonómico de teclado:
- Escuchador táctico para navegación por flechas y cierre con Escape en el visor modal. Sistema predictivo de alineación de tablas por conteo de caracteres por columna.
• v1.9.0 — Panel de rastreo y alertas:
- Rastreador en tiempo real con contadores de DNI, notas de traducción y casillas rotas. Herramientas de personalización estética con persistencia en localStorage.
Todavía no hay ningún edicto guardado. En cuanto proceses uno, aparecerá aquí.