Arquitectura de Software

Domain-Driven Design: Inteligencia Artificial, cómo programar con agentes de IA sin perder el criterio arquitectónico

Aprende a orientar agentes de IA con reglas del negocio, límites de contexto y criterios verificables. Un caso en TypeScript muestra cómo preparar instrucciones, proteger Order, diseñar pruebas y revisar cambios sin delegar el criterio arquitectónico.

Cargando tus opciones de aprendizaje…

Una petición como «crea un endpoint para confirmar pedidos» parece suficiente para empezar. Un agente puede generar una ruta Express, actualizar una columna y devolver un 200. El código compila, pero también permite confirmar pedidos vacíos o reactivar uno cancelado.

El problema aparece antes de elegir una biblioteca: la solicitud describía una operación técnica y omitía su significado en el negocio. Un resultado convincente puede convertir esos vacíos en reglas inventadas.

En este artículo vamos a trabajar con una tarea pequeña del contexto Sales. El objetivo es aprender a entregar contexto útil, limitar decisiones, revisar cambios y reunir evidencia. Cursor, OpenAI Codex y Claude Code son ejemplos de herramientas con las que puede aplicarse este proceso; los mecanismos concretos de configuración dependen del producto y de su versión.

La IA necesita decisiones del negocio, además de archivos

Un agente de programación puede leer código, proponer cambios y, según sus permisos, ejecutar herramientas. Eso amplía su capacidad de trabajo, pero no lo convierte en experto del negocio ni demuestra que sus conclusiones sean correctas.

Considera esta solicitud inicial:

Crea POST /orders/:id/confirm con TypeScript y Express.
Usa buenas prácticas y agrega pruebas.

«Buenas prácticas» no determina qué significa confirmar. Tampoco especifica qué hacer ante un pedido vacío, un estado incompatible o una segunda solicitud. Es posible producir esta implementación incorrecta, aquí representada mediante pseudocódigo TypeScript:

// Contraejemplo: acceso a persistencia deliberadamente esquemático.
app.post("/orders/:id/confirm", async (request, response) => {
  await database.orders.update(request.params.id, {
    status: "CONFIRMED",
  });
  response.sendStatus(200);
});

Una prueba que solo compruebe la respuesta 200 reforzaría el error. El mismo agente habría inventado el comportamiento y construido una prueba que lo acepta.

El contexto que falta debe incluir el significado de la operación, sus restricciones y la evidencia esperada. La documentación oficial de Codex también recomienda expresar objetivo, contexto, restricciones y condiciones de finalización al formular tareas. Esa estructura resulta útil como disciplina general, aunque no elimina la necesidad de revisar. Buenas prácticas de Codex.

Un contrato de trabajo para Sales

Para nuestro ejemplo, el negocio ha confirmado estas reglas:

  • Un pedido puede estar en DRAFT, CONFIRMED o CANCELLED.
  • Solo un pedido DRAFT con productos puede confirmarse.
  • Confirmar uno ya confirmado debe rechazarse.
  • Confirmar uno cancelado debe rechazarse.
  • Las modificaciones se realizan mediante métodos del dominio.

No añadiremos comprobaciones de pago ni reservas de inventario: no forman parte de este encargo. Tampoco deduciremos cuándo se puede cancelar o editar un pedido.

Esto importa incluso dentro de una misma colección de ejemplos. Una implementación anterior podría haber tratado una segunda confirmación como una operación idempotente, es decir, sin efectos adicionales y con resultado exitoso. Aquí el acuerdo es distinto: debe rechazarse. Copiar una decisión anterior sin comprobar su vigencia sería una desviación del dominio.

Separar hechos, decisiones y preguntas

Un documento breve puede distinguir lo siguiente:

Hechos confirmados
- Sales es responsable del ciclo de vida de Order.
- La confirmación cambia DRAFT a CONFIRMED.
- Un pedido vacío no puede confirmarse.

Decisiones del encargo
- La repetición se rechaza, incluso si el pedido ya está confirmado.
- El agregado protege las reglas.
- El caso de uso coordina carga y persistencia.

Preguntas pendientes
- ¿Qué respuesta HTTP corresponde a cada rechazo?
- ¿Cómo se detectan actualizaciones concurrentes?
- ¿Quién puede ejecutar la operación?
- ¿La operación debe producir algún efecto adicional?

Las preguntas no obligan a detener todo el trabajo. Es posible implementar y probar la transición del dominio mientras se resuelve el contrato HTTP. En cambio, no se debe publicar un endpoint de producción suponiendo una autorización inexistente o ignorando un conflicto de concurrencia.

