# Desarrollo con IA: contratos para obtener código verificable

## La IA no elimina las decisiones de diseño

Una herramienta de IA puede explorar un repositorio, proponer una API, escribir tests e implementar código con rapidez. Esa velocidad no resuelve por sí sola qué comportamiento necesita el sistema. Si el encargo es ambiguo, el resultado puede ser técnicamente plausible y aun así resolver otro problema.

API-DD es compatible con este modo de trabajo porque ofrece una perspectiva para explicitar el límite de cada módulo: quién lo consume, qué mensajes intercambia, qué garantiza y qué mantiene oculto. No impone una arquitectura, un proceso ni una división entre el trabajo humano y el de la IA. Ayuda a convertir decisiones difusas en un contrato que ambos pueden revisar.

La diferencia práctica está en el criterio de éxito. «Genera el código para esta funcionalidad» invita a completar huecos por probabilidad. «Implementa esta API y demuestra estos casos» reduce la ambigüedad y permite evaluar el resultado por su comportamiento.

## Pensar el sistema como APIs

Antes de generar una implementación conviene describir las conversaciones afectadas. Para cada módulo basta con responder lo necesario:

| Decisión | Qué aclara para la IA |
|---|---|
| Consumidor | Desde qué necesidad debe diseñarse el cambio |
| Mensajes | Qué operaciones, resultados y errores puede usar |
| Garantías | Qué comportamiento debe conservarse |
| Límite | Qué archivos y módulos pertenecen al cambio |
| Detalles internos | Qué decisiones puede tomar libremente la implementación |
| APIs consumidas | Qué colaboraciones existen y cuáles pueden sustituirse en un test |

Este mapa reduce dos fallos frecuentes. El primero es ampliar el cambio con abstracciones que nadie pidió. El segundo es copiar detalles del mecanismo en la API: nombres de una librería, estructuras de persistencia o pasos internos que luego quedan convertidos en contrato.

No todos los huecos deben rellenarse antes de empezar. Algunos se descubren al investigar el código o al escribir el primer test. Lo importante es reconocer qué es una decisión pendiente en vez de dejar que una respuesta generada la tome de forma accidental.

## Los casos de prueba concretan el resultado

Un caso de prueba expresa una diferencia observable: una entrada, una acción y un resultado, estado o mensaje que importa al consumidor. Al ejecutarlo se convierte además en feedback para la persona y para la IA.

Un buen conjunto de casos cubre las diferencias relevantes sin repetir la misma regla con datos decorativos. Por ejemplo:

```text
dado un búfer vacío con capacidad uno
cuando se añade un valor
entonces el valor queda disponible

dado un búfer lleno
cuando se intenta añadir otro valor
entonces informa que está lleno y conserva el primero
```

Estos casos dicen más que una petición genérica de «manejar errores». También dejan libertad para usar una lista, un arreglo circular u otra representación. El test protege el contrato; no dicta el recorrido interno.

Ver el test fallar antes de implementar aporta una evidencia sencilla: la prueba puede detectar la ausencia del comportamiento. Verlo pasar después confirma que esa implementación satisface el ejemplo. Ninguna de las dos señales demuestra por sí sola que el diseño esté completo, pero juntas son más fiables que aceptar código porque parece correcto.

## Spec-Driven Development con la especificación en el código

API-DD es compatible con Spec-Driven Development. En esta forma de aplicarlo, la especificación ejecutable no es un documento externo que la implementación deba interpretar: es el archivo —o el conjunto— de tests versionado junto al código de producción.

La documentación puede seguir explicando contexto, motivaciones y decisiones. Sin embargo, la parte del contrato que debe aceptar la implementación queda expresada en código y puede ejecutarse. Antes de implementar, los tests hacen visibles:

- la API pública mediante la que habla el consumidor;
- los casos que representan diferencias de comportamiento relevantes;
- los resultados, errores, estados y efectos esperados;
- los límites del cambio y las colaboraciones que pueden sustituirse;
- lo que no se exige del recorrido interno.

Esto acerca la especificación al lugar donde puede contradecirse. Si cambia la firma de la API, el test deja de compilar. Si cambia una garantía, el test falla. El repositorio conserva juntos el contrato ejecutable, la implementación y la historia de ambos.

También permite separar el trabajo según la dificultad de cada etapa:

```text
intención + repositorio
        ↓
modelo con mayor capacidad de razonamiento
        ↓
API + casos + tests en rojo + límites explícitos
        ↓
modelo más simple, acotado por esa especificación
        ↓
implementación + tests en verde
```

La fase de especificación concentra la ambigüedad: hay que investigar consumidores, distinguir alternativas plausibles, decidir el vocabulario y seleccionar los casos que protegen riesgos reales. Ahí puede aportar más un modelo con mayor capacidad de razonamiento y contexto. Cuando la API y los tests ya fijan el resultado observable, una implementación rutinaria puede delegarse a un modelo más simple que reciba feedback inmediato al ejecutar la suite.

