API-DDDiseñar módulos como APIs
Manifiesto
Índice del manifiesto

Manifiesto API-Driven Development

Software modular desde sus APIs.

Un manifiesto breve sobre cómo diseñar módulos desde el punto de vista del código que los usa. Habla de contratos, nombres, acoplamiento y tests.

API-Driven Development, o API-DD, es sencillamente el nombre de esta forma de mirar el software.

5 fundamentos65 min de lecturaEjemplos en GoUn capítulo sobre IA

Introducción · Introducción

API-Driven Development: diseñar cada módulo como una API

Del origen del término API a una perspectiva centrada en módulos y conversaciones.

6 min de lectura Abrir Markdown fuente
En este capítulo
  1. De dónde viene API
  2. Cómo lo reinterpreta API-DD
  3. Manifiesto
  4. Definición
  5. Diseñar la API
  6. Un ejemplo pequeño
  7. Ejemplo en Go
  8. Una forma de aplicarlo

De dónde viene API#

API abrevia Application Programming Interface. El término es anterior a la Web y su idea central es aún más antigua: permitir que un programa use una capacidad sin depender de cómo está construida.

AñoHitoIdea que permanece
1951Wilkes, Wheeler y Gill describen una biblioteca de subrutinas para EDSACEl consumidor necesita una forma conocida de invocar comportamiento reutilizable
1968Cotton y Greatorex emplean application program interface en un sistema de gráficos remotosUna interfaz estable puede separar al programa de terminales y mecanismos diferentes
1974Date y Codd comparan interfaces de programación para bases de datosEl diseño de la interfaz repercute en el diseño del sistema completo

La API, por tanto, no nació como sinónimo de endpoint HTTP ni de servicio público. Esos son mecanismos posteriores para expresar una interfaz. También son APIs las funciones de una biblioteca, las llamadas de un sistema operativo o cualquier protocolo que permita colaborar con una capacidad encapsulada.

Cómo lo reinterpreta API-DD#

API-DD conserva esa función histórica de la interfaz: separar al consumidor del mecanismo. La amplía con una idea de Alan Kay: en un sistema capaz de crecer importa más diseñar cómo se comunican sus módulos que fijar sus propiedades internas. Kay situó el intercambio de mensajes en el núcleo de Smalltalk, pero su observación sobre módulos y comunicación puede aplicarse fuera de la orientación a objetos (mensaje original de 1998).

API-DD lleva la API desde el límite exterior de una aplicación hasta cada relación modular que merezca un contrato. La conversación puede ser local o remota y expresarse con funciones, métodos, eventos o HTTP. El mecanismo cambia; la separación entre consumidor e implementación permanece.

Esta es una reinterpretación deliberada, no la afirmación de que API haya significado siempre exactamente lo mismo. De su historia tomamos la separación entre uso e implementación; de Kay, el foco en los mensajes entre módulos. De ahí surge API-Driven Development.

Manifiesto#

El software cambia. Sus contratos permiten que cambie sin obligar a cada consumidor a conocer de nuevo su interior.

  1. Las interacciones importan más que la forma interna. Un módulo se entiende por las conversaciones que ofrece y consume.
  2. El vocabulario forma parte del diseño. Los nombres expresan intenciones, resultados y hechos que otros módulos pueden comprender.
  3. Lo visible crea acoplamiento. La API muestra lo necesario y mantiene reemplazables algoritmos, coordinación y representación.
  4. La autonomía llega hasta cada resultado. Un módulo completo y válido cumple su contrato sin pedir al consumidor que repare su estado, y entrega resultados que no comparten referencias mutables.
  5. El contrato puede verificarse desde fuera. Los tests actúan como consumidores y observan resultados, estado o mensajes públicos.

Estos fundamentos pueden aplicarse de forma recursiva cuando un módulo se descompone en otros módulos con contratos propios. API-DD no prescribe una arquitectura, un paradigma ni un orden de trabajo. Ofrece una perspectiva para diseñar las conversaciones que mantienen unido el sistema.

Definición#

API-Driven Development (API-DD) propone diseñar cada módulo como una API: hacer visibles los mensajes que acepta, las garantías que conserva, los detalles que oculta y las APIs que necesita.

El punto de partida no es la clase, la carpeta ni el patrón que vamos a utilizar. Es el contrato mediante el que un módulo colabora con los demás. Antes de resolver su interior, aclaramos qué API ofrece y qué compromisos deben sobrevivir a cualquier implementación correcta. Es una perspectiva de diseño, no una arquitectura ni un conjunto de reglas obligatorias.

En este libro, módulo no significa necesariamente un módulo del lenguaje, un paquete, un servicio desplegable ni un archivo. Es una parte encapsulada del software que otros utilizan mediante un contrato. Según la escala, puede materializarse como un conjunto de funciones, un tipo, un paquete, un proceso o una combinación de ellos.

TérminoUso en API-DD
MóduloParte encapsulada del software que ofrece comportamiento a otros
ConsumidorActor, sistema o módulo que depende de ese comportamiento
APIProtocolo mediante el que un módulo se relaciona con sus consumidores
MensajePetición, respuesta o hecho que cruza esa API
ProveedorImplementación que satisface el contrato

Una API puede expresarse con funciones, métodos, eventos, endpoints o cualquier otro mecanismo. Lo importante es la conversación, no su sintaxis.

Diseñar la API#

Mirar un módulo como una API vuelve visibles pocas preguntas, pero concretas:

  1. ¿Quién lo consume y para qué?
  2. ¿Qué mensajes puede enviarle?
  3. ¿Qué resultados, errores, estado o efectos puede observar?
  4. ¿Qué garantías se mantienen entre llamadas?
  5. ¿Qué detalles deben permanecer ocultos?
  6. ¿Qué otras APIs consume el módulo?

Estas respuestas forman el contrato. La firma es solo su representación más visible.

Un ejemplo pequeño#

Supongamos un módulo que aplica un estilo a un texto:

text
Formatter.Format(Text, Style) → FormattedText | UnsupportedStyle

Formatter es el módulo y Format es un mensaje de su API. Text, Style, FormattedText y UnsupportedStyle forman el vocabulario del contrato. Conviene precisar qué significan y qué puede hacer el consumidor con ellos, pero no son APIs independientes por el mero hecho de aparecer en la firma.

El contrato puede prometer que:

  • un estilo admitido produce un texto formateado;
  • un estilo desconocido devuelve un error distinguible;
  • el texto original no cambia;
  • el resultado no revela la librería utilizada internamente.

El consumidor no necesita conocer si el proveedor usa una plantilla, un árbol intermedio o una librería externa. Esas decisiones pueden cambiar mientras se conserven las garantías.

Ejemplo en Go#

Go puede expresar la conversación mediante una interfaz y un error distinguible.

go
import "errors"

type Text string
type Style string
type FormattedText string

var ErrUnsupportedStyle = errors.New("unsupported style")

type Formatter interface {
    Format(Text, Style) (FormattedText, error)
}

La representación podría cambiar mientras Formatter, Format y el significado de ErrUnsupportedStyle conserven el contrato.

Una forma de aplicarlo#

Una secuencia posible para diseñar un cambio con API-DD:

  1. Identifica los módulos afectados y sus consumidores.
  2. Describe la conversación que cada consumidor necesita.
  3. Define mensajes, vocabulario y garantías observables.
  4. Separa el contrato de las decisiones internas.
  5. Repite el análisis solo para los módulos internos con relaciones propias.
  6. Implementa y prueba el contrato sin fijar un recorrido interno innecesario.

API-DD no sustituye el modelado del dominio, la arquitectura ni TDD. Ayuda a concretar cómo colaboran los módulos que esas disciplinas descubren.

API-DD permite mirar cada módulo como una API y conservar libre su implementación.

I · Fundamentos · Fundamento 1 de 5

Fundamento 1. Recursión

Aplicar la misma perspectiva a cada módulo y concentrarse en sus interacciones.

3 min de lectura Abrir Markdown fuente
En este capítulo
  1. Una API puede conceptualizarse como un módulo
  2. El interés está en las interacciones
  3. Todos los fundamentos se repiten
  4. Dónde continuar y dónde detenerse
  5. Ejemplo en Go
  6. Revisión práctica

Una API puede conceptualizarse como un módulo#

Para diseñar una API resulta útil conceptualizarla como un módulo visto desde fuera. El módulo reúne una capacidad y conserva su implementación; la API es el límite por el que otros colaboran con él.

No son exactamente lo mismo. Un módulo puede ofrecer más de una conversación a consumidores diferentes y también consumir otras APIs. La equivalencia sirve como herramienta de diseño: cuando aparece una API relevante, buscamos el módulo responsable de sostener su contrato.

text
módulo
├── API ofrecida → consumidores
├── implementación oculta
└── APIs consumidas → otros módulos

El módulo puede materializarse como una función, un tipo, un paquete, un proceso o varios elementos coordinados. Su forma técnica no determina su escala conceptual.

El interés está en las interacciones#

API-DD pone el interés en lo que ocurre entre módulos: mensajes, respuestas, errores, efectos y garantías. Esta mirada sigue la idea de Alan Kay presentada en la introducción: los sistemas crecen mejor cuando se diseña cómo se comunican sus módulos, no cuando se fija de antemano todo su interior.

Mirar las interacciones permite formular preguntas concretas:

  • ¿qué necesita expresar el consumidor?;
  • ¿qué módulo responde por esa capacidad?;
  • ¿qué puede observarse al otro lado del límite?;
  • ¿qué conversación mantiene el módulo con sus propios proveedores?;
  • ¿qué decisiones pueden cambiar sin afectar a los demás?

El algoritmo sigue siendo importante, pero pertenece a otro nivel. Primero se distingue qué debe sobrevivir a cualquier implementación correcta y después se elige cómo conseguirlo.

Todos los fundamentos se repiten#

Cuando un módulo se descompone en módulos con contratos propios, los cinco fundamentos pueden aplicarse de nuevo en cada límite:

FundamentoPregunta que reaparece
Recursión¿Qué módulos y conversaciones existen en esta escala?
Vocabulario¿Qué significan sus nombres y mensajes?
Visibilidad¿Qué necesita conocer cada consumidor?
Autonomía¿Puede el módulo cumplir su contrato y entregar resultados sin estado mutable compartido?
Testeabilidad¿Puede verificarse mediante observaciones públicas?

