Verificar código generado por IA: 112 posts con el schema roto
El 10 de septiembre de 2026 le pedí a un script que auditara la FAQ de todo el blog de Dominicode. No esperaba encontrar gran cosa: llevo meses aprobando cada post yo mismo antes de publicarlo, y la sección de preguntas frecuentes siempre se veía perfecta — pregunta en negrita, respuesta debajo, todo alineado en el editor de WordPress y en el navegador.
El script devolvió 112 posts con el schema FAQPage roto.
Es el mismo problema que tienes al intentar verificar código generado por IA con solo leer el resultado: se ve perfecto y sigue roto.
No roto a medias. En un grupo de esos 112, el JSON-LD que se genera para Google y para cualquier motor que lea structured data tenía las preguntas literalmente llamadas "Respuesta:". Ciento doce posts publicados, revisados por mí uno por uno antes de publicarlos, y ninguno cumplía el contrato real que el frontend del blog necesita para generar ese schema.
Nadie lo había visto leyendo el HTML. Yo tampoco.
En corto: leer el diff o el HTML de código generado por IA no es lo mismo que verificar que cumple el contrato que otro sistema necesita para consumirlo — solo confirma que "se ve bien". Verificar código generado por IA por contrato significa ejecutar el mismo parser o extractor que usará el consumidor final antes de dar el visto bueno. Lo descubrimos auditando nuestro propio blog: 112 posts aprobados a simple vista tenían el schema FAQPage roto, invisible en el navegador, durante meses.
¿Qué es "verificar por contrato" y por qué no es lo mismo que leer el código?
Verificar por contrato es comprobar que un cambio produce exactamente lo que el sistema que lo consume necesita — no que "se vea bien" para un humano que lo lee. Leer un diff o un post publicado confirma que el resultado es legible. No confirma que un parser o un test automatizado pueda procesarlo.
Son dos preguntas distintas. "¿Se ve bien?" la responde cualquiera en cinco segundos. "¿Cumple el contrato de quien lo consume?" solo la responde ejecutar ese sistema —o replicar su lógica exacta— contra el resultado.
Ya escribí el método completo, con el AGENTS.md entero, en Revisión por Contrato: cómo verificar código de agentes de IA. Este post es la prueba de que el método no es teoría: es lo que evitó que 112 posts siguieran rotos indefinidamente.
El blog no usa ningún plugin de WordPress para generar el schema FAQPage. Lo genera el frontend en Next.js parseando el HTML del post con una función propia (extractFaqsFromContent, en app/lib/api.ts).
Su contrato es estricto: la sección FAQ debe abrir con un <h2> que case con "FAQ" o "Preguntas frecuentes", cerrar en el primer <hr> o el siguiente <h2> —lo que llegue antes— y dentro de esa sección solo reconoce <h3> como pregunta y <p> como respuesta. Cualquier otra estructura, por bien que se vea en pantalla, no existe para ese parser.
Seis formas de "verse bien" que rompían el contrato
El catálogo acumuló seis formas distintas de escribir la FAQ, heredadas de plantillas de distintas épocas. Ninguna usaba <h3> + <p> dentro de la sección — todas se veían impecables en WordPress.
| Forma heredada | Dónde vivía el id del ancla |
Qué producía el schema real |
|---|---|---|
A — <li> con enlace + <div id><p><strong>Respuesta:</strong> R</p></div> |
La respuesta | Todas las preguntas literalmente "Respuesta:" |
B — igual que A, pero <strong>P</strong> cuelga del <div>, fuera del <p> |
La respuesta | 0 preguntas — el extractor solo mira dentro de <p> |
C — <div class="faq-question"> con el enlace + <div id> con texto suelto |
La respuesta | 0 preguntas — no hay ni <h3> ni <p> |
D — <p><a href="#id">P</a></p> como pregunta |
La respuesta | 0 preguntas — se lee como párrafo suelto y se descarta |
E — <h3 id="id">P</h3> suelto, sin enlace de índice |
La propia pregunta | La respuesta vive en un <div> sin <p>; la pregunta se pierde sin dejar rastro |
F — <section id="id"> que solo envuelve al <h3> |
La propia pregunta | Mismo problema que E: el <div> de respuesta queda fuera de lo que ve el extractor |
Seis formas, un solo fallo compartido: la pregunta y la respuesta nunca vivían dentro de <h3> + <p> a la vez. El contrato no pedía nada exótico — pedía dos etiquetas concretas, en el lugar concreto.
Por qué nadie lo vio en 112 revisiones
Esto no es negligencia mía en particular. Es lo que le pasa a cualquier revisión manual cuando el criterio de "correcto" no es visual.
En un hilo de Hacker News sobre por qué las code reviews casi nunca encuentran bugs, un comentario cita un dato de Wikipedia: menos del 15% de los comentarios que se dejan en una revisión de código señalan errores reales. El resto es estilo y preferencia —lo que un humano sí evalúa mirando.
Una FAQ con formato bonito no activa ninguna alarma en un revisor humano. Activa un montón en un parser que busca <h3> y no encuentra ninguno.
Hay un detalle que le añade ironía al caso: Google dejó de mostrar el rich snippet de FAQ en el buscador el 7 de mayo de 2026, cuatro meses antes de que reparáramos el nuestro, y sin anunciarlo —solo lo cambió en la documentación.
¿Reparar un schema que ya no produce un desplegable en el SERP es tiempo perdido? No: el FAQPage sigue siendo structured data válida, y sigue siendo el tipo de dato limpio y extraíble que un motor generativo necesita para leer y citar tu contenido sin tener que adivinar dónde empieza cada respuesta.
Que Google apagara el escaparate visual no cambia que el contrato de fondo siga decidiendo si tu contenido es citable.
Cómo se reparó — sin confiar en que "debería funcionar"
El script scripts/fix-faq-schema.mjs corre por post individual o con --all, en modo dry-run por defecto — solo escribe con --apply explícito. Antes de tocar nada, replica el contrato exacto del extractor del frontend para diagnosticar si un post está roto. Después de reparar, vuelve a correr ese mismo contrato contra el HTML reparado, no contra lo que "debería" haber quedado.
Aborta ese post concreto — sin tocar los demás — si detecta cualquiera de estas condiciones:
- Aparece un
<h1>inesperado en el cuerpo. - Hay un
<p>metido dentro de un bloque<pre>. - Cambia el número de
<pre>,<h2>o<img>respecto al original. - Se pierde algún enlace externo que existía antes de reparar.
- El extractor, tras reparar, devuelve menos preguntas que antes de tocar nada.
- El diagnóstico, tras reparar, sigue devolviendo
fatalobaden vez de pasar aok.
node scripts/fix-faq-schema.mjs --all # dry-run: solo diagnostica
node scripts/fix-faq-schema.mjs --all --apply # repara de verdad
El guardarraíl más importante es el último paso: después de escribir en WordPress, el script vuelve a leer el post ya guardado en el servidor y comprueba que el schema sale bien ahí —no en la respuesta que WordPress devolvió al hacer el POST—. No confía en que la escritura funcionó. Verifica que funcionó.
El resultado, verificado — no asumido
| Categoría | Antes de reparar | Después de reparar |
|---|---|---|
| Schema roto | 112 posts | 0 posts |
| Schema válido | 375 posts | 487 posts |
| Sin sección FAQ (no aplica) | 106 posts | 106 posts |
De los 487 posts con schema válido, 7 quedaron con un aviso cosmético menor — alguna pregunta sin signo de interrogación — que no bloquea el schema y no tiene impacto real. El resto: cero posts rotos.
Cuándo esto no es suficiente
Verificar por contrato no es magia, y sería deshonesto venderlo como si lo fuera.
No arregla un contrato mal definido desde el principio. Si el contrato replicado por el script hubiera asumido, por ejemplo, que el extractor acepta <h4> cuando en realidad solo acepta <h3>, el script habría dado el visto bueno a posts que seguían rotos.
Verificar contra un contrato equivocado da la misma falsa confianza que no verificar nada. El contrato hay que sacarlo del código real que consume el resultado, no de la memoria de quien escribió la plantilla hace dos años.
La propia herramienta de verificación puede fallar, y necesita sus propios guardarraíles. Un script que repara HTML a golpe de expresiones regulares puede corromper contenido de formas que no están en su lista de comprobaciones.
Por eso aborta ante solapes de edición, cambios en el número de imágenes o enlaces perdidos, o si el diagnóstico sigue en rojo después de reparar —pero esa lista la escribimos nosotros, pensando en lo que podía salir mal. Un caso que no anticipamos no queda cubierto. Esto no termina en un script que se audita a sí mismo una vez y ya: se sigue vigilando.
Qué puedes hacer hoy
Si mantienes contenido o código que un sistema automatizado consume después de ti —un schema, un feed, la salida de un agente que escribe en tu repo— deja de revisarlo leyendo el resultado final.
Escribe (o pide a un agente que escriba) una función de verificación que replique exactamente lo que ese consumidor necesita, y corre esa función antes de aprobar nada. Es el mismo principio que explico con el AGENTS.md completo —contrato, carril y veredicto— en el ebook gratuito Revisión por Contrato.
Si quieres ver cómo aplicamos esto a proyectos más grandes, con guardarraíles reales y no solo el argumento, en Dominicode Labs seguimos publicando los scripts y los casos según van pasando — este incluido.
Preguntas frecuentes
¿Qué diferencia hay entre revisar código y verificarlo por contrato?
Revisar código es leer el resultado —un diff, un HTML, una pantalla— y juzgar si parece correcto. Verificar código generado por IA por contrato es ejecutar, o replicar, el mismo proceso que usará el sistema que consume ese resultado, y comprobar que produce lo esperado.
La revisión detecta si algo se ve bien; la verificación detecta si funciona para quien lo necesita, que casi nunca es un humano leyendo por encima.
¿Por qué el HTML se veía perfecto si el schema estaba roto?
Porque "verse bien" y "cumplir el contrato" son criterios distintos. El navegador y el editor de WordPress renderizan cualquier combinación de etiquetas de forma legible, aunque esa combinación no sea la que un parser automatizado espera.
El fallo solo existe desde el punto de vista del extractor, no desde el punto de vista de quien lee la página.
¿El schema FAQPage sigue sirviendo de algo si Google ya no muestra el rich snippet?
Sigue siendo structured data válida, y sigue siendo el tipo de dato limpio y extraíble que un sistema automatizado —no un humano— necesita para leer tu contenido sin ambigüedad.
Que Google retirara el desplegable visual del buscador en mayo de 2026 no cambia que ese contrato de fondo siga importando para cualquier motor que consuma tu página en vez de un lector.
¿Cómo verifico código generado por IA cuando lo escribe un agente en mi propio repo?
Igual que aquí: define el contrato exacto que ese código debe cumplir —qué test tiene que pasar, qué estructura tiene que respetar— antes de que el agente escriba una sola línea.
Después no apruebes el resultado leyendo el diff: corre ese contrato contra lo que el agente entregó. Si estás construyendo ese agente desde cero, el mismo principio aplica en cada uno de los 5 pasos. El método completo de verificación, con ejemplos de AGENTS.md, está en el post sobre Revisión por Contrato.
¿Qué pasa si el propio script de verificación tiene un error?
Puede pasar, y por eso no basta con escribirlo una vez y confiar en él para siempre. Este en concreto se protege con guardarraíles explícitos —aborta si cambia el número de imágenes o enlaces, y relee el contenido ya guardado en el servidor en vez de asumir que la escritura funcionó.
Pero esa lista de guardarraíles la definió una persona, y solo cubre lo que esa persona anticipó. Verificar por contrato reduce el margen de error; no lo elimina.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
¿Te resultó útil este artículo?
Compártelo con tu comunidad y ayuda a otros desarrolladores.