La IA puede organizar preguntas y señalar contradicciones. La validación de las respuestas corresponde a las personas responsables del negocio y del sistema.

Preparar contexto pequeño, verificable y vigente

No hace falta entregar toda la documentación de la empresa. Conviene proporcionar fuentes concretas y pedir que el agente contraste su contenido con la implementación real.

Esta estructura es una propuesta didáctica, no el resultado de inspeccionar un repositorio:

AGENTS.md
docs/
  domain/
    sales-glossary.md
    sales-boundaries.md
    order-confirmation.md
  decisions/
    0007-confirmation-rejects-repetition.md
src/
  sales/
    domain/
    application/
    infrastructure/
    presentation/

El glosario explica los términos relevantes. El documento de límites indica quién posee cada regla y qué otros contextos pueden consultarse mediante contratos. El acuerdo de confirmación contiene estados, ejemplos y preguntas pendientes. Un ADR registra una decisión arquitectónica y su justificación; no necesita convertirse en una enciclopedia.

Un archivo de instrucciones podría contener:

## Trabajo en Sales

- Lee docs/domain/order-confirmation.md antes de modificar confirmación.
- Conserva las reglas en Order.
- ConfirmOrderUseCase coordina el contrato OrderRepository.
- El dominio no importa Express, ORM ni clientes externos.
- Inspecciona los scripts existentes antes de elegir verificaciones.
- Conserva los cambios ajenos y limita el diff al encargo.
- No agregues dependencias, migraciones o servicios sin autorización.
- Informa preguntas pendientes y pruebas que no pudiste ejecutar.

Codex admite instrucciones mediante archivos AGENTS.md; su documentación describe cómo descubre instrucciones globales y del proyecto. En otras herramientas debe verificarse el mecanismo equivalente, sin asumir que leerán automáticamente cualquier archivo Markdown. Instrucciones de proyecto con AGENTS.md.

Mantén estas instrucciones cortas y enlaza las fuentes del negocio. Dos documentos que discrepan sobre la confirmación deben resolverse explícitamente: el agente no debería elegir silenciosamente el que le resulte más cómodo.

Un prompt que convierte el dominio en criterios de aceptación

Esta plantilla puede adaptarse a un repositorio real:

Actúa como un ingeniero de software especializado en
Domain-Driven Design, Clean Architecture y TypeScript.

Contexto
Trabajamos en el Bounded Context Sales de un comercio electrónico.
Order es el Aggregate Root responsable de sus transiciones de estado.

Fuentes
Lee las instrucciones locales, el glosario de Sales,
el acuerdo de confirmación y las decisiones relacionadas.
Contrasta esos documentos con la estructura y las pruebas existentes.

Reglas confirmadas
- Estados: DRAFT, CONFIRMED y CANCELLED.
- Solo DRAFT puede confirmarse.
- Un pedido sin productos no puede confirmarse.
- Confirmar un pedido CONFIRMED debe rechazarse.
- Confirmar un pedido CANCELLED debe rechazarse.
- Las modificaciones se realizan mediante métodos del dominio.

Restricciones
- Las reglas permanecen dentro de Order.
- ConfirmOrderUseCase coordina la operación.
- La persistencia utiliza OrderRepository.
- Express gestiona responsabilidades HTTP.
- El dominio no depende de Express, ORM ni bases de datos.
- No agregues servicios ni abstracciones sin necesidad demostrada.

Proceso
1. Inspecciona estructura, estado de Git, instrucciones y pruebas.
2. Identifica archivos afectados y convenciones que reutilizarás.
3. Presenta un plan proporcional al cambio.
4. Separa reglas confirmadas, supuestos y decisiones pendientes.
5. Implementa únicamente el comportamiento acordado.
6. Agrega pruebas para cada regla y sus casos de rechazo.
7. Ejecuta las verificaciones existentes pertinentes.
8. Revisa el diff y entrega resultados, riesgos y limitaciones.

No inventes decisiones de autorización, concurrencia, pagos
o inventario. Solicita validación cuando una decisión pendiente
sea necesaria para implementar ese comportamiento.
No publiques ni despliegues sin autorización.

El valor de esta plantilla está en sus condiciones observables. Se puede comprobar si el dominio importa Express o si una prueba rechaza una confirmación repetida. «Usa DDD correctamente» no ofrece esa precisión.

Inspeccionar antes de planificar

En una aplicación existente, pedir una estructura ideal desde cero puede generar duplicación. Quizá ya hay un agregado Order, una convención de errores y un contrato de repositorio que solo requieren un cambio pequeño.