La recursión no convierte cada parámetro, helper o estructura en otra API. Value, Result o Error forman parte del vocabulario de un mensaje. Solo pasan a considerarse módulos cuando reúnen comportamiento, tienen consumidores o necesitan evolucionar mediante un contrato propio.

Dónde continuar y dónde detenerse#

La perspectiva puede repetirse cuando aparece al menos una de estas señales:

  • otro consumidor necesita usar la capacidad directamente;
  • existe una responsabilidad con garantías propias;
  • el componente puede evolucionar o sustituirse de forma independiente;
  • una interacción cruza un límite técnico u organizativo relevante.

Se detiene cuando la decisión solo explica cómo trabaja el módulo actual. Un bucle, un índice o una función auxiliar no necesitan una API propia si ninguna relación externa depende de ellos.

Esto evita confundir recursión con una jerarquía infinita de interfaces. El objetivo es reconocer límites útiles, no fabricar capas.

Ejemplo en Go#

Un Pipeline ofrece una API y consume la API de cada Stage. El recorrido del arreglo permanece dentro de su implementación.

go
type Stage interface {
    Apply([]byte) ([]byte, error)
}

type Pipeline struct {
    stages []Stage
}

func (p Pipeline) Run(value []byte) ([]byte, error) {
    var err error
    for _, stage := range p.stages {
        value, err = stage.Apply(value)
        if err != nil {
            return nil, err
        }
    }
    return value, nil
}

Pueden existir muchas implementaciones de Stage. Cada una puede revisarse otra vez como módulo, mientras que el recorrido del slice sigue siendo un detalle de Pipeline.

Revisión práctica#

  1. Nombra la API que estás observando y el módulo que responde por ella.
  2. Identifica sus consumidores y las APIs que consume.
  3. Repite el análisis solo en colaboraciones con contrato propio.
  4. Mantén algoritmos y helpers dentro del módulo que los utiliza.
  5. Comprueba que la descomposición aclara una relación real.

La recursión sigue las conversaciones entre módulos, no cada línea de código.

I · Fundamentos · Fundamento 2 de 5

Fundamento 2. Vocabulario

Nombrar módulos, mensajes y valores desde la conversación que necesita el consumidor.

6 min de lectura Abrir Markdown fuente
En este capítulo
  1. Diseñar el vocabulario
  2. Ejemplo: añadir una tarea a una cola
  3. Ejemplo en Go
  4. Nombres exportados
  5. Buenos nombres
  6. El módulo
  7. Los mensajes
  8. Valores y resultados
  9. Booleanos, errores y eventos
  10. Comunicación
  11. El contexto evita repeticiones
  12. Encontrar un nombre
  13. Renombrar es migrar

Diseñar el vocabulario#

Un nombre no decora una solución terminada. Decide qué concepto verá el consumidor y qué podrá esperar de él.

Cuando una API usa palabras como execute, data o response, obliga a leer la implementación para descubrir su significado. Cuando nombra una intención, un resultado o una situación reconocible, permite entender el contrato sin abrir la implementación.

El vocabulario de una API debería seguir siendo verdadero aunque cambie su implementación.

Todo valor que cruza una API necesita un significado claro. Normalmente basta con resolver estas preguntas:

  • ¿qué representa?;
  • ¿qué valores son válidos?;
  • ¿cómo se compara?;
  • ¿puede estar ausente?;
  • ¿quién conserva su estado mutable?;
  • ¿qué representación puede observar el consumidor?

La respuesta no siempre requiere un tipo nuevo. Un tipo propio aporta cuando nombra una diferencia, protege una invariante o impide una combinación inválida. Si el contexto ya evita la confusión y las reglas son las mismas, separar dos valores solo añade ceremonia.

Ejemplo: añadir una tarea a una cola#

Esta firma describe principalmente la organización del código:

text
QueueService.Execute(EnqueueCommand) → QueueResponse

No sabemos qué se ejecuta, qué contiene la respuesta ni qué puede salir mal. La misma capacidad puede expresarse así:

text
Queue.Enqueue(Task) → Position | QueueFull

Cada palabra cumple una función:

  • Queue nombra el módulo y aporta contexto;
  • Enqueue expresa la intención del consumidor;
  • Task nombra el valor que entra;
  • Position explica el resultado;
  • QueueFull identifica una situación ante la que el consumidor puede actuar.

La implementación puede usar memoria, archivos o un sistema remoto. Ninguna de esas decisiones obliga a cambiar el mensaje.

Ejemplo en Go#

El ejemplo mantiene Queue, Enqueue, Task, Position y QueueFull como vocabulario visible.

go
import "errors"

type Task struct{ ID string }
type Position int

var ErrQueueFull = errors.New("queue full")

type Queue interface {
    Enqueue(Task) (Position, error)
}

Nombres exportados#

Los nombres públicos son el vocabulario compartido entre un módulo y sus consumidores. Merecen más estabilidad que los identificadores internos porque aparecen en llamadas, documentación, tests y, a veces, datos serializados.

Cada lenguaje expresa esa frontera de otra forma. En Go, un identificador exportado comienza con mayúscula; los nombres con minúscula permanecen dentro del paquete. Un directorio internal también permite limitar qué parte del árbol puede importar un paquete.

Exportar no mejora un nombre ni convierte un tipo en una buena abstracción. Primero se identifica qué necesita nombrar el consumidor; después se le da la visibilidad correspondiente. Los helpers y representaciones internas pueden usar palabras más técnicas sin contaminar la conversación pública.

Buenos nombres#

El módulo#

El nombre de un módulo debe indicar la capacidad que reúne, no el patrón con el que fue construido.

Manager, Service, Handler, Facade o Helper suelen ser débiles cuando aparecen solos. Clasifican una estructura técnica, pero no explican qué ofrece. QueueManager, por ejemplo, permite imaginar casi cualquier responsabilidad; Queue establece un contexto concreto para mensajes como Enqueue, Next o Remove.

Esto no convierte los nombres técnicos en un error universal. En la composición puede ser útil distinguir MemoryQueue de RemoteQueue, porque allí el consumidor elige una implementación. La API que ambas proporcionan puede seguir llamándose Queue.

La pregunta útil es: ¿el consumidor necesita conocer este mecanismo? Si la respuesta es no, el nombre técnico pertenece al interior.

Los mensajes#

Los mensajes expresan intenciones o hechos, no pasos internos.

Un verbo como Enqueue permite anticipar el efecto. Run, Execute, Process o Handle solo son precisos cuando ejecutar, procesar o despachar constituye realmente la capacidad del módulo.

La prueba más sencilla consiste en leer el uso como una frase:

text
position = queue.Enqueue(task)

Si para entenderla hay que traducir categorías del framework, el contrato todavía habla desde la implementación.

Valores y resultados#

Un tipo merece nombre cuando representa una diferencia que importa. Task, Position y Capacity dicen más que Data, Item o ValueObject, siempre que esas sean las palabras del contexto real.

Los sufijos que repiten la categoría técnica suelen sobrar:

text
TaskModel
PositionValueObject
QueueResponseDTO

Pueden ser necesarios dentro de un adaptador que traduce dos representaciones, pero no deberían propagarse a la API principal por accidente.

Las unidades también forman parte del significado. Delay puede resultar ambiguo si el consumidor necesita distinguir milisegundos de segundos. No siempre hace falta un nombre más largo; sí hace falta conservar la diferencia que evita un uso incorrecto.

Booleanos, errores y eventos#

Un booleano se entiende mejor como una proposición:

text
queue.IsFull()
queue.Contains(taskID)

Check, Flag o Status no aclaran qué significa true. Si existen más de dos resultados relevantes, probablemente el contrato necesite un estado nombrado en lugar de un booleano.

Los errores públicos describen situaciones ante las que el consumidor puede reaccionar. QueueFull permite esperar o elegir otra cola. DatabaseError filtra infraestructura y quizá no ofrece ninguna decisión útil. Los detalles técnicos pueden conservarse como causa interna, log o diagnóstico sin convertirse en vocabulario estable.

Un evento nombra un hecho que ya ocurrió. TaskQueued evita confundir la notificación con la petición EnqueueTask. El pasado también ayuda a que el nombre siga siendo cierto aunque cambie el transporte.

Comunicación#

El vocabulario funciona como un sistema, no como una lista de términos aislados. Módulo, mensaje, argumentos y resultados deben poder leerse juntos como una conversación.

El contexto evita repeticiones#

La precisión no consiste en incluir toda la explicación en cada identificador:

text
queue.Enqueue(task)

es más claro que:

text
queueService.ExecuteTaskEnqueueCommand(taskValueObject)

En el primer caso, módulo, mensaje y argumento reparten el significado. En el segundo, las categorías técnicas añaden longitud sin explicar mejor el contrato.

Un nombre corto puede ser ambiguo y uno largo puede compensar un contexto mal elegido. La meta es usar las palabras mínimas que conserven el significado donde se leen.

Encontrar un nombre#

El nombre suele aparecer al describir primero la conversación:

  1. Escribe una frase desde el consumidor: «quiero añadir esta tarea a la cola».
  2. Separa el contexto, la intención, los valores y las situaciones que cambian la conducta.
  3. Comprueba qué palabras utilizan las personas que conocen el problema.
  4. Lee una llamada completa y elimina lo que el contexto ya dice.
  5. Imagina otra implementación y comprueba que el vocabulario sigue siendo cierto.
  6. Revisa de nuevo cada error o evento que el consumidor deba distinguir.

Si cuesta nombrar un módulo, puede haber responsabilidades mezcladas o una abstracción prematura. En ese caso conviene volver a la conversación antes de buscar un sinónimo más elegante.

Esta práctica coincide con el lenguaje ubicuo de DDD: los nombres ganan precisión dentro de un contexto explícito, no en un diccionario universal (DDD Reference, Eric Evans). API-DD añade una comprobación concreta: el vocabulario debe funcionar desde el módulo consumidor y sobrevivir a implementaciones alternativas.

Renombrar es migrar#

Una vez publicada, una palabra forma parte del contrato. Cambiarla puede romper compilación, mensajes serializados, documentación, métricas o integraciones.

Una migración puede requerir introducir el nombre nuevo junto al anterior, adaptar consumidores y retirar el alias cuando ya no exista dependencia. Que el nombre nuevo sea mejor no vuelve inocuo el cambio.

Un buen nombre explica la conversación y no delata el mecanismo que la hace posible.

I · Fundamentos · Fundamento 3 de 5

Fundamento 3. Visibilidad

Mostrar lo que necesita el consumidor y mantener reemplazable la implementación.