Esta separación no es una garantía ni una obligación. Un algoritmo difícil o un cambio de alto riesgo puede necesitar el modelo más capaz también durante la implementación. Y el modelo que implementa no debe modificar los tests para conseguir el verde: si descubre que el contrato es imposible, incompleto o contradictorio, debe devolver esa evidencia a la fase de especificación.

La especificación tiene además un límite deliberado: solo exige los casos y garantías que contiene. Una suite verde no demuestra comportamientos que nunca fueron representados. Por eso el trabajo más importante ocurre antes de implementar: elegir la API, las diferencias observables, los límites y las casuísticas que merecen convertirse en evidencia ejecutable.

## Un reparto de trabajo útil

El reparto cambia según el riesgo y el contexto. Como punto de partida:

- las personas aportan intención, prioridades, restricciones y decisiones con consecuencias de producto o arquitectura;
- la IA puede investigar usos existentes, resumir contratos, proponer casos, preparar una primera implementación y ejecutar verificaciones;
- ambos revisan las decisiones ambiguas y el resultado observable.

Delegar una tarea no significa delegar su criterio de aceptación. Cuanto mayor sea el impacto de una decisión, más explícita debe quedar antes de convertirla en código. En cambios rutinarios, los tests y las convenciones del repositorio pueden proporcionar casi todo ese contexto.

## Un flujo posible

Este flujo es una guía adaptable, no una condición de API-DD:

1. Investiga el comportamiento actual, sus consumidores y las convenciones locales.
2. Dibuja los módulos afectados y las APIs que ofrecen o consumen.
3. Separa las decisiones ya confirmadas de las preguntas abiertas.
4. Define la API y escribe en tests un caso por cada diferencia observable importante.
5. Ejecuta la especificación y comprueba que los casos nuevos fallan por la razón esperada.
6. Entrega a la implementación esos tests y los límites ya acordados, sin autorizar que los reescriba para obtener el verde.
7. Implementa el cambio sin ampliar el contrato innecesariamente.
8. Ejecuta las verificaciones del repositorio y revisa el diff como un consumidor de la API.

El ciclo puede volver atrás. Un test difícil de escribir quizá revele una API incómoda; una implementación puede mostrar que faltaba representar un resultado. Corregir el contrato en ese momento es parte del diseño, no un fracaso del proceso.

## Qué suele degradar el resultado

- Pedir una implementación sin indicar el consumidor ni el comportamiento esperado.
- Entregar tanto contexto irrelevante que las restricciones importantes se pierdan.
- Permitir que la IA invente silenciosamente nombres, errores o compatibilidad.
- Probar helpers, llamadas internas o estructuras de datos en lugar de la API.
- Modificar el test hasta que acepte el código generado, sin revisar qué garantía cambió.
- Repetir casos equivalentes y confundir volumen de tests con cobertura de decisiones.
- Dar por terminado el cambio sin ejecutar las comprobaciones reales del proyecto.

La solución no es escribir un prompt enorme. Es entregar contexto seleccionado: contrato, casos, límites, convenciones y comandos de verificación. Los apéndices ofrecen plantillas breves para [investigar y acordar el contrato](prompt-01-contrato-api-dd.md) y para [implementar y verificarlo](prompt-02-implementacion-api-dd.md).

## Ejemplo en Go

El test describe el contrato de un búfer con capacidad uno. La implementación no está incluida a propósito: podría ser escrita por una persona o generada con IA y seguiría siendo evaluada por las mismas observaciones.

```go
import "testing"

func TestBoundedBufferContract(t *testing.T) {
    buffer := NewBoundedBuffer[int](1)

    if got := buffer.Push(7); got != Stored {
        t.Fatalf("expected Stored, got %v", got)
    }
    if got := buffer.Push(8); got != Full {
        t.Fatalf("expected Full, got %v", got)
    }

    value, ok := buffer.Pop()
    if !ok || value != 7 {
        t.Fatalf("expected first value, got %v, %v", value, ok)
    }
}
```

El caso fija capacidad, respuesta y conservación del primer valor. No fija clases auxiliares, número de llamadas ni estructura interna. Esa libertad permite que la IA proponga una implementación y que el equipo la cambie después sin alterar el contrato.

## Qué mejora en el código esperado

Pensar en APIs y casos de prueba estrecha el espacio de soluciones sin elegir de antemano el mecanismo. La IA recibe nombres con significado, límites concretos y ejemplos ejecutables; el equipo recibe una forma objetiva de revisar el resultado.

El beneficio no es que todo código generado sea correcto. Es que deja de evaluarse solo por su apariencia: debe respetar el contrato, superar los casos acordados y conservar la libertad interna del módulo.

> **La IA acelera una propuesta; el contrato y las pruebas permiten decidir si esa propuesta sirve.**