Antes de editar, el agente debería identificar:

  1. Dónde está implementada hoy la confirmación.
  2. Qué código y documentos describen sus reglas.
  3. Qué pruebas cubren el comportamiento y cuáles faltan.
  4. Qué archivos tienen cambios previos que debe preservar.
  5. Qué verificaciones existen y cuáles requieren infraestructura.

Un plan razonable podría limitarse a modificar Order, adaptar un caso de uso existente y añadir pruebas. Crear un bus de eventos, un contenedor de dependencias y tres servicios adicionales no se justifica por el tamaño del prompt.

Si existe contradicción entre una prueba que acepta repetición y el nuevo acuerdo que la rechaza, el plan debe mostrar esa diferencia. Cambiar la prueba sin explicar por qué ocultaría una modificación del contrato.

Una implementación pequeña para revisar decisiones

El siguiente archivo TypeScript es una unidad didáctica completa del comportamiento de confirmación. La reconstitución recibe datos internos previamente validados; no constituye un validador de JSON ni un endpoint público.

Usamos campos privados de JavaScript para impedir la escritura directa de estado en ejecución. La lista de productos se copia al reconstituir el agregado. No implementamos edición de productos ni cancelación porque sus reglas no fueron definidas.

// Order.ts
export type OrderStatus = "DRAFT" | "CONFIRMED" | "CANCELLED";

export type OrderSnapshot = Readonly<{
  id: string;
  status: OrderStatus;
  productIds: readonly string[];
}>;

export class OrderRuleViolation extends Error {
  constructor(
    readonly code: "ORDER_NOT_DRAFT" | "ORDER_EMPTY",
  ) {
    super(code);
    this.name = "OrderRuleViolation";
  }
}

export class Order {
  #status: OrderStatus;
  readonly #productIds: readonly string[];

  private constructor(
    readonly id: string,
    status: OrderStatus,
    productIds: readonly string[],
  ) {
    this.#status = status;
    this.#productIds = [...productIds];
  }

  static reconstitute(snapshot: OrderSnapshot): Order {
    return new Order(
      snapshot.id,
      snapshot.status,
      snapshot.productIds,
    );
  }

  get status(): OrderStatus {
    return this.#status;
  }

  confirm(): void {
    if (this.#status !== "DRAFT") {
      throw new OrderRuleViolation("ORDER_NOT_DRAFT");
    }

    if (this.#productIds.length === 0) {
      throw new OrderRuleViolation("ORDER_EMPTY");
    }

    this.#status = "CONFIRMED";
  }
}

El método expresa la regla y modifica el estado únicamente después de validarla. El orden de comprobación define qué error aparece cuando coinciden varias condiciones; no debe convertirse en un contrato público sin acordarlo. Las pruebas de abajo aíslan cada regla para no depender de esa precedencia.

Coordinar sin duplicar las reglas

Este segundo archivo muestra el contrato de persistencia y el caso de uso:

// ConfirmOrderUseCase.ts
import { Order } from "./Order.js";

export interface OrderRepository {
  findById(id: string): Promise<Order | null>;
  save(order: Order): Promise<void>;
}

export class OrderNotFound extends Error {
  constructor() {
    super("ORDER_NOT_FOUND");
    this.name = "OrderNotFound";
  }
}

export class ConfirmOrderUseCase {
  constructor(private readonly orders: OrderRepository) {}

  async execute(orderId: string): Promise<void> {
    const order = await this.orders.findById(orderId);

    if (order === null) {
      throw new OrderNotFound();
    }

    order.confirm();
    await this.orders.save(order);
  }
}

El caso de uso no repite la condición DRAFT ni cuenta productos. El controlador Express debe validar la entrada y traducir resultados al contrato HTTP acordado, además de integrarse con autenticación y autorización existentes. No mostramos una ruta ejecutable porque esas decisiones siguen pendientes.

Este contrato de repositorio tampoco resuelve concurrencia. Dos solicitudes podrían cargar el mismo estado antes de persistir. Antes de producción hay que definir cómo se detectan conflictos, por ejemplo mediante una versión y una escritura condicional, si esa es la estrategia aprobada. Una prueba unitaria del agregado no demuestra esa propiedad.

Pedir pruebas que puedan contradecir la implementación

Las pruebas deben derivarse del acuerdo del negocio. Para este caso esperamos un éxito y tres rechazos independientes:

// Order.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import {
  Order,
  OrderRuleViolation,
  type OrderStatus,
} from "./Order.js";