5 min de lectura Abrir Markdown fuente
En este capítulo
  1. Visibilidad y acoplamiento
  2. Lo que puede observar el consumidor
  3. Ejemplo: un conjunto sin duplicados
  4. Ejemplo en Go
  5. API pública e implementación
  6. Estado y efectos
  7. Repetición e idempotencia
  8. Compatibilidad
  9. Revisión práctica

Visibilidad y acoplamiento#

Una API es el protocolo que permite a dos módulos colaborar. Su contrato reúne la información mínima que un consumidor necesita para usarla sin conocer su implementación.

Una firma puede mostrar nombres y tipos:

text
Set.Add(Value) → AddResult

Pero no explica por sí sola si se permiten duplicados, cómo se compara un valor, qué cambia después de la llamada ni qué ocurrirá al repetirla. Esas garantías también pertenecen al contrato.

Cada elemento visible crea una dependencia: el consumidor puede utilizarlo y el proveedor tendrá que conservarlo o migrarlo. Reducir visibilidad disminuye acoplamiento solo cuando la API sigue expresando toda la capacidad necesaria.

Lo que puede observar el consumidor#

PartePregunta
Consumidores¿Quién utiliza la API?
Mensajes¿Qué puede pedir o comunicar?
Vocabulario¿Qué significan entradas, resultados, errores y eventos?
Validez¿Qué valores y secuencias se aceptan?
Garantías¿Qué resultado, estado o efecto se conserva?
Visibilidad¿Qué consumidores pueden acceder al contrato?
Compatibilidad¿Qué usos anteriores deben seguir funcionando?

No todas las APIs necesitan documentar cada dimensión con el mismo detalle. La profundidad depende del riesgo y del número de consumidores. Un módulo local con una operación pura puede quedar claro con una firma y dos ejemplos; un protocolo compartido necesitará más precisión.

Ejemplo: un conjunto sin duplicados#

Consideremos esta API independiente:

text
Set.Add(Value)      → Added | AlreadyPresent
Set.Contains(Value) → bool
Set.Size()          → integer

Su contrato puede expresarse con cuatro garantías:

  1. añadir un valor ausente devuelve Added;
  2. después de añadirlo, Contains devuelve true;
  3. añadir el mismo valor otra vez devuelve AlreadyPresent;
  4. repetir la operación no aumenta Size.

El contrato aún necesita una decisión sobre igualdad: ¿cuándo representan dos valores lo mismo? No necesita decidir si la implementación usa una tabla hash, un árbol o una lista. Cualquiera de ellas es válida si conserva las garantías.

Este ejemplo también muestra la diferencia entre API y vocabulario. Value, Added y AlreadyPresent forman parte de los mensajes. No son tres APIs nuevas. Solo necesitarían contratos independientes si adquirieran comportamiento, consumidores o evolución propios.

Ejemplo en Go#

Esta implementación protege el contrato y deja la estructura interna fuera de la API.

go
type AddResult uint8

const (
    Added AddResult = iota
    AlreadyPresent
)

type UniqueSet[T comparable] struct {
    values map[T]struct{}
}

func (s *UniqueSet[T]) Add(value T) AddResult {
    if s.values == nil {
        s.values = make(map[T]struct{})
    }
    if _, exists := s.values[value]; exists {
        return AlreadyPresent
    }
    s.values[value] = struct{}{}
    return Added
}

func (s *UniqueSet[T]) Contains(value T) bool {
    _, exists := s.values[value]
    return exists
}

func (s *UniqueSet[T]) Size() int {
    return len(s.values)
}

API pública e implementación#

Una decisión pertenece a la API cuando un consumidor legítimo necesita utilizarla o distinguirla y el proveedor está dispuesto a conservarla.

Pertenece al contratoPermanece en la implementación
Intenciones que el consumidor puede expresarSecuencia interna de llamadas
Valores y diferencias que cambian su conductaEstructuras intermedias
Errores ante los que puede actuarFallos técnicos ya traducidos
Estado y efectos observablesAlgoritmos y mecanismos de coordinación
Orden o cantidad cuando alteran el resultado externoOptimización y distribución del trabajo

Una superficie pequeña es útil si permite expresar la capacidad completa. Ocultar una diferencia necesaria no reduce acoplamiento: obliga al consumidor a deducirla o a buscarla en la implementación.

Estado y efectos#

El resultado inmediato es solo una forma de observación. Un mensaje puede modificar estado que luego se consulta mediante la API o producir un efecto dirigido a otro módulo.

El contrato debe nombrar esos efectos cuando el consumidor depende de ellos. No debe publicar la coordinación utilizada para conseguirlos.

En el ejemplo del conjunto, Size y Contains permiten observar el estado sin exponer su representación. El consumidor puede comprobar la garantía sin recibir la colección interna ni modificarla por referencia.

Cuando la API devuelve colecciones o estructuras mutables, hay que decidir quién conserva su propiedad. Si siguen perteneciendo al proveedor, una copia o una vista inmutable puede evitar cambios accidentales. Si la propiedad se transfiere, conviene decirlo de forma explícita.

Repetición e idempotencia#

Una operación es idempotente cuando repetir la misma intención produce el mismo efecto observable que ejecutarla una vez. No significa que el proveedor ejecute una sola instrucción ni que devuelva la misma instancia.

Set.Add es idempotente respecto del contenido: repetir el mismo valor no crea otra entrada. La respuesta puede cambiar de Added a AlreadyPresent y la garantía seguir siendo válida, porque el estado final no cambia.

La idempotencia solo merece entrar en el contrato cuando existen reintentos, repeticiones o entregas duplicadas que el consumidor deba poder manejar. Añadirla sin necesidad introduce identidad, estado y costes de retención.

Compatibilidad#

Una API publicada acumula consumidores. Cambiar un nombre, una regla de validez, un error o el significado de un campo puede romperlos aunque el código siga compilando.

Antes de modificar el contrato hay que saber:

  • qué consumidores existen;
  • qué comportamiento observan hoy;
  • si el cambio puede ser aditivo;
  • cómo convivirán temporalmente las dos versiones;
  • cuándo podrá retirarse el contrato anterior.

La compatibilidad no exige conservar para siempre una mala decisión. Exige tratar su corrección como una migración y no como un refactor interno.

Revisión práctica#

Antes de implementar una API, comprueba:

  1. El consumidor y su intención están identificados.
  2. Cada mensaje expresa una capacidad necesaria.
  3. Entradas, resultados y errores tienen un significado inequívoco.
  4. Estado y efectos observables están declarados.
  5. Los detalles internos siguen siendo reemplazables.
  6. Los valores compartidos tienen reglas y responsable claros.
  7. La repetición y la compatibilidad se deciden solo donde importan.

Un buen contrato permite dos cosas a la vez: que el consumidor use el módulo con confianza y que el proveedor cambie su interior con libertad.

I · Fundamentos · Fundamento 4 de 5

Fundamento 4. Autonomía

Construir módulos, APIs y resultados autónomos en cada escala del sistema.

5 min de lectura Abrir Markdown fuente
En este capítulo
  1. Autonomía en cada escala
  2. API autónoma
  3. Módulo completo
  4. Módulo válido
  5. Resultados autónomos
  6. Ejemplo en Go
  7. Valores iniciales y ausencia
  8. Dependencias autónomas
  9. Revisión práctica

Autonomía en cada escala#

La autonomía no aparece únicamente en el módulo más grande. Puede fomentarse de forma granular cada vez que la perspectiva de API-DD se aplica recursivamente.

EscalaQué significa autonomía
MóduloReúne la capacidad y protege sus invariantes
APIOfrece una conversación suficiente sin revelar cómo se coordina el interior
Mensaje de resultadoTiene significado propio y no comparte estado mutable con quien lo produjo

Autonomía no significa aislamiento. Un módulo puede consumir otras APIs y un resultado puede contener varios valores. La diferencia es que cada elemento conserva un límite claro: ningún consumidor necesita reparar su estado, completar su significado ni conocer una representación ajena.

Aplicar el fundamento a un resultado no lo convierte en otro módulo. El resultado sigue siendo vocabulario de la API, pero también necesita propiedad y validez propias.

API autónoma#

Una API es autónoma cuando el consumidor puede expresar una intención y comprender la respuesta dentro de la misma conversación. No necesita abrir la implementación, consultar una estructura interna ni coordinar colaboradores que pertenecen al proveedor.

Una secuencia pública puede formar parte de un protocolo real. Pierde autonomía cuando sus pasos solo existen para terminar de montar el módulo:

text
value := Interval{}
value.SetStart(2)
value.SetEnd(8)
value.Validate()

La intención completa puede expresarse en un solo mensaje:

text
Interval.New(2, 8) → Interval | InvalidInterval

La autonomía tampoco exige que cada respuesta incluya todos los datos imaginables. Incluye lo necesario para que el consumidor actúe ante las diferencias que el contrato reconoce.

Módulo completo#

Un módulo es completo cuando reúne lo necesario para cumplir el contrato que ofrece. El consumidor usa su capacidad mediante la API sin completar pasos internos, corregir su estado ni decidir cómo deben coordinarse sus dependencias.

Completo no significa autosuficiente. El módulo puede leer, calcular o producir efectos mediante otras APIs. Su responsabilidad consiste en gobernar esas colaboraciones y traducirlas al contrato que ofrece.

Una señal de incompletitud aparece cuando varios consumidores repiten la misma coordinación para obtener una capacidad que debería pertenecer al módulo. La solución no es esconder cualquier secuencia: es asignar la responsabilidad al límite que puede garantizarla.

Módulo válido#

Un módulo válido conserva sus invariantes en todos los estados observables. Puede rechazar una entrada o devolver un error; evita continuar con un estado incoherente que otro consumidor tenga que descubrir después.

La validez se protege donde entra información:

  • construcción;
  • mensajes que cambian estado;
  • interpretación de datos externos;
  • recuperación desde persistencia;
  • resultados recibidos desde otra API.

Un estado inicial vacío puede ser válido. También puede ser inválido pero seguro. La API debe distinguirlo antes de producir un efecto incorrecto.

Resultados autónomos#

Un resultado autónomo pertenece a la conversación que lo recibe. Su significado está completo y su contenido no cambia porque el proveedor continúe trabajando ni porque otro consumidor lo utilice.

Esto requiere evitar referencias compartidas a estado mutable. Devolver directamente un slice, un mapa o un puntero interno permite que el consumidor modifique al proveedor y que el proveedor altere un resultado ya entregado. Ambos dejan de ser autónomos.

