Hace unos meses escribí sobre el vibe coding y sobre cómo lo que parece magia en la demo se convierte en deuda cuando toca ir a producción. Este artículo es la continuación natural de aquella reflexión, porque desde entonces he cambiado bastante mi forma de trabajar con Claude Code, y el cambio tiene nombre: spec-driven development.
La idea no es nueva. Pensar antes de construir es la disciplina de siempre. Lo que ha cambiado es el contexto. Cuando programar era caro, la propia fricción te obligaba a pensar. Ahora le pides a Claude Code "hazme un servicio de notificaciones" y en veinte minutos tienes dos mil líneas funcionando. Funcionando para el caso feliz que el modelo se imaginó, no para el que tu producto necesita. Lo he vivido varias veces: prototipos preciosos que había que tirar porque la IA tomó veinte decisiones de arquitectura que nadie le pidió tomar.
El punto de inflexión para mí fue dejar de tratar a Claude Code como un autocompletado gigante y empezar a tratarlo como lo que es: un ingeniero muy rápido y muy literal que necesita un briefing decente. Ese briefing es la spec.
Qué es una spec (y qué no es)
Una spec es un documento en markdown que vive en el repositorio y define tres cosas: qué se construye, con qué restricciones y cómo sabremos que está bien. Es el contrato entre tú y el agente. Antes de pedir código, escribes el contrato. Después, el prompt se reduce a algo tan simple como "implementa specs/notification-service.md, empieza por el dispatcher".
Conviene aclarar lo que no es, porque aquí es donde he visto tropezar a más gente. No es documentación escrita a posteriori para justificar lo que ya existe. Tampoco es un PRD de cuarenta páginas: una buena spec para una feature media ocupa entre 50 y 150 líneas. Y no es un prompt largo. El prompt es efímero, se pierde al cerrar la sesión. La spec persiste, se versiona por PR y se refina entre iteraciones, igual que el código.
Mi regla mental: si mañana borro toda la implementación, la spec me tiene que permitir regenerarla sin perder ninguna decisión importante. El código se puede regenerar. El criterio, no.
Configurando el contexto: Rules y Skills
Una buena spec no viaja sola. Para que el agente de IA entienda realmente tu ecosistema de desarrollo, necesitas definir las reglas globales del proyecto (rules) y las habilidades específicas o scripts que puede ejecutar (skills). Esto se configura a nivel de repositorio usando archivos como AGENTS.md o la carpeta de personalizaciones (customizations). De este modo, Claude Code o Antigravity conocen las restricciones de tu proyecto antes de procesar el archivo.
Si quieres profundizar en cómo funciona este sistema de reglas, cómo cargarlas y cómo evitar que el agente actúe por su cuenta, te recomiendo leer mi artículo anterior: Cómo sacarle más a Google Antigravity (sin que el agente trabaje en tu contra), donde detallo el uso de customizaciones, rules y workflows.
Los cinco bloques de una spec que funciona
Después de meses iterando en Venturest, mis specs han convergido en una estructura de cinco bloques.
El primero es el contexto y objetivo: qué problema de negocio resuelve, en dos párrafos como mucho. El modelo necesita el porqué para resolver bien las ambigüedades que inevitablemente van a quedar.
El segundo es el alcance y no-alcance. El no-alcance es el bloque más rentable de todo el documento. Es donde le quitas al agente la libertad de "mejorar" cosas que nadie le pidió. Cada línea de no-alcance son cientos de líneas de código que no tendrás que revisar ni borrar.
El tercero son las decisiones técnicas cerradas: stack, servicios, patrones, convenciones de nombres. Todo lo que ya está decidido va aquí, escrito como hecho consumado. Si lo redactas como sugerencia, el modelo lo tratará como negociable.
El cuarto es el comportamiento esperado: casos de uso, casos límite y errores, en tabla o en formato Given/When/Then. Este bloque es el que Claude Code convierte casi literalmente en tests, así que el esfuerzo se amortiza dos veces.
Y el quinto, los criterios de aceptación: la lista que permite responder sí o no a la pregunta "¿está terminado?". Si un criterio no se puede verificar, no es un criterio, es una intención.
Ejemplo real: el servicio de notificaciones de Venturest
Cuando construimos nuestro servicio de notificaciones multicanal, la primera petición fue vaga y el resultado fue el esperado: un monolito acoplado al backend. La segunda vez escribí esto (versión resumida):
# SPEC: Notification Service
## Contexto
Venturest necesita notificar eventos de plataforma (propuestas, meetings,
clarifications, pagos) por email, push e in-app. Hoy los emails se envían
inline desde el backend, sin reintentos ni trazabilidad.
## Alcance
- Servicio desacoplado del backend, orientado a eventos.
- Canales: email (SES), push (SNS + FCM/APNs), in-app (API GW WebSocket).
## No-alcance
- NO SMS. NO preferencias de usuario en esta fase. NO panel de administración.
## Decisiones técnicas (cerradas, no proponer alternativas)
- Cola: SQS FIFO como bus de entrada. DLQ tras 3 reintentos.
- Procesamiento: Lambda Node.js 20, batch size 10, 256MB, timeout 15s.
- Templates: Handlebars en S3 (`templates/{tipo}/{canal}.html|txt`),
cacheados en memoria en warm starts. Actualización sin redeploy.
- Historial: DynamoDB con TTL de 90 días.
- Orígenes: backend, EventBridge y webhooks externos publican en la
misma cola con un envelope común.
## Comportamiento esperado
| Caso | Resultado |
|---|---|
| Evento válido, canal email | Render template + envío SES + registro en DynamoDB |
| Template inexistente en S3 | Log de error + mensaje a DLQ, sin reintento |
| Fallo transitorio de SES | Reintento vía SQS (máx 3), luego DLQ |
| Evento duplicado (mismo dedup ID) | Descartado por FIFO, sin efecto |
## Criterios de aceptación
- [ ] Un evento publicado en la cola genera notificación en < 30s (p95)
- [ ] Los 3 canales comparten el mismo envelope de entrada
- [ ] Cobertura de tests en el dispatcher > 80%
- [ ] Cambiar un template en S3 no requiere redeployCon esta spec, Claude Code produjo la infraestructura, el dispatcher y los tests en una sesión. Y lo más importante: sin inventarse un canal de SMS ni un CRUD de preferencias que nadie pidió. El bloque de no-alcance hizo su trabajo.
Segundo ejemplo: Meetings, cuando hay un tercero en la ecuación
El segundo caso me parece ilustrativo por otro motivo: la feature depende de un proveedor externo. Construimos Venturest Meetings, las videollamadas integradas dentro del contexto de cada proyecto, sobre daily.co. Cuando integras un tercero, el trabajo más importante de la spec es fijar la frontera entre tu producto y el proveedor, porque esa es justo la zona donde la IA improvisa con más alegría.
# SPEC: Venturest Meetings (daily.co)
## Contexto
Clientes y Growth Partners hacen las reuniones de proyecto en Zoom o Meet,
fuera de la plataforma. Se pierde el contexto: lo hablado no queda vinculado
al proyecto ni alimenta los AI Summaries. Meetings trae la videollamada
dentro del proyecto.
## Alcance
- Crear y unirse a videollamadas desde la vista de proyecto.
- Grabación y transcripción de la sesión.
- Al terminar, pipeline de resumen: summary, next steps y alertas de scope
vinculados al proyecto.
## No-alcance
- NO webinars ni salas de más de 10 participantes. NO SDK móvil nativo
en esta fase. NO chat propio dentro de la llamada (se usa Clarifications).
## Decisiones técnicas (cerradas, no proponer alternativas)
- Proveedor: daily.co con Prebuilt embebido (iframe). No construir UI
de llamada propia.
- Las rooms se crean desde el backend vía REST API de daily.co, siempre
privadas, asociadas a un `project_id`.
- Acceso solo con meeting token firmado, generado por nuestro backend,
con expiración. Nunca URLs públicas de room.
- Webhook `recording.ready` de daily.co dispara el pipeline de
transcripción y resumen. La grabación se copia a nuestro S3;
no somos dependientes de la retención del proveedor.
- Tracking: evento `Meeting Scheduled` con `account_id`, `project_id`,
`duration_min`, `source`.
## Comportamiento esperado
| Caso | Resultado |
|---|---|
| Usuario con acceso al proyecto | Token generado, entra a la room |
| Usuario sin acceso al proyecto | 403, sin token, sin URL de room |
| Token expirado | Acceso denegado, opción de regenerar desde el proyecto |
| Webhook recording.ready | Copia a S3 + transcripción + summary en el proyecto |
| daily.co caído al crear room | Error controlado al usuario, reintento manual |
## Criterios de aceptación
- [ ] Ninguna room es accesible sin token firmado por nuestro backend
- [ ] Room creada y usuario dentro en < 3s (p95)
- [ ] El summary aparece vinculado al proyecto tras la sesión
- [ ] La caída del webhook no pierde grabaciones (reconciliación diaria)Mirad la decisión de acceso. Sin spec, lo más probable es que el modelo genere rooms con URL pública, porque es el camino corto y es lo que muestran los ejemplos de la documentación de cualquier proveedor. Es el tipo de agujero que no se ve en la demo y que aparece meses después, cuando alguien comparte un enlace donde no debía. La spec convierte esa decisión en una regla explícita antes de que exista una sola línea de código.
Errores comunes al escribir specs
El primero es escribir specs demasiado abiertas. "Usa buenas prácticas" no es una instrucción, es una plegaria. Cada decisión que no cierras tú, la cierra el modelo por ti, y no siempre en la dirección que te conviene.
El segundo es no versionar las specs. Cuando la feature evoluciona y la spec se queda atrás, el agente trabaja contra un contrato obsoleto. La spec vive en el repo y cambia por PR, como cualquier otro archivo.
El tercero es dejar la frontera con los terceros sin definir. Cuando la feature integra un proveedor externo, como pasa con daily.co en Meetings, todo lo que no fijes en la spec lo va a resolver el modelo copiando el quickstart de la documentación del proveedor. Y el quickstart está pensado para la demo, no para tu modelo de permisos ni para tu retención de datos.
Y el cuarto, quizá el más sutil, es confundir spec con prompt. El prompt es la conversación de hoy. La spec es el documento que sobrevive a la conversación. El prompt apunta, la spec define.
La reflexión de fondo
Hay algo casi irónico en todo esto. La IA nos ha devuelto una disciplina que la industria llevaba años erosionando. Escribir especificaciones se veía como burocracia de otra época, y ahora resulta que es la palanca que separa a los equipos que generan código de los que entregan producto.
Mi conclusión después de estos meses es que el trabajo del ingeniero sénior no se ha reducido, se ha desplazado. Menos tiempo tecleando implementación y más tiempo haciendo el trabajo que siempre fue el difícil: decidir qué construir, qué no construir y cómo saber si está bien. La IA no te quita ese trabajo. Te lo pone delante, en markdown, antes de la primera línea de código.
¿Y en tu equipo? ¿Escribís la spec antes de abrir Claude Code, o seguís negociando con el modelo a base de prompts? Me interesa saber cómo lo estáis resolviendo.