function fixture(
  status: OrderStatus,
  productIds: readonly string[] = ["product-1"],
): Order {
  return Order.reconstitute({
    id: "order-1",
    status,
    productIds,
  });
}

function hasCode(
  code: "ORDER_NOT_DRAFT" | "ORDER_EMPTY",
): (error: unknown) => boolean {
  return (error) =>
    error instanceof OrderRuleViolation && error.code === code;
}

test("confirma un pedido DRAFT con productos", () => {
  const order = fixture("DRAFT");
  order.confirm();
  assert.equal(order.status, "CONFIRMED");
});

test("rechaza un pedido vacío y conserva DRAFT", () => {
  const order = fixture("DRAFT", []);
  assert.throws(() => order.confirm(), hasCode("ORDER_EMPTY"));
  assert.equal(order.status, "DRAFT");
});

test("rechaza una segunda confirmación", () => {
  const order = fixture("DRAFT");
  order.confirm();
  assert.throws(() => order.confirm(), hasCode("ORDER_NOT_DRAFT"));
  assert.equal(order.status, "CONFIRMED");
});

test("rechaza la confirmación de un pedido cancelado", () => {
  const order = fixture("CANCELLED");
  assert.throws(() => order.confirm(), hasCode("ORDER_NOT_DRAFT"));
  assert.equal(order.status, "CANCELLED");
});

test("una lista externa no puede vaciar el agregado", () => {
  const products = ["product-1"];
  const order = fixture("DRAFT", products);
  products.length = 0;
  order.confirm();
  assert.equal(order.status, "CONFIRMED");
});

Estas son pruebas propuestas, no una suite ejecutada como parte de este artículo. En un proyecto hay que ajustarlas al compilador y al ejecutor existentes.

Para el caso de uso, también conviene verificar que no se llama a save cuando confirm falla o cuando el pedido no existe. Un repositorio falso debe registrar llamadas o trabajar con copias: si guarda la misma referencia mutable que entrega, una prueba de persistencia podría pasar aunque save nunca se invoque.

Pregunta al agente qué modificación incorrecta detectaría cada prueba. Si elimina la comprobación de productos y todo continúa verde, falta cobertura. Evita confundir cantidad de pruebas con capacidad para detectar errores.

Revisar el diff con preguntas concretas

Una segunda instrucción puede orientar una revisión independiente:

Revisa el diff sin modificar archivos.

Contrasta cada cambio con el acuerdo de confirmación de Sales.
Busca reglas inventadas, validaciones duplicadas, acceso directo
al estado, imports prohibidos y cambios ajenos al encargo.

Para cada hallazgo, indica archivo, evidencia, consecuencia
y una corrección mínima. Distingue defectos demostrables de
preguntas del negocio. Comprueba que las pruebas rechazan
pedido vacío, cancelado y repetición.

No declares que una prueba pasó si no existe salida de ejecución.
Indica qué riesgos requieren integración o validación humana.

Presta atención a modelos anémicos: clases que solo almacenan datos mientras toda la decisión queda en el controlador. También a una fachada con nombre correcto que sigue delegando la regla a SQL, o a una comprobación de stock añadida «por seguridad» sin respaldo del negocio.

Que el agente cite una regla no prueba que el código la cumpla. Hay que seguir el camino real de ejecución y comprobar sus dependencias.

Especialistas y herramientas con alcance limitado

Para una modificación pequeña, un solo agente y una revisión humana pueden ser suficientes. En una tarea mayor se pueden separar responsabilidades: una revisión del modelo, implementación backend, pruebas y análisis de seguridad cuando el cambio toque controles sensibles.

La división necesita objetivos y propiedad de archivos. Dos agentes editando simultáneamente Order pueden sobrescribir decisiones o construir soluciones incompatibles. Un revisor puede trabajar en solo lectura, y quien coordina debe resolver contradicciones contra las fuentes del negocio.

Varios agentes pueden compartir la misma suposición equivocada. Su número no garantiza calidad. La evidencia útil sigue siendo el acuerdo validado, un diff comprensible y verificaciones pertinentes.

Los archivos de instrucciones tampoco crean aislamiento. Los permisos reales, las credenciales y las herramientas disponibles determinan qué puede ejecutar un agente. Usa entornos de prueba, acceso mínimo y autorizaciones explícitas para despliegues, migraciones o acciones externas.

No incluyas secretos, datos personales ni registros productivos completos en los prompts. Los comentarios del código, documentos recuperados y respuestas de herramientas son fuentes de información; no deberían adquirir autoridad para cambiar el encargo o autorizar una operación.