Las alternativas habituales son:

  • devolver valores con semántica de copia;
  • usar campos privados y operaciones de lectura;
  • copiar colecciones al entregar y al exponer su contenido;
  • transferir la propiedad de forma explícita;
  • ofrecer otra API para recorrer datos cuando una copia resulte demasiado costosa.

Go no tiene una declaración general de inmutabilidad. Aquí, inmutable significa que la API pública no permite cambiar el resultado y que el proveedor no puede alterarlo después de entregarlo. Un struct copiado puede contener slices, mapas o punteros que aún comparten memoria; la autonomía debe llegar hasta cada referencia mutable, no detenerse en el tipo exterior.

Ejemplo en Go#

Collection conserva su estado y entrega un Snapshot autónomo. El snapshot copia los datos y solo ofrece operaciones de lectura.

go
package collection

import "errors"

var (
    ErrInvalidLimit = errors.New("invalid limit")
    ErrInvalid      = errors.New("invalid collection")
    ErrFull         = errors.New("collection full")
)

type Snapshot struct {
    values []string
}

func newSnapshot(values []string) Snapshot {
    return Snapshot{values: clone(values)}
}

func (s Snapshot) Len() int {
    return len(s.values)
}

func (s Snapshot) At(index int) (string, bool) {
    if index < 0 || index >= len(s.values) {
        return "", false
    }
    return s.values[index], true
}

type Collection struct {
    limit  int
    values []string
}

func New(limit int) (*Collection, error) {
    if limit <= 0 {
        return nil, ErrInvalidLimit
    }
    return &Collection{limit: limit}, nil
}

func (c *Collection) Add(value string) error {
    if c == nil || c.limit <= 0 {
        return ErrInvalid
    }
    if len(c.values) == c.limit {
        return ErrFull
    }
    c.values = append(c.values, value)
    return nil
}

func (c *Collection) Snapshot() Snapshot {
    if c == nil {
        return Snapshot{}
    }
    return newSnapshot(c.values)
}

func clone(values []string) []string {
    return append([]string(nil), values...)
}

New produce un módulo válido o un error. Add responde de forma segura incluso ante el valor cero de Go. Snapshot no conserva el slice de Collection ni lo expone: Len y At permiten leerlo sin ofrecer una operación de mutación.

Valores iniciales y ausencia#

En Go, todo tipo concreto tiene un valor cero. Publicar New no lo elimina. El contrato puede tratarlo como útil, como ausencia o como inválido pero seguro. La especificación de Go define el mecanismo; la API define su significado.

Ausente y presente con valor cero tampoco son siempre equivalentes. Si la diferencia cambia el comportamiento, puede representarse mediante (T, bool), un puntero o un resultado nombrado. Los formatos de transporte se traducen en el adaptador para que el módulo reciba significado, no detalles de serialización.

Dependencias autónomas#

Una dependencia forma parte de la construcción cuando el módulo no puede cumplir su contrato sin ella. Recibirla mediante una API hace visible qué necesita, pero no traslada su coordinación al consumidor.

No hace falta introducir una interfaz para cada helper. Una dependencia merece contrato propio cuando tiene otros consumidores o proveedores, cruza un límite relevante o evoluciona de forma autónoma. En ese punto los mismos fundamentos pueden aplicarse de nuevo.

Revisión práctica#

  1. La API expresa una intención completa para su consumidor.
  2. El módulo gobierna sus dependencias y conserva sus invariantes.
  3. La creación produce un módulo válido o un fallo explícito.
  4. Cada resultado contiene el significado necesario para actuar.
  5. Ningún resultado comparte estado mutable con el proveedor.
  6. Slices, mapas, punteros y elementos internos tienen propiedad clara.
  7. La misma revisión se repite en cada límite granular relevante.

La autonomía se conserva desde el módulo hasta el resultado que cruza su API.

I · Fundamentos · Fundamento 5 de 5

Fundamento 5. Testeabilidad

Verificar resultados, estado y mensajes sin convertir el interior en especificación.

5 min de lectura Abrir Markdown fuente
En este capítulo
  1. El test como consumidor
  2. Qué puede observar una prueba
  3. Ejemplo: conservar un valor ante un fallo
  4. Ejemplo en Go
  5. Hablar con el vocabulario habitual
  6. Elegir colaboradores
  7. Cuándo observar interacciones
  8. Qué debe conservar un fake
  9. Método de revisión

El test como consumidor#

Un test funcional representa a un consumidor del módulo. Entra por su API y comprueba resultados, estado o mensajes que pertenecen al contrato.

La caja negra no tiene que abarcar todo el sistema. Puede ser un módulo pequeño siempre que la prueba respete su límite y no convierta la coordinación interna en una promesa pública.

text
test → API → implementación
                  └── API consumida → colaborador

La pregunta principal es qué debe seguir siendo cierto para el consumidor. La elección entre implementación real, doble de prueba o integración viene después.

Qué puede observar una prueba#

Un contrato ofrece tres clases de observación:

ObservaciónEjemplo genérico
Resultado directoValor o error devuelto
Estado accesible por la APIUna consulta posterior refleja el cambio
Mensaje salienteSe publica un evento o se solicita un efecto

Los helpers utilizados, el reparto entre objetos internos y el algoritmo elegido quedan fuera. También quedan fuera el número y el orden de llamadas cuando no cambian el resultado observable.

Una prueba funcional debería aceptar dos implementaciones que produzcan los mismos resultados, el mismo estado visible y los mismos mensajes contractuales.

Ejemplo: conservar un valor ante un fallo#

Consideremos un módulo de caché:

text
Cache.Refresh(Key) → Value | SourceUnavailable
Cache.Get(Key)     → Value | NotFound

Cache consume otra API:

text
Source.Load(Key) → Value | error

Queremos demostrar una sola garantía: si la fuente falla durante una actualización, el valor anterior continúa disponible.

El test ejecuta la implementación real de Cache y prepara un doble de Source que devuelve un fallo. Después observa únicamente la API:

text
dado un valor almacenado para una clave
cuando Refresh recibe SourceUnavailable
entonces devuelve SourceUnavailable
y Get conserva el valor anterior

La prueba no necesita saber si la caché escribe primero en una copia, usa un bloqueo o revierte una asignación. Tampoco necesita una fuente remota real, porque el riesgo observado es la reacción del módulo al fallo, no el protocolo de red.

Una prueba diferente debería comprobar el adaptador real si el riesgo estuviera en la serialización, la configuración o el transporte.

Ejemplo en Go#

Suponiendo la API anterior, el caso puede escribirse sin observar ninguna llamada interna.

go
import (
    "errors"
    "testing"
)

type sourceStub struct{ err error }

func (s sourceStub) Load(string) (string, error) {
    return "", s.err
}

func TestRefreshKeepsPreviousValue(t *testing.T) {
    cache := NewCache(sourceStub{err: ErrSourceUnavailable})
    cache.Put("key", "previous")

    _, err := cache.Refresh("key")
    if !errors.Is(err, ErrSourceUnavailable) {
        t.Fatalf("expected source error, got %v", err)
    }

    got, _ := cache.Get("key")
    if got != "previous" {
        t.Fatalf("expected previous value, got %q", got)
    }
}

El test controla una entrada indirecta mediante un stub y comprueba resultado y estado. No exige cuántos helpers debe ejecutar la caché.

Hablar con el vocabulario habitual#

Doble de prueba es el término general para cualquier sustitución usada durante una prueba. Dentro de esa familia conviene mantener los significados conocidos, en lugar de redefinir fake para abarcarlo todo.

TérminoUso
Implementación realLa misma implementación usada fuera del test
FakeImplementación funcional simplificada, como un almacenamiento en memoria
StubDevuelve respuestas preparadas para controlar una entrada indirecta
SpyRegistra mensajes para poder observarlos después
MockDeclara y verifica expectativas sobre interacciones

La terminología procede de la taxonomía recogida por Gerard Meszaros y resumida por Martin Fowler en Test Double. Saber el nombre ayuda, pero la decisión importante sigue siendo qué comportamiento sustituye el doble y qué riesgo deja sin cubrir.

En el ejemplo anterior basta un stub de Source: prepara el fallo que el caso necesita. Un fake funcional sería útil si muchos tests necesitaran una fuente en memoria con reglas estables. Un mock solo tendría sentido si una interacción concreta formara parte del contrato.

Elegir colaboradores#

La implementación real ofrece la mayor fidelidad y es la primera opción cuando resulta rápida, determinista, hermética, segura y fácil de construir. La guía de Software Engineering at Google sobre test doubles propone el mismo punto de partida pragmático.

SituaciónElección habitual
Colaborador rápido y deterministaImplementación real
Muchos casos necesitan semántica estable sin infraestructuraFake funcional
Un caso necesita una respuesta excepcionalStub local
El contrato incluye un mensaje salienteSpy o mock
El riesgo depende de protocolo, transacción o configuraciónIntegración con el adaptador real

No es necesario sustituir valores, entidades o funciones puras solo porque colaboren en el caso. Tampoco hace falta levantar infraestructura real para demostrar una regla que no depende de ella.

Cuándo observar interacciones#

Un mensaje saliente puede formar parte del contrato. En ese caso un spy o un mock permite observar su contenido, cantidad u orden.

La expectativa debe limitarse a la diferencia funcional:

  • contenido, si otro consumidor depende de esos campos;
  • cantidad, si duplicar u omitir el mensaje cambia el efecto externo;
  • orden, si el protocolo lo exige.

Comprobar que se llamó a un mapper, una query concreta o un helper privado congela la implementación. Comprobar que se publicó un mensaje requerido protege el contrato.

Una interacción solo demuestra que el mensaje se intentó enviar. No demuestra que el receptor real lo acepte. Cuando esa compatibilidad es el riesgo, hace falta una prueba del adaptador o una integración.

Qué debe conservar un fake#

Un fake funcional implementa un subconjunto declarado del contrato. Debe:

  • aceptar las mismas entradas en los casos soportados;
  • producir resultados, errores y estado compatibles;
  • ser determinista y aislar el estado de cada test;
  • rechazar de forma visible las capacidades que no implementa;
  • documentar las propiedades que omite.

No necesita copiar la infraestructura. Una fuente en memoria puede reproducir lectura, ausencia y versiones sin simular red o latencia. Si el caso trata precisamente de esas propiedades omitidas, el fake deja de ser una prueba suficiente.

