Audit HTML Project — Auditoría Sistemática de Proyectos HTML
Procedimiento para auditar proyectos HTML grandes (10+ archivos) de forma sistemática y eficiente.
Cuándo usar
- Proyecto HTML con 10+ archivos y el usuario reporta errores
- Antes de hacer commit/push a un repositorio de contenido educativo
- Cuando se genera contenido HTML masivamente y se necesita QA
- Cuando un sitio estático muestra errores de navegación
- SPA con backend (Express + Chart.js + Three.js) — auditoría de endpoints, sync de datos, responsive de componentes JS, estado de DB
Pasos
1. Inventario del proyecto
import os
base = "/path/to/project"
html_files = [f for f in os.listdir(base) if f.endswith('.html')]
# Clasificar por tipo: páginas de contenido, páginas índice, archivos de navegación
2. Detección de errores sistemáticos
Escanear cada archivo por estos problemas:
Críticos (❌):
- Sin atribución correcta (
David Antizar+❤️) - Navegación rota:
href="#">con texto "Anterior" o "Siguiente" - Enlaces rotos internos: referencias a archivos que no existen
Advertencias (⚠️):
- Sin ejercicios interactivos (en contenido educativo)
- Sin resumen final
- Sin sección de teoría
- Sin ejemplos
- Sin caja de error frecuente o idea clave
- Sin barra de progreso
- Contenido inexistente (páginas de volumen vacías)
3. Clasificar por severidad
- Bloqueantes — navegación rota, enlaces rotos, contenido inexistente
- Importantes — atribuciones incorrectas, sin resumen
- Mejora — sin ejercicios, sin ejemplos
4. Estrategia de escaneo para proyectos grandes (30+ archivos)
Para proyectos con 30+ archivos HTML, NO usar read_file por cada archivo — es lento y consume el límite de tool calls. Usar grep vía terminal para el escaneo inicial:
# Escaneo masivo de atribución
grep -c 'David Antizar' *.html | grep ':0$'
# Escaneo de KaTeX
grep -c 'katex' *.html | grep ':0$'
# Enlaces a archivos que no existen
for f in *.html; do
grep -oP 'href="[^"]*\.html"' "$f" | while read -r href; do
target=$(echo "$href" | sed 's/href="//;s/"//')
[ ! -f "$target" ] && echo "ROTO: $f → $target"
done
done
Luego, para las fases de corrección y verificación profunda, usar execute_code con Python y un solo script que procese todos los archivos.
5. Corrección en lotes
Usar execute_code con Python para batch-fix:
from hermes_tools import read_file, write_file, patch
import os
base = "/path/to/project"
html_files = sorted([f for f in os.listdir(base) if f.endswith('.html')])
all_set = set(html_files)
# Ejemplo: corregir todas las atribuciones
files = ['s09-3-bachiller.html', 's10-1-carrera.html', ...]
for f in files:
content = read_file(path=os.path.join(base, f)).get('content', '')
content = content.replace("corazón", "❤️")
write_file(path=os.path.join(base, f), content=content)
5.1 Añadir CDN faltante (KaTeX, Plotly.js, etc.)
Para proyectos educativos con contenido matemático, verificar si KaTeX está presente y añadirlo si falta:
katex_cdn = '''<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
for fname in archivos_sin_katex:
content = read_file(path=join(base, fname)).get('content', '')
new_content = content.replace('</title>', f'</title>\n{katex_cdn}', 1)
write_file(path=join(base, fname), content=new_content)
Regla: KaTeX SOLO para niveles donde haya fórmulas (ESO, Bachiller, Universidad). Primaria usa emojis y texto plano — no necesita KaTeX.
5.2 Crear índices de nivel faltantes
Cuando un nivel (ej: 1º Primaria) carece de índice, y otros niveles (4º+) sí tienen, crear el índice replicando el diseño visual y estructural de los existentes:
# 1. Identificar un índice de referencia (ej: s04-4primaria.html)
# 2. Extraer su estructura: header, grid de tarjetas, colores, footer
# 3. Mapear las sesiones reales del nivel
# 4. Generar el nuevo índice con ese mismo diseño
El nuevo índice debe incluir:
- Header con título y descripción del nivel
- Grid de tarjetas, UNA por sesión, con: título, descripción corta, tag/tema
- Cada tarjeta enlaza a su sesión correspondiente
- Footer con atribución y enlace "Volver al índice general"
- Misma paleta de colores y glassmorphism que los índices existentes
Regla: NO reutilizar nombres de archivo existentes. Si s01-1primaria.html ya existe y es una sesión, crear s01-1primaria-index.html como índice.
6. Verificación post-corrección
Re-ejecutar el escáner para confirmar que todos los errores se resolvieron. Usar un script de verificación único que cubra 8 checks:
print('1. ENLACES ROTOS:') # 0 = perfecto
print('2. ATRIBUCION:') # 0 = perfecto
print('3. KATEX:') # 0 en niveles que lo necesitan
print('4. NUEVOS INDICES:') # existen los que creamos
print('5. INDEX.HTML ENLACES:') # apuntan a los nuevos índices
print('6. ENLACES CORREGIDOS:') # los targets rotos ya no aparecen
print('7. CONEXION CADENAS:') # cadenas paralelas conectadas
print('8. INDEX.HTML ATRIBUCION:') # tiene footer
Formato de salida (preferido por el usuario)
El usuario pide auditorías "estrictas y críticas" que "expliquen todo bien". Usar este formato:
# 🔍 AUDITORÍA COMPLETA — [Nombre del Proyecto]
**Proyecto:** [Descripción]
**Archivos HTML:** [N]
---
## 📊 RESUMEN EJECUTIVO
| Severidad | Encontrados | Estado |
|-----------|------------|--------|
| ❌ **Críticos** | N | [resumen] |
| ⚠️ **Importantes** | N | [resumen] |
| 💡 **Mejoras** | N | [resumen] |
---
## ❌ ERRORES CRÍTICOS
### 1. [Descripción del error]
[Tabla con archivos afectados y detalle del error]
**Impacto:** [Qué le pasa al usuario/alumno]
---
## ⚠️ PROBLEMAS IMPORTANTES
[Similar formato]
---
## 💡 MEJORAS SUGERIDAS
[Similar formato]
---
## ✅ LO QUE FUNCIONA BIEN
[Bullet points positivos]
---
## 📋 PLAN DE CORRECCIÓN
Si quieres que arregle todo, el orden sería:
1. **Fase 1 (Crítico):** [Acción]
2. **Fase 2 (Crítico):** [Acción]
...
Regla: SIEMPRE terminar con "¿Quieres que empiece a corregir?" — el usuario quiere acción, no solo diagnóstico.
Reglas
- NUNCA modificar la estructura de navegación de sesiones individuales (eso es responsabilidad del generador)
- Las páginas de volumen (nivel educativo completo) deben ser índices funcionales con: título, descripción, lista de sesiones con enlaces, objetivos de aprendizaje y resumen
- El INDEX.html debe reflejar correctamente el contenido real (niveles, etiquetas, descripciones)
- Siempre verificar que los archivos referenciados existen antes de corregir enlaces
- Commit y push tras las correcciones
- RITMO: no parar entre pasos — El usuario frustración = "¿por qué has parado?". Flujo: terminar un fix → siguiente inmediatamente. Mostrar progreso en vivo, no esperar a tener todo perfecto para comunicar. Si hay 3+ fixes, hacerlos secuencialmente sin preguntar entre cada uno.
Flujo post-auditoría: fix → generate → deploy
Cuando el usuario pide "arreglar y completar" tras una auditoría, el flujo natural es:
- Fix crítico → corregir enlaces rotos, atribuciones, navegación (usar
execute_code+patchpara fixes simples,write_filepara HTML completo) - Fix importante → actualizar trackers (progress.json, README, etc.) — ⚠️ NO usar
read_filepara JSON (prependea números de línea), usarterminal("cat ...")+json.loads() - Generar contenido faltante → cargar skill del dominio, generar HTML con template, verificar calidad (>12KB, SVG, ejercicios)
- Actualizar índice → INDEX.html con todos los temas
- Deploy → git push (Pages se activa automático si repo es público)
Cada fase → commit separado
🔧 Fix: <descripción>para correcciones📝 <bloque>: <temas generados>para contenido nuevoINDEX: actualizado con N temaspara actualización del índice
Pitfalls
- 🔴 CSS CON DOBLES LLAVES
{{}}DE TEMPLATE ENGINE — Algunos archivos HTML generados por scripts Python/Jinja pueden contener{{y}}en lugar de{y}en bloques<style>. Esto rompe TODOS los estilos CSS del archivo. Detección:grep -n '{{' *.htmldentro de bloques<style>. Corrección: reemplazar{{→{y}}→}SOLO dentro de<style>tags (nunca en HTML content). Prioridad: ❌ Crítico — el archivo se ve sin estilos. Verreferences/css-double-braces-fix.mdpara patrón de corrección batch. - 🔴 PREFIJOS DE LÍNEA
N|EN HTML — Archivos HTML contienen prefijos tipo1|,2|al inicio de cada línea. Causa CSS destruido + texto basura visible. Origen: output de tools de visualización de código guardado como archivo real. Detección:grep -rlP '^\s*\d+\|' *.html. Corrección:re.sub(r'^\s*\d+\|', '', content, flags=re.MULTILINE). Trampa:read_filede Hermes SIEMPRE prependeaN|— no confundir con corrupción real. Usarterminal("head -5 file")para verificar contenido real. Verreferences/line-number-prefix-corruption.md. - 🔴 NÚMEROS DE LÍNEA INCORPORADOS EN HTML (
N|PREFIX) — Archivos HTML pueden contener prefijos tipo52|o3|al inicio de cada línea. Causa: una herramienta escribe el output deread_file()(que prependea números de línea) de vuelta al archivo. Síntomas: números como texto visible en la página, CSS roto (números dentro de<style>), contenido renderizado con basura. Detección:grep -cP '^\s*\d+\|' *.html | grep -:0$— si algún archivo tiene matches, está corrompido. Corrección:re.sub(r'^\s*\d+\|', '', content, flags=re.MULTILINE)para eliminar todos los prefijos. Prioridad: ❌ Crítico — el archivo se ve completamente roto. Verreferences/line-number-corruption-fix.mdpara patrón de detección y corrección. - 🔴
write_fileDOBLE-ESCAPA BACKSLASHES EN REGEX — Cuandowrite_fileescribe código que contiene patrones regex comonew RegExp('[\\s\\S]*?')o/\\d+/g, el tool puede duplicar los backslashes (escritura:[\\s\\S]→ archivo:[\\\\s\\\\S]). El regex queda roto silenciosamente — el código pasanode --checkpero no funciona en runtime. Detección:grep -n '' file.js— si hay resultados sospechosos, hay doble-escape. Prevención: UsarindexOf/substringen vez de regex para patrones simples (limpiar tags, extraer JSON). Si se necesita regex, verificar el contenido real concat -n file | grep 'pattern'tras escribir. Trampa:read_filede Hermes también puede mostrar doble-escape — usarterminal("cat -n file")para verificar el contenido real del archivo. Verpdf-to-landing/references/server-2-endpoint-pattern.mdpara ejemplo completo. - 🔴
write_fileCORROMPE CONTENIDO COMPLEJO (NO solo prefijos de línea) — Cuando se usaread_filedentro deexecute_codepara leer HTML/JS y luegowrite_filepara escribir de vuelta (incluso después de procesar), el contenido puede corromperse: strings truncados, secuencias de escape rotas, caracteres UTF-8 mangled. Esto ES DIFERENTE al pitfall de prefijosN|— aquí el problema es quewrite_filedentro deexecute_codepuede truncar o corromper datos dentro de strings JavaScript complejos (arrays de objetos, datos con caracteres especiales). Síntomas:SyntaxError: Invalid or unexpected tokenen Node.js, navegador no ejecuta inline scripts, datos de referencia (como_contractTypes) truncados. Detección:node --checkfalla. Corrección: Restaurar desde el último commit funcional (git show COMMIT:index.html). Prevención: NUNCA usarwrite_filepara reescribir archivos completos con contenido JS/CSS complejo extraído deread_filedentro deexecute_code. Usarpatchconold_string/new_stringpara ediciones específicas. Verreferences/write-file-corruption-pattern.mdpara el caso completo. - 🔴 BATCH FIX SOBRESCRIBE CORRECCIONES ANTERIORES — Si haces múltiples fases de corrección (ej: Fase 2 añade transiciones, Fase 5 reconstruye navegación), la Fase 5 puede sobrescribir lo que hiciste en Fase 2. SOLUCIÓN: Al hacer batch-fix de navegación, PRESERVAR los enlaces de transición entre niveles que ya existían. Marcarlos antes del batch y re-insertarlos después. Verificar con test específico post-fix.
- 🔴 FÓRMULAS KATEX COMO TEXTO PLANO — Las fórmulas pueden estar escritas como texto (
R²,{(1,0), (0,1)}) en lugar de KaTeX ($R^2$,$\\{(1,0), (0,1)\\}$). El CDN de KaTeX puede estar cargado pero las fórmulas no se renderizan porque no tienen delimitadores$. SIEMPRE verificar: (1) KaTeX CDN presente, (2) script auto-render con delimiters, (3) fórmulas envueltas en$...$o$$...$$. Detectar con:re.findall(r'(?<!\\$)R[²³](?!\\$)', content). - 🔴 Plotly/KaTeX en contexto de ejecución equivocado — El código JavaScript puede terminar en ubicaciones que impiden su ejecución: (1) dentro de
<script src="...">(el navegador ignora el inline content), (2) flotante sin tag<script>, (3) en un<script>separado que ejecuta ANTES de DOMContentLoaded (el div aún no existe). SIEMPRE verificar: Plotly.newPlot y renderMathInElement deben estar dentro de un handlerDOMContentLoaded. Verreferences/plotly-chart-verification.mdpara patrones de detección y corrección. - 🔴 CONVERSIÓN KATEX: SOLO en HTML, NO en scripts — Al convertir símbolos unicode a KaTeX, los símbolos dentro de
<script>tags son código JavaScript válido (títulos de gráficos, strings). Convertirlos rompe el JS. Patrón seguro: Dividir contenido por tags<script>, procesar solo partes HTML. Verreferences/katex-formula-conversion.mdpara el script de corrección batch. - GitHub Pages case sensitivity —
INDEX.htmlno se sirve como raíz, necesitaindex.html - 🔴 Navegación Siguiente → # — patrón común en contenido generado automáticamente
- 🔴 Atribución con emoji corrupto — "corazón" en texto plano en vez de
❤️ - 🔴 Páginas de volumen como índices — no confundir con sesiones individuales; son páginas de navegación entre sesiones
- 🔴 Archivo nombrado como índice pero que es sesión —
s01-1primaria.htmlpuede sonar a "índice de 1º Primaria" pero ser en realidad una sesión individual. Verificar siempre contando enlaces a otras sesiones (< 3 = es sesión, no índice). - 🔴 Dos cadenas paralelas sin conexión — un nivel puede tener archivos
sXX-YYprimaria.html(sesiones resumen) Y archivossXX-YY-tema.html(sesiones detalladas) sin que ninguna enlace a la otra. Los alumnos que entren por una cadena nunca verán la otra. - 🔴 Transición entre niveles rota por naming inconsistente —
s02-7primaria.html(última de 2º) puede enlazar as03-1primaria.htmlque no existe porque el índice real de 3º se llamas03-3primaria.html. El naming numérico no es fiable entre niveles. - 🔴 KaTeX version pinning — usar siempre una versión concreta (
@0.16.9), nunca@latest, para evitar roturas por cambios en CDN. - 🔴 CDN en índices de nivel — los índices (ej:
s09-bachiller.html) no tienen fórmulas, pero es buena práctica añadir KaTeX para que cualquier preview/snippet de sesión se renderice bien. - 🔴 ORDEN ALFABÉTICO vs NUMÉRICO rompe navegación —
sorted()pones01-10ANTES des01-2(porque "1" < "2"). Si se genera navegación con orden alfabético, TODOS los enlaces Anterior/Siguiente apuntan al archivo incorrecto. SIEMPRE usar sorting numérico explícito:sorted(files, key=lambda x: int(re.match(r's\\d+-(\\d+)', x).group(1))). Verificar con prueba: ¿s01-10viene después des01-9? - 🔴 Navegación rota sistemáticamente → reconstruir, no parchear — Si la mayoría de enlaces de navegación apuntan a archivos incorrectos (ej: 73/73 sesiones con links rotos), NO intentar corregir uno por uno. En su lugar: (1) construir mapa de navegación correcto con orden numérico, (2) usar
execute_codepara batch-reemplazar todas las secciones<div class="nav">de una vez. Más eficiente y menos propenso a errores. - 🔴 REGEX TRAP: extraer navegación de
<div class="nav">completo — No usar regex parciales comor'(?:Anterior|Siguiente).*?href="([^"]+\\.html)"'para identificar qué enlace es Anterior y cuál es Siguiente. El orden de<a>dentro del div puede no coincidir con el orden textual, y el texto "Anterior" puede aparecer DESPUÉS del href en algunos generadores. SOLUCIÓN: Extraer el bloque completo<div class="nav">...</div>y analizarlo: el primer<a>con←es Anterior, el segundo con→es Siguiente. Patrón seguro:r'<div class="nav">\\s*<a href="([^"]+)".*?</a>\\s*<a href="([^"]+)".*?</a>\\s*</div>'y luego verificar con←/→qué es cuál. Verreferences/navegacion-nav-div-extraction.md. - 🔴 NOMBRES DE ARCHIVO INCORRECTOS EN NAVEGACIÓN — Un generador puede crear enlaces con nombres de archivo que NO coinciden con los archivos reales. Ejemplo:
b03-05-piezas-caballera.htmlen vez deb03-05-perspectivas-piezas.html. Esto es más sutil quehref="#"porque el enlace está bien formado pero apunta al archivo equivocado. SOLUCIÓN: Escanear TODOS loshref="*.html"y verificar que cada target existe como archivo físico. No confiar en que "suena bien" — verificar conos.path.exists(). - 🔴 NOMBRES DE ARCHIVO EN progress.json ≠ archivos reales: El tracker puede referenciar
b06-04-reglas-acotacion-iso-129.htmlpero el archivo real se llamab06-04-reglas-acotacion.html. SOLUCIÓN: Antes de cualquier operación, verificaros.path.exists()para cada archivo referenced en progress.json. Si hay discrepancia, corregir inmediatamente. - 🔴 LÍNEAS CORROMPIDAS POR MERGE/PATCH — Cuando se aplica un
patcho se resuelve un merge conflict sobre un archivo HTML grande con inline JS, dos líneas pueden fusionarse en una sola. Patrones reconocibles: (1)const // ===== SECTION =====— unconstsuelto antes de un comentario, (2)});getElementById('xxx');— cierre de callback pegado a nueva declaración, (3)// ===== NAME =====getElementById('xxx');— comentario pegado a código. Detección:grep -n "// ====.*[a-zA-Z]('". index.htmlygrep -n "const.*// ====" index.html. Corrección: Restaurar línea desde commit anterior congit show HEAD~1:index.html. Prevención: Verificar convm.Scriptdespués de cada patch/merge. Decisión revert vs fix: Si el diff de brace balance entre versiones es ≥ 2 y >5 líneas afectadas, revert es más seguro que fix manual. Verreferences/inline-js-syntax-validation.mdpara técnicas completas de diagnóstico (vm.Script, brace balance comparison, binary search). - 🔴 INLINE JS ESCAPING EN ONCLICK HANDLERS — Los
<script>inline en HTML usan comillas simples'para strings JS, pero los atributosonclick="..."necesitan comillas simples en el OUTPUT HTML. Patrón roto:onclick="fn('' + id + '')"— el primer'cierra el string JS, rompiendo todo. Patrón correcto:onclick="fn(\x27" + id + "\x27)"o usar template literals (backticks) para el string externo. Detección:node --checkdel script inline falla conSyntaxError: Unexpected string. ⚠️ Trampa:node --checkNO puede validar scripts inline porque su escaping está diseñado para contexto HTML, no JS standalone — el test puede dar falsos positivos. Corrección: Cambiar delimiters del string externo de'a`(template literal), donde'es solo un carácter regular. Verreferences/inline-js-escaping-patterns.mdpara patrones completos. Verreferences/write-file-corruption-pattern.mdpara la restauración desde git. - 🔴 EXTRAER SCRIPTS INLINE A ARCHIVOS EXTERNOS CAMBIA EL CONTEXTO DE ESCAPING — Cuando mueves contenido de
<script>inline a<script src="...">externo, los requerimientos de escaping CAMBIAN. En HTML inline,'(4 backslashes + quote) se interpreta como: HTML parser →'→ JS parser →'(escaped quote). En archivo JS standalone,'se interpreta como: JS parser →\\(literal backslash) +'(cierra string). Resultado: El script que funcionaba inline ROMPE al extraerlo a archivo externo. SOLUCIÓN: Al extraer scripts inline a archivos externos, reescribir el escaping: (1) usar template literals (backticks) para strings que construyen HTML, (2) o usar\x27para comillas simples en output, (3) o usar concatenación con+ "'" +. NUNCA simplemente copiar el contenido inline a un archivo .js y asumir que funciona. Verreferences/inline-js-escaping-patterns.mdpara la tabla completa de conversión. - 🔴 Duplicados temáticos — Dos archivos pueden tratar el mismo tema con nombres ligeramente distintos (ej:
s04-1-fracciones-equivalentes.htmlys04-4-fracciones-equivalentes.html). No son idénticos pero uno es redundante. SOLUCIÓN: Comparar títulos, contenido y referencias cruzadas. Eliminar el redundante y actualizar todos los índices que lo referencian.
🔴 CORRECCIÓN DE HTML COMPLEJO: ORDEN DE OPERACIONES (v2.1 — NUEVO)
Cuando un archivo HTML tiene múltiples problemas simultáneos (scripts rotos + divs desbalanceados + contenido faltante), el orden de corrección es CRÍTICO. Corregir en el orden incorrecto puede introducir nuevos bugs o hacer que los fixes anteriores se pierdan.
Regla de oro: Scripts → Estructura → Contenido
1. 🔴 SCRIPTS (arreglar execution context)
→ Eliminar <script src="..."> con contenido inline
→ Consolidar scripts rotos en bloques válidos
→ Verificar balance <script> / </script>
2. 🟡 ESTRUCTURA (arreglar HTML balance)
→ Contar <div> y </div> — deben coincidir
→ Eliminar </div> extra o añadir divs faltantes
→ Verificar que no hay tags HTML desbalanceados
3. 🟢 CONTENIDO (añadir mejoras pedagógicas)
→ Añadir ejercicios, SVGs, badges, etc.
→ Cada mejora añade divs → verificar balance de nuevo
¿Por qué este orden?
- Si corriges scripts primero, los divs añadidos por el contenido nuevo no afectan la corrección de scripts.
- Si corriges divs antes de scripts, un script fix puede añadir/eliminar divs y romper el balance que ya arreglaste.
- Si corriges contenido antes de scripts/divs, el contenido nuevo puede añadir divs que desbalancean la estructura que ya arreglaste.
Detección de compound failure
Un archivo tiene compound failure si cumple 2+ de:
- Scripts rotos (
<script src=...>con inline content) - Divs desbalanceados (diff != 0)
- Contenido faltante (ejercicios, SVGs, badges)
- Scripts flotantes sin
<script>tags
Checklist de verificación post-corrección
def verify_html_integrity(content):
checks = {}
# 1. Scripts balance
script_opens = content.count('<script')
script_closes = content.count('</script>')
checks['scripts'] = script_opens == script_closes
# 2. Divs balance
div_opens = content.count('<div')
div_closes = content.count('</div>')
checks['divs'] = div_opens == div_closes
# 3. Structure
checks['doctype'] = '<!DOCTYPE html>' in content
checks['html_close'] = '</html>' in content
# 4. No <script src=...> con inline content
import re
bad_scripts = re.findall(r'<script\\s+src="[^"]*">\\s*[^<]', content)
checks['no_bad_scripts'] = len(bad_scripts) == 0
return checks
Revisión Visual y Modernización CSS (v2.0)
Cuando un proyecto HTML tiene 50+ archivos y se necesita una revisión completa de diseño y calidad visual, seguir este procedimiento de 6 fases:
Fase 1: Inventario y diagnóstico
import os, re
from collections import Counter
project_dir = "/path/to/project"
html_files = [f for f in os.listdir(project_dir) if f.endswith('.html')]
# 1. Glassmorphism
glass_count = sum(1 for f in html_files if 'backdrop-filter' in open(os.path.join(project_dir, f)).read())
print(f"Glassmorphism: {glass_count}/{len(html_files)} archivos")
# 2. Estilos inline
inline_counts = []
for f in html_files:
content = open(os.path.join(project_dir, f)).read()
count = len(re.findall(r'style="[^"]*"', content))
inline_counts.append((f, count))
total_inline = sum(c for _, c in inline_counts)
files_over_15 = [(f, c) for f, c in inline_counts if c > 15]
print(f"Total estilos inline: {total_inline}")
print(f"Archivos con >15 estilos inline: {len(files_over_15)}")
# 3. Plotly/KaTeX por nivel
for f in html_files:
content = open(os.path.join(project_dir, f)).read()
has_plotly = 'plotly' in content.lower()
has_katex = 'katex' in content.lower()
# Clasificar por nivel (eso, bachiller, carrera, primaria)
Fase 2: Glassmorphism batch injection
Añadir glassmorphism a todos los archivos que lo necesitan:
glass_css = """
/* Glassmorphism effect */
.glass{background:rgba(255,255,255,.75);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.3);box-shadow:0 4px 12px rgba(0,0,0,.06)}
.box.glass{background:rgba(255,255,255,.75);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.3);box-shadow:0 4px 12px rgba(0,0,0,.06)}
.interactive.glass{background:rgba(241,245,249,.7);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.3);box-shadow:0 4px 12px rgba(0,0,0,.06)}
.summary.glass{background:rgba(239,246,255,.8);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.3);box-shadow:0 4px 12px rgba(0,0,0,.06)}
.chart-container.glass{background:rgba(248,250,252,.7);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.3);box-shadow:0 4px 12px rgba(0,0,0,.06)}
"""
for fname in html_files:
content = open(os.path.join(project_dir, fname)).read()
if 'backdrop-filter' not in content:
content = content.replace('</style>', glass_css + '\n</style>')
open(os.path.join(project_dir, fname), 'w').write(content)
Pitfall: Algunos archivos pueden tener </style sin el > de cierre (corrupción de template engine). Detectar con grep -c '</style' file vs grep -c '</style>' file. Si hay diferencia, reparar primero el tag roto antes de inyectar CSS.
Fase 3: Reducción de estilos inline
Objetivo: Reducir estilos inline moviendo patrones comunes a clases CSS reutilizables.
# 1. Extraer todos los estilos inline y contar frecuencias
all_styles = []
for f in html_files:
content = open(os.path.join(project_dir, f)).read()
inline_styles = re.findall(r'style="([^"]*)"', content)
all_styles.extend(inline_styles)
style_counts = Counter(all_styles)
top_styles = style_counts.most_common(50)
# 2. Crear clases CSS para los patrones más frecuentes
style_to_class = {
'margin-top:.5rem': 'mt-1',
'padding-left:1.2rem; margin-top:.5rem': 'pl-1-mt-1',
'display: inline-flex; align-items: center; gap: 0.5rem; color: #2563eb; text-decoration: none; font-weight: 600; padding: 1rem 1rem; border-radius: 10px; background: rgba(255,255,255,0.6); border: 1px solid rgba(255,255,255,0.3); font-size: 1rem': 'nav-link',
'color:#94a3b8; font-size: 1rem': 'text-muted',
'text-align:center; margin: 1rem 0': 'text-center',
# ... más patrones
}
# 3. Reemplazar inline styles con clases
for style, cls in style_to_class.items():
escaped = re.escape(style)
content = re.sub(f'style="{escaped}"', f'class="{cls}"', content)
Regla: No intentar eliminar TODOS los estilos inline. El objetivo es reducir los patrones más comunes (los que aparecen 10+ veces). Los estilos únicos de cada archivo se dejan como están — no merece la pena crear una clase CSS para un solo uso.
Objetivo real: Reducir de ~2000 a ~900 estilos inline (60% de reducción). Los que quedan son combinaciones muy específicas de cada archivo.
Fase 4: Inyección de Plotly/KaTeX por nivel
Para proyectos educativos, añadir gráficos interactivos y fórmulas según el nivel:
# Plotly para ESO (no para Primaria)
plotly_cdn = '''<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>'''
# Para cada archivo de ESO que no tenga Plotly:
# 1. Añadir CDN antes de </head>
# 2. Añadir div de gráfico con id="plot-tema"
# 3. Añadir script de renderizado antes de </body>
plot_div = f'''
<div class="chart-container glass">
<h3>{titulo_grafico}</h3>
<div id="{plot_id}" style="width:100%;max-width:600px;height:350px;margin:0 auto;"></div>
</div>'''
plot_script = f'''
<script>
const plotData = {data_json};
const layout = {layout_json};
const config = {{responsive: true, displayModeBar: false}};
if (typeof Plotly !== 'undefined') {{
Plotly.newPlot('{plot_id}', plotData, layout, config);
}}
</script>'''
Regla: Plotly SOLO para ESO, Bachiller y Universidad. Primaria usa emojis y texto plano — no necesita gráficos interactivos.
Regla: Cada gráfico debe ser relevante al tema del archivo. No añadir un gráfico genérico — debe ilustrar el concepto que se está enseñando.
Fase 5: Detección y eliminación de duplicados
# Comparar archivos por título y contenido
for f1, f2 in combinations(html_files, 2):
c1 = open(os.path.join(project_dir, f1)).read()
c2 = open(os.path.join(project_dir, f2)).read()
# Extraer títulos
t1 = re.search(r'<title>(.*?)</title>', c1)
t2 = re.search(r'<title>(.*?)</title>', c2)
if t1 and t2 and t1.group(1) == t2.group(1):
print(f"DUPLICADO EXACTO: {f1} == {f2}")
# Comparar títulos similares (mismo tema)
if t1 and t2 and t1.group(1).lower() == t2.group(1).lower():
print(f"DUPLICADO TEMÁTICO: {f1} vs {f2}")
Procedimiento de eliminación:
- Eliminar el archivo duplicado
- Actualizar TODOS los índices que referencian el duplicado para que apunten al archivo principal
- Verificar que no hay enlaces rotos post-eliminación
Fase 6: Verificación visual por nivel
Navegar a un archivo representativo de cada nivel educativo y verificar visualmente con browser_vision:
- Primaria → verificar: diseño child-friendly, emojis, colores vivos, sin KaTeX
- ESO → verificar: glassmorphism, Plotly renderizado, colores Aurora (#2563eb + #f97316)
- Bachiller → verificar: KaTeX fórmulas renderizadas, Plotly graphs, diseño profesional
- Universidad → verificar: LaTeX complejo, gráficos avanzados, coherencia visual
Regla: NO verificar todos los archivos visualmente. Uno representativo por nivel es suficiente. El escaneo automático (Fases 1-5) cubre la consistencia técnica.
Flujo de trabajo recomendado
1. Inventario → 2. Glassmorphism → 3. Inline styles → 4. Plotly/KaTeX → 5. Duplicados → 6. Verificación visual → 7. Commit/Push
Ritmo: No parar entre fases. El usuario quiere acción, no diagnósticos intermedios. Cada fase → commit separado.
Objetivos de calidad por proyecto:
- Glassmorphism: 100% de archivos
- Estilos inline: reducir >60%
- Plotly: 100% de ESO/Bachiller/Universidad
- KaTeX: 100% de Bachiller/Universidad
- Duplicados: 0
- Navegación: 0 enlaces rotos
Verificación de fórmulas KaTeX (nuevo en v1.6)
Cuando el proyecto tiene contenido matemático (ESO, Bachiller, Universidad), verificar que las fórmulas están correctamente formateadas para KaTeX:
12. Verificar delimitadores KaTeX
Las fórmulas DEBEN estar envueltas en $...$ (inline) o $$...$$ (display). KaTeX CDN puede estar cargado pero si las fórmulas son texto plano, no se renderizan.
# Detectar fórmulas como texto plano (fuera de $ delimiters)
def find_plain_math(content):
"""Find math symbols not wrapped in $ delimiters"""
issues = []
lines = content.split('\n')
in_script = False
for i, line in enumerate(lines):
if '<script' in line: in_script = True
if '</script' in line: in_script = False
if in_script or '<style' in line: continue
# Check for R², R³ outside of $
if re.search(r'(?<!\$)R[²³](?!\$)', line):
issues.append((i+1, 'R²/R³ sin delimitador $'))
# Check for {sets} outside of $
if re.search(r'\{[^}]{5,}\}', line) and '$' not in line:
if not any(x in line for x in ['class=', 'id=', 'style=']):
issues.append((i+1, 'Conjunto { } sin delimitador $'))
return issues
Criterio: Si hay >0 fórmulas sin delimitador, es ⚠️ Importante.
13. Convertir fórmulas de texto plano a KaTeX
Cuando se detectan fórmulas sin delimitores, convertirlas:
def convert_math_to_latex(content):
"""Convert plain text math to proper LaTeX with $ delimiters"""
# R² → $R^2$
content = re.sub(r'(?<!\$)(?<!\\)R²(?!\$)', r'$R^2$', content)
content = re.sub(r'(?<!\$)(?<!\\)R³(?!\$)', r'$R^3$', content)
# {(a,b), (c,d)} → $\{(a,b), (c,d)\}$ (sets need escaping)
content = re.sub(r'\{(\([^)]+\)(?:\s*,\s*\([^)]+\))*)\}', r'$\\{\1\\}$', content)
# Standalone sets {1, 2, 3}
content = re.sub(r'\{(\d+(?:\s*,\s*\d+)*)\}', r'$\\{\1\\}$', content)
# Math symbols
content = re.sub(r'(?<!\$)∈(?!\$)', r'$\\in$', content)
content = re.sub(r'(?<!\$)≤(?!\$)', r'$\\leq$', content)
content = re.sub(r'(?<!\$)≥(?!\$)', r'$\\geq$', content)
content = re.sub(r'(?<!\$)≠(?!\$)', r'$\\neq$', content)
content = re.sub(r'(?<!\$)±(?!\$)', r'$\\pm$', content)
return content
Pitfall: Los { y } en LaTeX necesitan ser escapados como \{ y \} para mostrarse. Si no se escapan, KaTeX los interpreta como grupos de agrupación.
14. Verificar script auto-render
El script de KaTeX debe tener los delimiters correctos:
# Verificar que el script de renderizado existe y tiene delimiters
has_render_script = 'renderMathInElement' in content
has_delimiters = 'delimiters' in content
if has_render_script and not has_delimiters:
print(f'⚠️ {fname}: renderMathInElement sin delimiters configurados')
Formato esperado:
renderMathInElement(document.body, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false}
],
throwOnError: false
});
Verificación de ejercicios interactivos (nuevo en v1.8)
15. Ejercicios con onclick que llaman funciones inexistentes
Problema: Los scripts generadores crean HTML con onclick="checkExercise(1, 3)" pero NO definen la función JavaScript. El ejercicio se ve pero al pulsar "Comprobar" sale error en consola.
Detección:
def check_exercise_functions(content):
"""Find onclick handlers calling functions that aren't defined"""
# Find all function calls in onclick
content))
# Find all function definitions
defined_fns = set(re.findall(r'function\s+([a-zA-Z_$]+)\s*\(', content))
missing = onclick_fns - defined_fns
if missing:
return f'❌ Funciones onclick no definidas: {missing}'
return None
Patrón mínimo de función checkExercise:
function checkExercise(num, correct) {
const input = document.getElementById('e' + num);
const feedback = document.getElementById('e' + num + '-fb');
if (!input || !feedback) return;
const val = input.value.trim();
const isCorrect = parseFloat(val) === correct;
feedback.textContent = isCorrect ? '¡Correcto! ✓' : 'Incorrecto. La respuesta es ' + correct;
feedback.className = 'feedback ' + (isCorrect ? 'correct' : 'incorrect');
}
Patrón para select (multiple choice):
function checkE1() {
const sel = document.getElementById('e1');
const fb = document.getElementById('e1-fb');
if (!sel || !fb) return;
const isCorrect = sel.value === 'correcta';
fb.textContent = isCorrect ? '¡Correcto! ✓' : 'Incorrecto.';
fb.className = 'feedback ' + (isCorrect ? 'correct' : 'incorrect');
}
Criterio: Si hay >0 funciones onclick sin definición, es ❌ Crítico — el ejercicio no funciona.
Pitfall: Las funciones setDerivPoint(x), setPotencia(n) para gráficos interactivos necesitan que Plotly esté cargado. Añadir check de que Plotly está disponible antes de llamar a funciones que usan Plotly.newPlot.
Verificación post-escritura obligatoria
Después de CUALQUIER operación write_file sobre archivos de código (HTML con inline JS, módulos JS, archivos CSS), ejecutar verificación antes de continuar:
# 1. JS syntax check (si hay archivos .js o inline scripts extraídos)
node --check archivo.js 2>&1 | head -5
# 2. HTML structure check (balance de tags)
python3 -c "
content = open('index.html').read()
assert content.count('<script') == content.count('</script>'), 'Scripts desbalanceados'
assert content.count('<div') == content.count('</div>'), 'Divs desbalanceados'
assert '<!DOCTYPE html>' in content, 'Sin DOCTYPE'
assert '</html>' in content, 'Sin cierre HTML'
print('✅ HTML structure OK')
"
# 3. Verificar elementos críticos no eliminados
python3 -c "
content = open('index.html').read()
for term in ['switchTab', 'function render', 'DOMContentLoaded']:
assert term in content, f'CRÍTICO: {term} eliminado por write_file'
print('✅ Critical elements OK')
"
Regla de oro: Si write_file corrompió algo, el node --check lo detecta ANTES de que el usuario lo vea. Nunca asumir que write_file preservó el contenido correctamente — siempre verificar.
Linked Files
references/line-number-prefix-corruption.md— Patrón de corrupción: prefijosN|incrustados en HTML por tools de visualización. Rompe CSS y muestra texto basura. Incluye diferenciación conread_filede Hermes.scripts/audit-quick.py— Script de auditoría rápida: ejecutar desde la raíz del proyecto. Detecta: corrupción de números de línea, enlaces rotos, CSS desbalanceado, navegación rota, divs desbalanceados.references/css-double-braces-fix.md— Patrón de detección y corrección batch de{{}}en CSS (template engine artifacts)references/escaneo-html-error-pattern.md— Scripts reutilizables para escaneo y corrección automáticareferences/escaneo-navegacion-curso.md— Escaneo específico de navegación de cursos educativos: cadenas paralelas, transiciones entre niveles, consistencia README, clasificación de páginasreferences/verificacion-ruta-completa.md— Verificación de ruta completa de navegación: INDEX → índice → sesión → vuelta al índice → INDEX, detección de enlaces malformados, navegación entre nivelesreferences/rebuild-navigation-batch.md— Técnica batch para reconstruir navegación rota sistemáticamente (sorting numérico + execute_code)references/plotly-chart-verification.md— Verificación de gráficos Plotly: contenedores vacíos, código de inicialización faltante, patrones de chart containersreferences/navegacion-nav-div-extraction.md— Patrón seguro para extraer Anterior/Siguiente de<div class="nav">con regex completo, casos especiales (3 enlaces, disabled, INDEX) y casos reales- `references/visual-audit-css-mod
…(truncated)