Aplicar el proceso a otros negocios

Plataforma educativa: completar no significa aprobar

Un agente recibe «marca el curso como completado». Puede deducir que basta con abrir la última lección. Sin embargo, el negocio quizá distingue lectura, entrega de actividades y aprobación de una evaluación.

El trabajo útil comienza identificando qué transición se pide y quién puede ejecutarla. Hasta validarlo, esas alternativas son preguntas. Si el contexto Learning Progress es dueño del progreso, el agente no debe copiar sus tablas dentro de Billing para decidir descuentos.

Logística: entregar no significa cerrar una incidencia

«Cierra los envíos entregados» puede mezclar la entrega física con una reclamación pendiente. La IA puede ayudar a ordenar escenarios, pero necesita conocer si ambos procesos pertenecen al mismo modelo y qué evidencia acredita cada hecho.

Aquí la revisión debe buscar términos confundidos y efectos colaterales. Un cambio técnicamente pequeño que cierre reclamaciones por accidente puede ser más relevante que una refactorización extensa.

En los tres escenarios, el esfuerzo de revisión debe considerar valor estratégico, impacto y riesgo. Una regla que diferencia al negocio merece atención del experto correspondiente; una integración genérica también puede requerir controles rigurosos si maneja identidad o datos sensibles.

Un ciclo repetible con evidencia

El flujo completo puede representarse así:

Objetivo y fuentes del negocio
          |
Inspección del repositorio
          |
Preguntas y decisiones validadas
          |
Plan y cambio acotado
          |
Pruebas, tipos y controles pertinentes
          |
Revisión del diff y validación humana
          |
Integración y despliegue autorizados

El informe final debe separar qué cambió, qué comandos se ejecutaron, qué resultados produjeron y qué quedó sin validar. «No pude ejecutar integración porque falta la base de pruebas» es información útil. «Todo funciona» sin evidencia no lo es.

Una tercera plantilla ayuda a cerrar el trabajo:

Resume el cambio con evidencia:
- comportamiento implementado y reglas que lo respaldan;
- archivos afectados y decisiones tomadas;
- verificaciones ejecutadas y su resultado real;
- verificaciones pendientes y motivo;
- riesgos de seguridad, concurrencia o compatibilidad;
- decisiones que requieren validación humana.

No conviertas propuestas en hechos ni omitas fallos.

Ejercicios para practicar

  1. Un agente añade paymentConfirmed a Order y exige su valor para confirmar. El encargo solo contiene las reglas de este artículo. ¿Qué debes revisar antes de aceptar ese cambio?
  2. Todas las pruebas devuelven 200 desde un controlador simulado. ¿Qué evidencia falta para confiar en la confirmación?
  3. Dos agentes deben trabajar en el cambio: uno implementa y otro revisa. ¿Cómo repartirías responsabilidades para evitar conflictos y una revisión circular?

Soluciones razonadas

Ejercicio 1. La regla de pago no está confirmada. Pide identificar su fuente y valida la necesidad con el negocio. También revisa quién posee la información de pago y cómo debería consultarse. No aceptes una dependencia entre contextos solo porque suena razonable.

Ejercicio 2. Faltan pruebas de las transiciones del agregado y de los rechazos, coordinación de persistencia y recorrido HTTP real según su contrato. Además, autorización y concurrencia requieren comprobaciones específicas. Un mock que siempre responde 200 no demuestra ninguna de esas propiedades.

Ejercicio 3. Asigna al implementador archivos concretos y criterios del dominio. El revisor trabaja en solo lectura contra esos mismos criterios y busca contraejemplos, sin limitarse al resumen del implementador. La coordinación humana resuelve discrepancias; las pruebas aportan evidencia independiente del texto generado.

El criterio sigue formando parte del trabajo

La pregunta planteada al comienzo de la colección era por qué importa el dominio al programar con IA. Ahora puede responderse mediante una práctica concreta: entregar conocimiento verificable, transformar reglas en comportamiento observable y revisar lo generado contra decisiones explícitas.

No necesitas un catálogo de agentes ni una arquitectura nueva para cada tarea. Necesitas saber qué cambio estás autorizando, qué decisiones siguen abiertas y qué evidencia permite aceptarlo. El siguiente artículo llevará estas piezas a un proyecto integrador; aquí hemos construido el proceso para trabajar sobre él con responsabilidad.

Cargando tus opciones de aprendizaje…

Cargando conversación…

Sigue aprendiendo