Un fake compartido acumula responsabilidad y merece tests propios. Cuando sea viable, una misma suite de contrato puede ejecutarse contra el fake y el adaptador real para detectar divergencias.

Método de revisión#

Para diseñar una prueba:

  1. Nombra el módulo, la API consumida y la garantía que se quiere proteger.
  2. Elige una observación pública: resultado, estado o mensaje.
  3. Retira afirmaciones sobre coordinación interna.
  4. Ejecuta colaboradores reales mientras sean prácticos.
  5. Introduce el doble más pequeño que permita controlar u observar el caso.
  6. Declara qué fidelidad falta y qué riesgo necesita integración.
  7. Comprueba que otra implementación correcta también pasaría.

Primero decide qué debe observarse; después elige con qué implementaciones puede demostrarse.

II · Desarrollo con IA · Capítulo 7 de 8

Desarrollo con IA: contratos para obtener código verificable

Usar contratos y tests para orientar y verificar el código generado.

6 min de lectura Abrir Markdown fuente
En este capítulo
  1. La IA no elimina las decisiones de diseño
  2. Pensar el sistema como APIs
  3. Los casos de prueba concretan el resultado
  4. Un reparto de trabajo útil
  5. Un flujo posible
  6. Qué suele degradar el resultado
  7. Ejemplo en Go
  8. Qué mejora en el código esperado

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ónQué aclara para la IA
ConsumidorDesde qué necesidad debe diseñarse el cambio
MensajesQué operaciones, resultados y errores puede usar
GarantíasQué comportamiento debe conservarse
LímiteQué archivos y módulos pertenecen al cambio
Detalles internosQué decisiones puede tomar libremente la implementación
APIs consumidasQué 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.

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. Formula un caso por cada diferencia observable importante.
  5. Ejecuta los tests y comprueba que los nuevos casos fallan por la razón esperada.
  6. Implementa el cambio sin ampliar el contrato innecesariamente.
  7. 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 y para implementar y verificarlo.

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.

III · TDD, DDD y Hexagonal · Capítulo 8 de 8

API-DD junto a TDD, DDD y arquitectura hexagonal

Combinar enfoques por las preguntas que responden.

4 min de lectura Abrir Markdown fuente
En este capítulo
  1. Preguntas diferentes
  2. DDD aporta significado
  3. La arquitectura hexagonal orienta conversaciones
  4. TDD guía la construcción
  5. Ejemplo en Go
  6. Confusiones frecuentes
  7. ¿Toda API es un port?
  8. ¿Todo módulo necesita una interfaz del lenguaje?
  9. ¿API-DD prescribe un orden de diseño?
  10. ¿Un test con varios módulos deja de ser unitario?
  11. ¿Observar un mensaje saliente rompe la caja negra?
  12. Una secuencia práctica

Preguntas diferentes#

API-DD no reemplaza estos enfoques. Pueden combinarse cuando cada uno conserva su pregunta principal.

EnfoquePregunta que ayuda a responder
DDD¿Qué significa el modelo y dentro de qué contexto?
Arquitectura hexagonal¿Qué conversaciones conectan la aplicación con actores y tecnologías?
TDD¿Cómo hacemos crecer el comportamiento mediante feedback ejecutable?
API-DD¿Cómo se comunican los módulos y qué contrato ofrece cada uno?

No hace falta adoptar los cuatro. La tabla sirve para evitar que una técnica responda preguntas que no le corresponden.

DDD aporta significado#

DDD ayuda a descubrir conceptos, invariantes, lenguaje y límites de modelo. Cuando dos áreas utilizan una palabra parecida, permite decidir si comparten significado o necesitan representaciones distintas.

API-DD puede aprovechar ese resultado para diseñar las conversaciones que los consumidores necesitan. No decide por sí solo cuál es el modelo correcto ni requiere que el trabajo empiece por DDD.

La referencia utilizada aquí es DDD Reference de Eric Evans.

La arquitectura hexagonal orienta conversaciones#

Un port representa una conversación con propósito; los adaptadores conectan mecanismos concretos a ella. Desde API-DD, ese port puede mirarse como una API: mensajes, vocabulario, garantías y efectos.

No todo módulo necesita convertirse en un port. Dos módulos internos pueden colaborar mediante una API local sin representar un límite arquitectónico de la aplicación. Convertir cada relación en port añadiría visibilidad y sustitución sin una necesidad real.

La intención original de puertos y adaptadores está descrita por Alistair Cockburn en Hexagonal Architecture.

TDD guía la construcción#

TDD aporta el ciclo de feedback: elegir el siguiente comportamiento, escribir un test que falle, implementarlo y refactorizar. API-DD ayuda a formular ese comportamiento como una garantía observable de un módulo.

text
garantía del contrato → rojo → verde → refactor

El test puede descubrir que el contrato estaba incompleto. En ese caso se revisa la decisión antes de continuar; no se fuerza la implementación para conservar una especificación equivocada.

El ciclo se apoya en la descripción de TDD de Martin Fowler, basada en el trabajo de Kent Beck.

Ejemplo en Go#

Supongamos que un módulo necesita guardar y recuperar bytes por clave. Esta API puede actuar como port de salida cuando existen proveedores intercambiables.

go
type Key string
type Value []byte

type Store interface {
    Load(Key) (Value, bool, error)
    Save(Key, Value) error
}

La arquitectura hexagonal orienta esta dependencia hacia la necesidad del consumidor. API-DD ayuda a hacer visibles decisiones como qué significa ausencia, quién posee los bytes devueltos y qué errores deben distinguirse. TDD permite implementar esas garantías una a una. Si nunca habrá otro proveedor ni un límite relevante, una interfaz separada puede ser innecesaria.

Confusiones frecuentes#

¿Toda API es un port?#

No. Todo port ofrece una API, pero una API también puede existir entre módulos internos que no cruzan el límite de la aplicación.

¿Todo módulo necesita una interfaz del lenguaje?#

No. Una API puede expresarse mediante un tipo concreto, funciones, métodos o mensajes. Una interface técnica aporta cuando un consumidor necesita sustitución o desacoplamiento, no como requisito ceremonial.

¿API-DD prescribe un orden de diseño?#

No. Se puede empezar por cualquier módulo, regla o conversación cuyo contrato y riesgo estén claros. API-DD ofrece una perspectiva para revisar cada módulo como una API, sin prescribir una dirección temporal para descubrir el sistema.

¿Un test con varios módulos deja de ser unitario?#

La cantidad de objetos o módulos no determina qué riesgo cubre la prueba. Resulta más útil declarar la API observada y qué implementaciones participan que discutir una etiqueta universal.

¿Observar un mensaje saliente rompe la caja negra?#

No cuando ese mensaje forma parte del efecto prometido. Sí cuando se comprueba una colaboración interna que otra implementación correcta podría resolver de manera distinta.

Una secuencia práctica#

  1. Usa el modelado disponible para aclarar conceptos y límites.
  2. Identifica los módulos implicados y las conversaciones entre ellos.
  3. Diseña cada módulo como una API.
  4. Formula garantías observables y elige el nivel de prueba adecuado.
  5. Implementa en ciclos pequeños y refactoriza sin alterar el contrato.

El trabajo real no será lineal. Un nombre descubierto durante un test puede cambiar el modelo; una restricción arquitectónica puede obligar a revisar la API. La separación de preguntas sirve para entender la decisión, no para imponer fases rígidas.

DDD aclara el significado, la arquitectura orienta las relaciones, TDD guía el cambio y API-DD ayuda a diseñar la conversación.

IV · Guía operativa · Apéndice A de 2

Prompt 1 de API-DD: descubrir el contrato y escribir sus tests en rojo

Prompt breve para investigar un cambio y dejar evidencia funcional en rojo.

12 min de lectura Abrir Markdown fuente
En este capítulo
  1. Uso
  2. Prompt

Uso#

Entrega este prompt a la IA junto con el ticket, la documentación funcional y el repositorio. Es interactivo: la IA investiga primero, pregunta solo por decisiones que cambian el contrato y se detiene hasta recibir respuesta. Después define el contrato y deja sus tests en rojo, sin implementar la funcionalidad.

El prompt es autónomo, pero la IA debe leer las instrucciones y convenciones del repositorio.

Sustituye [REPOSITORIO], [TAREA] y [CONTEXTO ADICIONAL].

Prompt#

text
Actúa como analista de dominio y diseñador de APIs y pruebas mediante API-DD (API-Driven Development).

Repositorio:
[REPOSITORIO]

Tarea o ticket:
[TAREA]

Contexto adicional:
[CONTEXTO ADICIONAL]

## Marco de trabajo

API-DD diseña desde fuera hacia dentro: identifica al consumidor y el comportamiento que necesita, define la API y su contrato observable, exprésalo con tests funcionales de caja negra y deja la implementación para la etapa siguiente.

Vocabulario:

- API: protocolo de cualquier módulo con consumidores; puede expresarse mediante operaciones, objetos, mensajes, eventos, endpoints u otros mecanismos.
- Boundary: punto donde cambia el consumidor, propietario, visibilidad, modelo, proceso o protocolo.
- Contrato observable: entradas, resultados, errores, estado, efectos, invariantes y garantías visibles para el consumidor, nunca el recorrido interno.
- Contrato estructural: compatibilidad de operaciones, nombres, tipos, schemas, requests, responses o mensajes.
- Contrato funcional: resultado y efectos de una capacidad en un escenario del dominio.
- Contrato del dominio: invariantes, permisos, estados y transiciones que deben preservarse.
- Test funcional: entra por una API pública y observa resultados o efectos sin especificar cómo se producen.
- Caso funcional único: obligación distinguible por regla, estado, clase de entrada o intención, resultado o efecto y boundary o riesgo protegido. Cambiar solo ejemplos, fixtures, entrada equivalente o nivel de test no crea otro caso.
- Cobertura semántica: trazabilidad entre reglas o riesgos y escenarios; el porcentaje de líneas es solo una señal secundaria.
- Implementación real: implementación de producción; tiene prioridad si es rápida, determinista, hermética, segura y sencilla de preparar.
- Fake: implementación simplificada pero funcional de una API, gobernada por estados y reglas coherentes.
- Dummy contractual: scaffold reusable mínimo que fija la forma pública del futuro soporte y permite ejecutar el test con comportamiento neutral. No implementa reglas, matching, reintentos ni fallos programables, y nunca debe causar el rojo.
- Stub, spy o mock: doble para preparar respuestas u observar interacciones. Solo es válido si se limita a un mensaje o garantía observable del contrato, no a coordinación interna accidental.
- Integración real: prueba cuyo riesgo depende de un adaptador, protocolo o infraestructura real. E2E: recorrido crítico a través de varios subsistemas o límites desplegables reales.
- Referente real: concepto reconocible del dominio —actor, entidad, valor, capacidad, política, evento, intención o resultado— al que corresponde un objeto público.

