Por qué tu spec falla con un agente de IA: 7 fallos y su arreglo
La spec tenía 900 palabras, títulos bien puestos, listas numeradas y hasta un diagrama. El agente la leyó entera y construyó otra cosa.
El dev que me la pasó estaba convencido de que el problema era el modelo. Probó con otro. Mismo resultado.
Le hice una sola pregunta: cuando el agente termine, ¿qué comando ejecutas para saber si lo ha hecho bien?
Silencio. No había ninguno.
Ese silencio es el diagnóstico completo. La mayoría de las specs que fallan —también las de quien ya aplica Spec-Driven Development a diario— no fallan por estar mal escritas. Fallan por no ser verificables. Y una spec que no se puede comprobar no es una especificación: es una carta de intenciones. Un agente no ejecuta intenciones.
El SDD no consiste en redactar un documento bonito antes de programar — de eso hablé cuando expliqué por qué el Spec-Driven Development evita el caos. Consiste en escribir un contrato que una máquina pueda dar por cumplido o por incumplido, sin que tú tengas que opinar.
Esto no es otro tutorial de redacción. Para la estructura desde cero ya tienes la anatomía de una spec para Claude Code. Esto es el diagnóstico de la spec que ya escribiste y no funcionó.
Siete fallos, ordenados por lo que más veo. Los cinco primeros los detectas leyendo el documento. Los dos últimos, no.
Busca tu síntoma: por qué tu spec falla con un agente de IA
| Lo que ves cuando el agente termina | El fallo en la spec | El arreglo |
|---|---|---|
| Dice "está hecho" y no sabes si está hecho | Criterios de éxito no comprobables | Un comando debajo de cada criterio |
| Hace las cosas como las hace todo el mundo, no como tu repo | Ambigüedad sin marcar | Señala el fichero que manda |
| Toca ficheros que nadie le pidió | No hay límites | Sección "Fuera de alcance" |
| Ignora la mitad de tus instrucciones técnicas | Mezcla el qué con el cómo | Cierra el qué, deja el cómo abierto |
catch vacíos y fallos que devuelven 200 |
Solo describe el camino feliz | Tabla de estados de error |
| Empieza bien y se desmadra a la mitad | La spec es demasiado grande | Pártela por unidad verificable |
| Cumple la spec, pero la spec ya no es verdad | Spec desactualizada | Vive en el repo o se borra |
Fallo 1: criterios de éxito que nadie puede comprobar
Un criterio de éxito es comprobable cuando existe un comando que lo declara cumplido o incumplido sin que nadie opine. Este es el fallo padre: los otros seis son variaciones suyas.
Mira esta spec. La he leído con distintos nombres decenas de veces:
## Feature: listado de productos
Endpoint para listar el catálogo.
Tiene que ser rápido y soportar filtros.
La respuesta debe ser consistente con el resto de la API.
Gestionar bien los errores.
Cuatro frases. Tres deseos y un título. ¿Rápido comparado con qué? ¿Consistente con cuál de los catorce endpoints que ya tienes? ¿Gestionar bien es devolver un 400 o un 422?
Ninguna de esas preguntas la puede responder el agente ejecutando algo. Así que las responde inventando, y tú te enteras después.
Ahora la misma feature escrita para que se pueda comprobar:
## Feature: GET /products
### Criterios de aceptación
1. `GET /products?limit=20` devuelve 200 con
`{ items: Product[], nextCursor: string | null }`.
2. Paginación por cursor sobre `created_at DESC, id DESC`.
`GET /products?cursor=<nextCursor>` devuelve la página siguiente
sin repetir ni saltarse elementos. Nada de offset/limit.
3. `limit` acepta 1-100, por defecto 20.
Fuera de rango devuelve 400 con `{ code: 'INVALID_LIMIT' }`.
4. p95 por debajo de 200 ms con 10.000 productos en tabla.
### Cómo se verifica
- `bun test test/products.e2e.ts` en verde (cubre los puntos 1 a 3).
- `bun run bench:products` imprime el p95 y sale con código 1
si supera los 200 ms.
La diferencia no es la longitud. Es que la segunda tiene una sección Cómo se verifica.
"Que sea rápido" es un deseo. "Que GET /products responda por debajo de 200 ms de p95 con 10.000 registros, y aquí está el comando que lo mide" es un criterio. El primero obliga a que alguien juzgue. El segundo se cierra solo.
Regla corta: debajo de cada criterio, el comando que lo prueba. Si no puedes escribir el comando, no tienes un criterio, tienes una preferencia.
Y cuando el criterio es ejecutable pasa lo interesante: puedes delegar el ciclo entero —escribe, ejecuta, lee el fallo, corrige— en lugar de revisar cada iteración a mano. Es el flujo diario que describí en cómo usar Claude Code a diario, y depende por completo de que exista ese comando.
Fallo 2: la ambigüedad la rellena el modelo, no tú
Todo hueco de la spec se rellena. Siempre. La pregunta no es si el agente va a improvisar, es con qué.
Y improvisa con lo más común de su entrenamiento: la mediana de internet. Tu repo no es la mediana de internet.
Si tu API pagina por cursor y la spec solo dice "con paginación", el agente escribe offset y limit, porque es el patrón que domina en el código público con el que se entrenó. Si tu proyecto devuelve Result en vez de lanzar, escribirá try/catch. Si tus tests usan Testing Library, te meterá un TestBed clásico.
Ninguno de esos es un error del modelo. Son la respuesta estadísticamente correcta a una pregunta que no hiciste.
Aquí está el matiz que casi nadie aplica: la spec no tiene que decirlo todo. Tiene que decir dónde no se puede improvisar.
Y la forma más barata de decirlo no es describir tu patrón en tres párrafos. Es apuntar al código que ya lo hace:
### Referencias obligatorias
- Paginación: copia el patrón de `src/orders/orders.controller.ts` (solo lectura).
Si hay conflicto entre este documento y ese fichero, manda el fichero.
- Errores: usa los helpers de `src/common/http-errors.ts`.
Prohibido lanzar `Error` pelado.
### Libre elección
- Nombres internos, orden de los métodos, dónde partes los helpers.
No preguntes por esto.
Ese último bloque parece de relleno y no lo es. Marcar lo que sí es libre evita el otro extremo: el agente que se para cada dos minutos a preguntar cómo llamar a una variable.
Tu trabajo no es documentar el proyecto entero dentro de la spec. Es marcar las tres o cuatro fronteras donde una decisión razonable sería, en tu repo, la decisión equivocada.
Fallo 3: no dice qué NO hacer
Los desastres que he visto con agentes casi nunca vienen de lo que el agente no hizo. Vienen de lo que hizo de más.
Le pides un endpoint y te reformatea 40 ficheros porque detectó que el estilo era inconsistente. Le pides un filtro y te instala una librería de query building. Le pides un fix y "de paso" refactoriza el módulo de auth, que estaba feo.
Todo eso es técnicamente razonable. Ninguna spec lo prohibía.
Los límites son parte del contrato, no una nota al margen:
### Fuera de alcance
- No tocar `src/auth/**` ni `src/orders/**`.
- No añadir dependencias. La paginación sale del query builder
que ya está en `src/common/pagination.ts`.
- No crear migraciones. Si hace falta un índice, lo propones
en el PR y paras.
- No cambiar la forma de respuesta de endpoints existentes.
- No reformatear ficheros que no toque la feature.
Cinco líneas. Te ahorran la revisión de un diff de 40 ficheros donde lo que importa está en tres. Es, por cierto, uno de los patrones que aparece una y otra vez en los errores comunes al adoptar Claude Code: falta de guardarraíles, no falta de capacidad.
No es opinión mía: las buenas prácticas oficiales de Claude Code lo dicen con todas las letras — las specs más útiles "nombran los ficheros e interfaces implicados, declaran qué queda fuera de alcance, y terminan con un paso de verificación end-to-end que demuestra que la feature funciona". Los tres primeros fallos de esta lista son exactamente esas tres cosas, en negativo.
Fallo 4: la spec que ya decide la implementación
Este falla al revés que los anteriores. No peca de vaga, peca de mandona.
Crea un `ProductsCacheInterceptor` en `src/products/interceptors/`.
Usa un `Map<string, { data: Product[]; ts: number }>` en memoria.
TTL de 60 s, limpieza con un `setInterval` cada 30 s.
La clave del Map es `JSON.stringify(req.query)`.
Eso no es una spec. Es pseudocódigo con saltos de línea.
Y tiene un agujero que probablemente no has visto: JSON.stringify(req.query) genera claves distintas para ?limit=20&cursor=x y ?cursor=x&limit=20. Son la misma petición. El agente puede implementar ese documento al pie de la letra, perfectamente, y aun así pegarle dos veces a la base de datos.
La versión que sí es un contrato:
### Criterio
Dos peticiones con los mismos parámetros a `GET /products` en menos de 60 s
golpean la base de datos una sola vez, independientemente
del orden de los parámetros en la query string.
### Cómo se verifica
`bun test test/products.cache.e2e.ts` — el test espía el
repositorio y afirma que solo hubo una query.
### Restricciones
Sin dependencias nuevas. Sin Redis: todavía no está en infra.
Fíjate en la paradoja. La versión que no dice cómo implementarlo es más exigente que la que lo dictaba línea a línea. La primera se puede cumplir y estar mal. La segunda no se puede fingir.
Además, cuando cierras el cómo, cierras también las soluciones mejores que la tuya. Y en cachés, colas y consultas, el agente propone alternativas buenas más a menudo de lo que resulta cómodo admitir.
Tú decides el qué observable. Él decide el cómo. El test decide quién tiene razón.
Fallo 5: la spec solo describe el camino feliz
Abre tu última spec y cuenta cuántas líneas hablan de qué pasa cuando algo falla. Lo normal es cero.
Aquí está la trampa: el agente no deja el manejo de errores sin hacer. Lo inventa. Y su versión inventada suele ser un catch que loguea y sigue, o un 200 con array vacío cuando la base de datos no responde. Un endpoint que miente en lugar de fallar.
Cuatro filas arreglan esto:
| Caso | Respuesta | Qué se loguea |
|---|---|---|
cursor malformado |
400 INVALID_CURSOR |
warn, sin volcar el cursor entero |
limit fuera de 1-100 |
400 INVALID_LIMIT |
nada |
| Timeout de la base de datos (>2 s) | 503 DB_TIMEOUT |
error, con query y duración |
| Fila de producto sin precio | se excluye del listado | warn con el id |
La última fila separa una spec escrita por alguien que ha estado de guardia de una escrita de memoria. Los datos sucios existen, y si no decides tú qué hacer con ellos, decide el agente.
Dos minutos de escritura. Es lo que hay entre un endpoint y un endpoint que puedes dejar sin mirar.
Los dos fallos que no ves leyendo la spec
Los cinco anteriores se detectan releyendo el documento. Estos dos solo aparecen cuando comparas la spec con el repo y con el tamaño del trabajo.
Fallo 6: la spec es demasiado grande
La unidad de una spec no es la feature, es el paso verificable. Si la sección "Cómo se verifica" no cabe en cinco líneas, no tienes una spec: tienes tres disfrazadas de una.
El síntoma es inconfundible: el agente empieza bien y se desmadra a la mitad, porque cada decisión que toma amplía la superficie de las siguientes.
Partir el trabajo en unidades que se cierran una a una es, literalmente, la mitad del método que enseño en Construye con IA. La otra mitad es no volver a abrir una unidad ya cerrada.
Fallo 7: la spec está desactualizada
La spec dice que el endpoint devuelve items, el código lleva tres semanas devolviendo data. El agente no tiene forma de saber cuál manda. Unas veces sigue al documento y otras al código, y ninguna de las dos es una elección tuya.
La regla que uso: la spec vive en el repo, entra en el mismo PR que el código, y cuando se contradicen gana el código. Entonces actualizas la spec o la borras. Una spec muerta es peor que no tener spec, porque es contexto con autoridad que resulta ser mentira.
El test de 30 segundos: ¿tu spec es verificable?
Una spec es verificable si responde a tres preguntas por escrito. No hace falta reescribir nada para saberlo. Coge la que ibas a pasarle al agente y hazle estas tres:
- ¿Qué comando prueba que está terminada?
- ¿Qué ficheros no puede tocar?
- ¿Qué pasa exactamente cuando falla?
Si las tres tienen respuesta escrita en el documento, la spec funciona. Si falta una, ya sabes dónde está el bug — y no está en el modelo.
Empieza hoy por la primera. Coge tus criterios de aceptación y escribe debajo de cada uno el comando que lo demuestra. Los que se queden sin comando, o los conviertes en algo medible o los sacas de la spec, porque no van a pasar de deseo.
Todo el sistema —el contrato verificable, los límites, cómo partir el trabajo y cómo mantener la spec viva junto al código— es lo que ordené en SDD: Spec-Driven Development. Si este post te ha señalado tres fallos en tu documento, ahí tienes el método completo para que no vuelvan.
Y si prefieres verlo sobre proyectos reales, con specs de gente que las está usando en producción, es una de las conversaciones habituales en Dominicode Labs.
Preguntas frecuentes
¿Cómo sé si mi spec es verificable?
Una spec es verificable si cada criterio de aceptación tiene debajo un comando que devuelve verde o rojo sin que nadie opine. Un test, un benchmark, un script de validación, un curl con la respuesta esperada.
La prueba rápida: pásale la spec a alguien que no conozca el proyecto y pídele que te diga si está hecha sin abrir el código. Si necesita preguntarte algo, el agente también lo habría necesitado, solo que él no pregunta.
¿Qué hago con los criterios que no se pueden medir con un comando?
Los conviertes en algo observable o los sacas de la spec. "Que la UI sea intuitiva" no es un criterio, pero "que el flujo de compra se complete en tres clics desde la ficha de producto y no haya ningún campo obligatorio sin marcar" sí lo es, y se comprueba con un test end-to-end.
Cuando de verdad no se puede —criterios estéticos, tono de los textos, decisiones de marca— déjalo fuera de la spec y márcalo como revisión humana explícita. Lo que no puede pasar es que quede dentro como si el agente pudiera resolverlo.
¿Cuánto debe ocupar una spec para un agente de IA?
No la mides en palabras, la mides en unidades verificables. Una spec debe cubrir un trozo de trabajo que se cierre con una tanda de comprobaciones y una revisión.
Si la sección "Cómo se verifica" ocupa más de cinco líneas o mezcla áreas del sistema que no comparten test, pártela. Dos specs de 300 palabras funcionan mejor que una de 900.
¿La spec sustituye a los tests?
No, los ordena. La spec dice qué tiene que ser cierto y el test lo comprueba en cada ejecución.
En la práctica la relación es más estrecha de lo que parece: si los criterios de aceptación están bien escritos, los nombres de tus tests salen casi copiados de ellos. Cuando un criterio no se deja convertir en un nombre de test, casi siempre es que el criterio estaba vago.
¿Por qué el agente ignora partes de mi spec?
Rara vez las ignora. Lo habitual es que las haya interpretado de una forma que a ti no se te ocurrió, porque estaban abiertas a más de una lectura.
Revisa esas partes buscando dos cosas: adjetivos sin unidad ("rápido", "robusto", "limpio") y sustantivos que en tu proyecto tienen un significado propio ("paginación", "validación", "caché"). Son los dos sitios donde el modelo rellena con lo más común de su entrenamiento en lugar de con lo que hace tu repo.
¿Qué hago si el agente toca ficheros que nadie le pidió?
Añade una sección "Fuera de alcance" a la spec y trátala como parte del contrato, no como una nota al margen. Cinco líneas bastan: qué directorios no se tocan, que no se añaden dependencias, que no se crean migraciones, que no se cambia la forma de respuesta de endpoints existentes y que no se reformatea nada que la feature no toque. Los desastres con agentes casi nunca vienen de lo que no hicieron, sino de lo que hicieron de más, y ninguna spec se lo prohibía.
¿La spec tiene que decir cómo implementar la feature?
No, y decirlo suele empeorar el resultado. Una spec que dicta la implementación línea a línea se puede cumplir al pie de la letra y aun así estar mal, porque el agente reproduce también tus errores de diseño. Una spec que fija el comportamiento observable y el comando que lo verifica no se puede fingir. Tú decides el qué, el agente decide el cómo, y el test decide quién tenía razón.
¿Dónde guardo la spec y quién manda si contradice al código?
En el repositorio, junto al código, y entra en el mismo pull request que la implementación. Fuera del repo se queda desactualizada en semanas y se convierte en contexto falso.
Cuando spec y código se contradicen, manda el código y la spec se corrige o se borra en ese mismo momento. Déjalo escrito dentro del propio documento: es la línea que evita que el agente tenga que adivinar cuál de las dos fuentes es la buena.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