Tu objetivo es:

1. investigar comportamiento, APIs, límites y tests existentes;
2. resolver con el usuario las decisiones funcionales ausentes;
3. definir el contrato y el catálogo mínimo de casos únicos;
4. añadir solo la estructura imprescindible y escribir tests funcionales en rojo;
5. definir, sin implementar, el soporte reusable que completará el Prompt 2.

Los tests nuevos deben compilar o cargarse, ejecutar la API y fallar en una observación funcional porque falta producción. Los tests previamente verdes deben conservar su estado. Si falta soporte, crea solo su contrato y dummy neutral; si ni eso permite un rojo fiable, deja el caso pendiente para el Prompt 2. No implementes la funcionalidad.

## Principios obligatorios

- Diseña cada límite relevante desde su consumidor. Una API no es sinónimo de HTTP ni cada operación u objeto constituye otra API.
- Contrata solo lo observable: mensajes, resultados, errores, estados, efectos, invariantes, compatibilidad, privacidad e idempotencia cuando proceda.
- Mantén reemplazables algoritmos, métodos privados y coordinación interna. Cantidad u orden solo son contrato si el dominio o protocolo los garantiza expresamente, por ejemplo no cobrar dos veces.
- No inventes reglas. Separa hechos confirmados, inferencias, propuestas, contradicciones y preguntas abiertas.
- Crea solo casos funcionales. No generes combinaciones mecánicas de nulo, vacío, cero o negativo salvo que representen clases funcionales confirmadas.
- Cada test nuevo debe detectar una desviación observable distinta y sobrevivir a un refactor que conserve el contrato.
- Usa primero implementaciones reales aptas. No sustituyas objetos de dominio, parsers, validadores o algoritmos locales que puedan probarse directamente.
- Reutiliza soporte de test existente. No crees un doble aislado por test cuando falta una colaboración reusable.
- Un fake modela el contrato sustituido mediante estado y reglas; no es una colección de respuestas arbitrarias. Un spy o mock solo observa mensajes que cruzan un boundary contractual.
- No hagas público código de producción únicamente para facilitar un test.
- No añadas integración real ni E2E sin aprobación explícita del usuario.
- Si una duda cambia comportamiento, compatibilidad, seguridad, privacidad, propiedad del dato o diseño público, pregunta y no elijas silenciosamente.

## Fase 0: inspecciona sin modificar

Antes de preguntar o escribir archivos:

1. Lee las instrucciones, documentación, automatización y convenciones relevantes del repositorio.
2. Localiza las APIs de entrada y salida afectadas, sus consumidores y proveedores, y cualquier schema, contrato remoto, evento o representación de transporte relacionada.
3. Revisa los tests del recorrido y registra qué obligación funcional protege realmente cada uno.
4. Para cada API colaboradora, localiza primero la implementación real y evalúa con evidencia su velocidad, determinismo, hermeticidad, seguridad, efectos externos y coste de preparación.
5. Solo si la real no es apta, busca soporte de test reusable, implementaciones en memoria, simuladores, suites compartidas y dobles locales, aunque no usen nombres como fake o mock.
6. Comprueba si el soporte encontrado cubre el perfil necesario sin cambios o admite una extensión aditiva compatible.
7. Construye un inventario con API, propietario, consumidores, implementación real, idoneidad, soporte, perfil de fidelidad y decisión `REUTILIZAR`, `EXTENDER`, `NUEVO` o `NO APTO`, siempre con evidencia.
8. Trata los dobles locales como antecedentes que quizá deban extraerse, no como motivo para duplicarlos.
9. Identifica las implementaciones y recursos reales de cualquier integración o E2E candidato.
10. Ejecuta una línea base acotada y separa fallos preexistentes de los que introducirá esta etapa.

No preguntes lo que pueda comprobarse en el repositorio.

## Mapa de APIs y límites

Para cada API afectada registra:

- capacidad, consumidor y proveedor;
- entradas, salidas y efectos observables;
- propietario y visibilidad;
- compatibilidad exigida;
- objetos públicos del contrato y referente real de cada uno.

Toda API pública nueva necesita consumidor, propósito y propietario. Cada objeto público del contrato debe corresponder a un concepto del dominio; las representaciones puramente técnicas permanecen en el boundary y se mapean al modelo. Evita objetos genéricos sin referente reconocible.

Profundiza el mapa según visibilidad, número de consumidores, coste de cambio, irreversibilidad y riesgo. No formalices cada método privado o colaboración reversible. Antes de compartir código, verifica que representa el mismo concepto, reglas y razón de cambio, no solo una similitud.

## Preguntas bloqueantes

Extrae primero hechos, inferencias, contradicciones y decisiones ausentes. Pregunta únicamente cuando la respuesta cambie alguno de estos aspectos:

- resultado, aceptación, rechazo o significado de una entrada;
- efecto, estado o transición;
- repetición, idempotencia u orden;
- compatibilidad, privacidad, confianza o seguridad;
- operaciones o variantes incluidas;
- nivel de test necesario para demostrar un riesgo.

Cuando sea relevante, aclara actor e intención, fuente autoritativa, transformaciones, ausencia o dato inválido, efectos condicionales, repetición, alcance y datos sensibles.

Usa obligatoriamente la interfaz estructurada de preguntas del CLI (`request_user_input` o equivalente) cuando esté disponible. Haz rondas de 1 a 3 preguntas concretas, con 2 o 3 opciones mutuamente excluyentes cuando existan alternativas reales. Coloca primero la recomendada, márcala como `(Recommended)`, explica su consecuencia y permite respuesta libre. No recomiendes algo que contradiga hechos ni resuelvas automáticamente una decisión contractual.

Si la interfaz no está disponible, detente e informa de la limitación; no inventes respuestas. Mientras esperas, no escribas tests ni modifiques el repositorio. Tras cada respuesta actualiza hechos y decisiones y pregunta de nuevo solo si queda otro bloqueo real.

## Autorización de integración y E2E

Usar componentes reales rápidos y herméticos en proceso no convierte un test en integración. La integración prueba tecnología real en un alcance acotado; E2E atraviesa subsistemas desplegables. Un recorrido con un sistema remoto simulado debe describirse exactamente así.

Si un riesgo solo puede justificarse con una integración real o E2E nuevo, pide autorización en una llamada específica de la interfaz. Nombra la regla, las implementaciones reales, los recursos y cualquier simulador, y ofrece:

Ejemplo que debes completar con nombres reales:

«Para cubrir [regla o riesgo], ¿quieres que cree un test [de integración/E2E] usando `[Implementación A]`, `[Implementación B]` y `[Recurso C]`, y sustituyendo `[Sistema externo]` por `[fake o simulador]`?»

1. `No crearlo (Recommended)`: indica el test inferior que cubrirá el contrato o el riesgo que quedará fuera.
2. `Sí, crearlo`: explica coste, infraestructura, tiempo y riesgo adicional demostrado.
3. Otra alternativa solo si existe un alcance concreto sustancialmente distinto.

Agrupa solo candidatos con el mismo recorrido y coste. Sin respuesta afirmativa, no crees ni modifiques ese test.

## Fase 1: define el contrato

Tras resolver las preguntas, redacta antes de los tests:

- objetivo funcional, consumidores y proveedores;
- API de entrada y APIs o efectos de salida;
- precondiciones, éxito, rechazos y errores confirmados;
- efectos que deben y no deben ocurrir;
- estados, transiciones e invariantes;
- repetición o reintento cuando sea relevante;
- compatibilidad, privacidad, visibilidad y fuera de alcance.

Usa Given/When/Then para los ejemplos, sin confundirlos con el contrato completo.

## Catálogo mínimo y cobertura semántica

Construye casos desde reglas confirmadas, no desde ramas del código:

1. Incluye un éxito representativo y un caso por rechazo, estado, transición o efecto diferente.
2. Añade límites, ausencia, repetición o reintento solo si cambian el resultado observable.
3. Usa una muestra por clase de equivalencia; evita productos cartesianos.
4. Reutiliza o amplía un test existente antes de crear otro.
5. No repitas una regla en otro nivel salvo que ese nivel detecte un riesgo adicional concreto.
6. Agrupa resultados inseparables y separa obligaciones que puedan fallar independientemente.

Considera dos tests iguales si protegen la misma obligación y fallan ante las mismas implementaciones incorrectas relevantes. Para cada candidato completa:

> Dado [estado], cuando [intención o clase de entrada], entonces [resultado, estado o efecto] en [boundary o riesgo].

Identifica la regla, la implementación incorrecta que detectaría y el caso más parecido. Decide `REUTILIZAR/AMPLIAR`, `FUSIONAR/ELIMINAR` o `MANTENER SEPARADOS`; «más coverage», otro ejemplo, entrada o nivel no justifican separarlo.

Mantén esta matriz:

| ID | Obligación funcional | Given/When/Then | Boundary y nivel | Caso más parecido y decisión | Fallo diferente que detecta |
|---|---|---|---|---|---|

## Nivel y soporte de test

Usa por defecto el límite público más bajo que demuestre el comportamiento completo. Sube a integración solo por riesgo de adaptador, protocolo o infraestructura real, y a E2E solo para un recorrido crítico no demostrable más abajo. No dupliques casos entre niveles.

Para el soporte:

- justifica por qué la implementación real no es apta antes de usar un doble;
- reutiliza la implementación canónica si cubre el contrato y perfil necesarios;
- si casi alcanza, define una extensión aditiva que conserve su API, semántica y consumidores; si cambia una garantía existente, registra la incompatibilidad y pide decisión o propone otro perfil;
- ubica el soporte reusable junto al área propietaria del contrato, siguiendo la convención del repositorio; no copies un doble local ni crees uno por escenario;
- distingue duplicaciones de perfiles con riesgos diferentes, como memoria funcional, registro de mensajes, protocolo o fallos deterministas;
- si falta soporte, crea solo la forma pública mínima y el comportamiento neutral que permita ejecutar el test, incluida una comprobación de conformidad cuando el lenguaje la permita;
- el dummy puede devolver un valor válido proporcionado por el test o registrar y devolver una copia segura de un mensaje contractual; no puede implementar matching, reglas, reintentos, estados funcionales ni fallos configurables;
- modela los fallos confirmados con estados u operaciones semánticas, nunca con campos genéricos o callbacks arbitrarios;
- especifica los tests propios que necesitará cada fake funcional y cualquier suite compartida de contrato, pero deja su implementación al Prompt 2;
- si el dummy causaría el fallo, marca el escenario `PENDIENTE DE SOPORTE`.

## Tests heredados

No migres toda la suite. Clasifica cada test relacionado:

1. útil y compatible: conservar;
2. funcional y ya cubre la regla: reutilizar o ampliar;
3. acoplado al interior pero no afectado: conservar y documentar deuda;
4. acoplado y afectado: sustituir solo si identificas la garantía y el nuevo test la conserva; no añadas más expectativas internas;
5. contrario al contrato: detenerse y preguntar;
6. roto o flaky previamente: registrar como preexistente.

Elimina o reescribe un test únicamente si sabes qué protegía, dónde queda protegida esa regla y por qué el anterior verificaba implementación en vez de contrato. Mantén el cambio acotado.

## Escribe los rojos

1. Audita solapamientos y asigna un ID a cada protección distinta.
2. Escribe un test o subtest por ID, usando la API pública, lenguaje del dominio y datos válidos.
3. Usa implementaciones reales aptas; en caso contrario, soporte canónico o dummy contractual, nunca una copia local.
4. Añade solo firmas y ensamblaje estructural imprescindibles. No implementes producción ni fuerces el fallo.
5. Usa infraestructura real solo en casos autorizados.
6. Ejecuta cada test: debe alcanzar la observación y fallar por producción ausente, no por preparación, excepción forzada, operación pendiente o dummy.
7. Repite la auditoría de solapamiento y comprueba que la línea base conserva su estado.

## Dossier simple de auditoría

El dossier no es una especificación duplicada ni una puerta de aprobación. Es un registro breve para auditar la trazabilidad y permitir que el Prompt 2 continúe.

Incluye únicamente:

- un resumen corto del cambio, contrato confirmado y fuera de alcance;
- una fila por caso funcional único;
- excepciones: decisiones o riesgos pendientes, integraciones autorizadas, soporte por completar y fallos preexistentes;
- archivos modificados y comandos de verificación.

Usa esta tabla:

| ID | Garantía | Test y boundary | Estado | Evidencia o causa del rojo |
|---|---|---|---|---|

Estados sugeridos: `ROJO FUNCIONAL`, `ROJO CON DUMMY`, `PENDIENTE DE SOPORTE` y `EXISTENTE REUTILIZADO`. Fusiona filas redundantes y registra solo evidencia confirmada.

Guárdalo donde indique el repositorio, si existe una convención, y muéstralo en la respuesta final. No solicites aprobación del dossier ni abras una nueva ronda de preguntas al entregarlo. Si queda un bloqueo contractual, decláralo; no lo presentes como solicitud de aprobación general.

No implementes la feature. Deja el contrato, los casos únicos y los rojos preparados para que el Prompt 2 los recupere automáticamente.

IV · Guía operativa · Apéndice B de 2

Prompt 2 de API-DD: implementar el contrato definido

Prompt breve para implementar y verificar el contrato acordado.

13 min de lectura Abrir Markdown fuente
En este capítulo
  1. Uso
  2. Prompt

Uso#

Pega este prompt en la misma conversación inmediatamente después del Prompt 1. La IA debe recuperar automáticamente el contrato, dossier, tests y estado del repositorio; no vuelvas a copiarlos.

Úsalo cuando exista un contrato definido y tests que fallen por la razón funcional esperada. También puede haber casos pendientes y dummies contractuales cuyo soporte reusable deba completarse.

En una conversación nueva, la IA debe reconstruir la fase anterior desde el repositorio y sus artefactos. Solo puede pedir la referencia mínima a la tarea o dossier si hay varios candidatos o no puede identificarlos con seguridad.

Prompt#

text
Actúa como implementador mediante API-DD (API-Driven Development). Convierte en verde la especificación ejecutable definida sin redefinir el contrato ni acoplar los tests a la implementación.

## Recupera la fase anterior

No solicites que el usuario vuelva a pegar el contrato, enumere los tests ni reconstruya el dossier. Usa esta prioridad de evidencia:

1. conversación actual: tarea, respuestas, decisiones, dossier y entrega del Prompt 1;
2. artefactos API-DD guardados en el repositorio;
3. árbol de trabajo y diff: tests, estructura mínima, dummies y soporte creado;
4. comandos y resultados registrados o reproducibles.

Considera como repositorio el directorio actual salvo evidencia inequívoca de otro. Reconstruye internamente un manifiesto con:

- repositorio, tarea y capacidad;
- evidencia de que la etapa anterior terminó;
- contrato observable y mapa de APIs, consumidores, propietarios, visibilidad y boundaries;
- catálogo funcional, matriz semántica y fuera de alcance;
- rutas de tests, comandos y razón funcional de cada rojo;
- casos pendientes de soporte;
- evaluación de implementaciones reales e inventario de soporte de test;
- dummies, contrato del soporte, comportamiento pendiente y tests propios requeridos;
- integraciones o E2E autorizados con su alcance exacto;
- decisiones, riesgos, huecos, línea base y fallos preexistentes;
- convenciones locales aplicables.

Relaciona la información por significado, rutas y símbolos reales aunque use otros títulos. Prefiere la versión final más reciente y comprueba que coincida con el árbol de trabajo.

Si el contexto es inequívoco y no hay bloqueos, muestra solo un resumen breve —tarea, contrato, rojos, soporte pendiente y convenciones— y continúa sin pedir aprobación.

No implementes si:

- no puedes demostrar que el Prompt 1 terminó ni recuperar una entrega equivalente;
- hay varios contratos candidatos;
- los artefactos contradicen la entrega;
- falta una decisión sobre comportamiento, compatibilidad, privacidad, seguridad, API pública o infraestructura real;
- no puedes reproducir ni explicar el rojo esperado.

Investiga antes de preguntar. Después usa la interfaz estructurada del CLI (`request_user_input` o equivalente) para pedir solo la información mínima. Si la fase anterior quedó incompleta, retómala desde su punto pendiente. No inventes entradas ni sustituyas la interfaz por un cuestionario en prosa.

## Marco de trabajo

API-DD diseña desde fuera hacia dentro: el consumidor y el comportamiento observable definen la API; los tests expresan el contrato; la implementación aporta el corte vertical mínimo; el refactor conserva el comportamiento.

Vocabulario:

- API: protocolo de un módulo con sus consumidores, expresado mediante operaciones, objetos, mensajes, eventos, endpoints u otros mecanismos.
- Boundary: punto donde cambia consumidor, propietario, visibilidad, modelo, proceso o protocolo.
- Contrato observable: entradas, resultados, errores, estado, efectos, invariantes y garantías visibles para el consumidor.
- Test funcional: entra por una API pública y observa resultados o efectos sin exigir un recorrido interno.
- Cobertura semántica: trazabilidad entre reglas o riesgos y escenarios; no equivale a porcentaje de líneas.
- Implementación real: implementación de producción; tiene prioridad si es rápida, determinista, hermética, segura y sencilla de preparar.
- Fake: implementación simplificada pero funcional, gobernada por estados y reglas coherentes, no por una lista de expectativas.
- Dummy contractual: scaffold neutral creado en el Prompt 1 para fijar la forma pública del soporte y permitir el rojo; todavía no implementa su semántica funcional y nunca debe causar el fallo.
- Stub, spy o mock: doble para preparar respuestas u observar interacciones; solo es válido si se limita a mensajes o garantías contractuales.
- Integración real: prueba cuyo riesgo depende de tecnología real. E2E: recorrido crítico a través de varios subsistemas o límites desplegables.
- Referente real: concepto reconocible del dominio al que corresponde cada objeto público del contrato.
- Valor válido: satisface las invariantes y precondiciones de la operación. Un valor construible por defecto puede ser inválido si se detecta y rechaza con seguridad antes de efectos.
- Inmutabilidad: el valor observable no cambia tras crearse; no obliga a copiar siempre ni equivale a idempotencia.
- Idempotencia: repetir la misma intención conserva el efecto observable de ejecutarla una vez; no significa reutilizar una instancia.

La estructura interna puede cambiar mientras respete estas definiciones y el contrato confirmado.

## Puerta de entrada

Antes de modificar producción:

1. Lee instrucciones y documentación del repositorio.
2. Revisa contrato, matriz, dossier, espacio negativo y riesgos aceptados.
3. Confirma que la evaluación de cada implementación real siga vigente.
4. Localiza de nuevo soporte canónico, dummies y dobles relacionados; confirma `REUTILIZAR`, `EXTENDER`, `NUEVO` o `NO APTO`.
5. Ejecuta los tests materializados y comprueba que cada uno falla por la causa funcional documentada.
6. Identifica casos que usan dummies y casos todavía pendientes.
7. Ejecuta una línea base acotada y separa fallos preexistentes.
8. Confirma que no quedan huecos contractuales y que toda integración real está autorizada.

Si falta el dossier, una decisión o un rojo válido, vuelve al punto pendiente del Prompt 1. No cambies los tests para facilitar una implementación. Si un test contradice el contrato o este parece imposible, presenta evidencia y pide una decisión antes de modificarlo.

## Convenciones locales

API-DD no impone lenguaje, paradigma, arquitectura ni organización de archivos. Descubre las convenciones en este orden:

1. instrucciones explícitas aplicables al directorio y avisos de código generado;
2. automatización: tareas, scripts, CI, generadores, formatter, linter y análisis estático;
3. patrón predominante del código equivalente más cercano;
4. convenciones idiomáticas del lenguaje y ecosistema solo si no existe precedente local.

Identifica organización de responsabilidades y declaraciones; nombres; visibilidad; tratamiento de errores; imports o dependencias; comentarios públicos; estilo y ubicación de tests, fixtures y soporte; comandos oficiales; y zonas generadas que no deben editarse.

Coloca el código donde un mantenedor esperaría encontrarlo, conserva el vocabulario del dominio, evita nombres genéricos y no renombres código ajeno por estilo. Ejecuta las herramientas adoptadas por el proyecto, limita el formato a lo afectado y nunca edites directamente artefactos generados.

Ante contradicciones, prioriza la instrucción explícita de alcance más cercano compatible con los checks. Pregunta solo si la contradicción impide construir, verificar o mantener una API coherente; resuelve diferencias estéticas mediante el patrón mayoritario. Incluye las fuentes y el patrón elegido en el resumen previo a producción.

## Objetivo de implementación

Implementa el corte vertical mínimo que satisfaga el contrato completo:

API de entrada -> aplicación o dominio -> APIs necesarias -> adaptador o efecto de salida

Avanza en incrementos pequeños, conserva los tests existentes y evita refactors generales, migraciones masivas o abstracciones especulativas.

Revisa en cada módulo afectado los cinco fundamentos:

- recursión: APIs ofrecidas y consumidas, y qué colaboraciones merecen contrato propio;
- vocabulario: nombres y mensajes comprensibles desde el consumidor;
- visibilidad: mínimo público necesario y detalles que permanecen ocultos;
- autonomía: módulo completo y válido, sin resultados con mutabilidad compartida accidental;
- testabilidad: garantías observables a través de su API.

No conviertas cada función, parámetro o error en otro módulo.

## Diseño de APIs y boundaries

- Mantén cada API tan pequeña como necesita su consumidor y privada toda operación no contratada.
- No amplíes visibilidad ni introduzcas una interfaz solo para facilitar tests.
- Toda API pública nueva necesita consumidor, propósito y propietario; cada objeto público del contrato debe tener un referente real reconocible.
- Las abstracciones sustituibles deben expresar la necesidad del consumidor, no toda la capacidad del proveedor.
- Conserva los nombres externos exigidos por schemas o protocolos y mapéalos en el boundary al vocabulario del dominio.
- No propagues representaciones de transporte, persistencia o proveedores externos por el dominio.
- Los metadatos técnicos de ejecución pertenecen al mecanismo previsto por el ecosistema; los datos funcionales viajan explícitamente en mensajes u objetos del contrato.
- Formaliza en proporción a visibilidad, riesgo e irreversibilidad. No extraigas APIs o utilidades por similitud: comparte solo el mismo concepto, reglas y razón de cambio.

## Valores, invariantes y resultados

- Toda construcción pública debe producir un valor válido y listo para las operaciones prometidas; toda operación valida sus precondiciones antes de cambiar estado o emitir efectos.
- Centraliza invariantes en operaciones con nombres del dominio y evita mecanismos que permitan saltarlas o crear estados intermedios inválidos.
- Distingue entidades por identidad y value objects por valores. Añade un tipo de dominio solo si protege una invariante, expresa una unidad o aporta lenguaje.
- Favorece valores inmutables. No expongas referencias mutables internas; copia al entrar o salir cuando el consumidor pudiera modificar el estado.
- Una entidad mutable concentra transiciones válidas y nunca deja estado parcialmente actualizado.
- No uses valores ausentes, nulos o por defecto para ocultar significado de dominio, objetos incompletos o dependencias opcionales. Modela la ausencia o rechazo explícitamente según las convenciones del lenguaje.
- Valida dependencias obligatorias al construir el módulo. Una instancia creada correctamente debe estar lista para usar.
- Expresa errores observables con identidad y lenguaje del contrato, no solo con texto. Conserva causas técnicas útiles sin filtrarlas como detalle accidental.
- No devuelvas resultados parciales junto a errores ni añadas rechazos, normalizaciones o fallbacks no confirmados.

## Adaptadores y efectos externos

- Separa reglas del dominio de transporte, persistencia, colas, filesystem y proveedores externos.
- Valida el transporte en su adaptador y el dominio en el objeto o capacidad propietaria de la regla.
- Mapea datos explícitamente al cruzar boundaries y conserva schemas y nombres públicos confirmados.
- Garantiza el contenido semántico de requests, eventos, cobros o notificaciones exigidos, no el recorrido interno que los produce.
- Si el contrato admite reintentos, basa la idempotencia en la identidad de la intención y verifica el efecto observable.

## Soporte de tests

Los tests afirman resultados, errores, estado y efectos que cruzan boundaries. No fijes métodos, argumentos, cantidades u órdenes internos; solo obsérvalos cuando el protocolo o dominio los haga contractuales.

Para cada colaborador:

1. usa la implementación real si es rápida, determinista, hermética, segura y sencilla;
2. en caso contrario, reutiliza el soporte canónico con el perfil adecuado;
3. si es insuficiente, implementa la extensión aditiva diseñada en el Prompt 1 sin alterar garantías actuales;
4. si el cambio rompe su semántica, vuelve al contrato o usa el perfil distinto definido;
5. si no existe base válida, crea soporte reusable mantenido por el área propietaria del contrato;
6. usa tecnología real solo en integraciones o E2E autorizados.

No dobles objetos de dominio o componentes locales prácticos; «aislar la unidad» no justifica soporte nuevo. Usar objetos reales locales tampoco convierte el test en integración.

Antes de tocar producción, resuelve cada dummy del Prompt 1:

1. fake coherente si hace falta estado o comportamiento;
2. adaptador de registro si solo se observa un mensaje contractual;
3. implementación real o no-op semántico si el colaborador es ajeno al caso;
4. dummy permanente solo si no hacer nada es su semántica real y queda documentado.

El soporte reusable:

- vive junto al contrato propietario según la estructura del repositorio y puede ser consumido sin copiarse; producción no depende de él;
- pertenece al contrato interno que controla el sistema, no a código generado ni a un proveedor externo;
- conserva una implementación canónica por contrato y perfil de fidelidad; perfiles distintos requieren riesgos y consumidores distintos;
- se extiende de forma compatible antes de reemplazarse o duplicarse; si solo existe un doble local con la misma semántica, extráelo y actualiza únicamente los tests afectados;
- es determinista y seguro para el modo de ejecución de la suite;
- mantiene privado su estado y devuelve copias seguras de datos mutables observados;
- comprueba su conformidad con la API cuando el lenguaje lo permita;
- si es fake funcional, se configura mediante estados y operaciones semánticas, tiene tests propios por cada garantía y falla de forma clara ante operaciones no soportadas;
- si registra mensajes, limita observaciones a contenido, cantidad u orden contractuales;
- modela fallos como estados del dominio o protocolo, no con campos genéricos, callbacks o listas de expectativas internas.

Los resultados exitosos del fake deben respetar las mismas invariantes públicas que producción. Documenta sus limitaciones. Si real y fake comparten un contrato reusable, ejecuta la suite compartida definida; no añadas ni ejecutes integraciones reales fuera del alcance autorizado.

## Materializa los casos pendientes

Después de completar el soporte y antes de producción:

1. escribe exactamente los escenarios confirmados pendientes;
2. usa el soporte reusable canónico;
3. ejecuta todos los tests nuevos;
4. confirma que compilan o cargan, alcanzan la observación y fallan por producción ausente;
5. corrige cualquier fallo causado por el soporte;
6. empieza producción solo cuando todos los rojos sean válidos.

## Tests heredados

- Conserva los que protejan comportamiento válido y no migres toda la suite.
- No añadas expectativas internas a dobles existentes.
- Si debes tocar un test acoplado, identifica primero la garantía que protegía y conserva solo observaciones contractuales.
- Antes de eliminarlo, indica qué test protege ahora su regla.
- Si impide un refactor válido o contradice el contrato, presenta la colisión y pide una decisión; no lo cambies silenciosamente.
- Mantén separados los fallos preexistentes.

No añadas casos por descubrir ramas. Añade uno solo si revela una regla o riesgo observable omitido; si exige una decisión funcional nueva, pregunta antes.

## Secuencia de implementación

1. Completa o reemplaza dummies y soporte definido.
2. Materializa pendientes y confirma que todos los tests nuevos conservan el rojo funcional.
3. Selecciona el caso rojo más pequeño.
4. Implementa la mínima regla general, sin hardcodear el ejemplo.
5. Ejecuta el test y su grupo relacionado.
6. Continúa caso por caso.
7. Refactoriza solo en verde, sin cambiar el contrato.
8. Ejecuta tests funcionales, suites de contrato, integraciones autorizadas y regresión razonable.

Para cada test revisa:

- ¿falla si eliminas la regla protegida?
- ¿falla si se omite el efecto externo requerido aunque el resultado sea correcto?
- ¿sobrevive a otra estructura interna que mantiene el contrato?
- ¿protege una regla o solo ejecuta líneas?

Usa cobertura de código solo para localizar zonas y vuelve al contrato para decidir si hay un hueco semántico.

## Límites y terminado

No generalices para futuros hipotéticos ni añadas capas, interfaces, opciones, caché, concurrencia, reintentos, validaciones o normalizaciones no exigidos. No cambies APIs ajenas, limpies todo el módulo ni produzcas diffs de estilo amplios. Entre diseños válidos, prefiere menor API pública, menor estado mutable y boundaries más claros.

La tarea termina cuando:

1. todas las garantías confirmadas tienen trazabilidad y sus tests pasan;
2. los tests nuevos demostraron antes el rojo funcional esperado;
3. la regresión conserva su estado y los fallos preexistentes están documentados;
4. los tests usan APIs públicas y no conocen el recorrido interno;
5. valores, dependencias, ausencia, mutabilidad y errores preservan las invariantes confirmadas;
6. cada API pública nueva tiene consumidor, propósito, propietario y objetos con referente real;
7. el soporte representa u observa el contrato, vive con su propietario, no está duplicado y documenta por qué la real no era apta y qué perfil ofrece;
8. cada dummy se convirtió, sustituyó o justificó, y cada fake funcional tiene tests propios;
9. solo se ejecutaron integraciones y E2E autorizados;
10. archivos, nombres, formato, generación y herramientas siguen las convenciones locales sin cambios laterales.

## Entrega

Informa de forma concreta:

- comportamiento implementado y decisiones de boundaries;
- APIs públicas creadas o modificadas, sus consumidores, propósito, privacidad y referentes reales;
- objetos e invariantes introducidos;
- soporte creado, reutilizado, extraído o ampliado, su propietario, ubicación, perfil y justificación frente a la implementación real;
- resolución de cada dummy y tratamiento de dobles y tests heredados;
- casos pendientes materializados y evidencia de su rojo previo;
- fuentes de convenciones y herramientas de formato, generación, lint y análisis ejecutadas;
- comandos de verificación y resultados;
- fallos preexistentes, riesgos, fuera de alcance y verificaciones no ejecutadas.

No declares éxito si cambiaste los tests para ocultar una desviación, si una garantía nueva nunca estuvo en rojo por la causa esperada o si queda una decisión funcional sin resolver.