En este artículo
La promesa rota — y por qué la culpa no es de la IA
El coste real de la deuda arquitectónica medido en tokens
Por qué DDD y los LLMs se entienden tan bien
Scaffolding on the fly — el cambio de mentalidad
Un ejemplo real: comando CLI + orquestación con IA
El overengineering ha cambiado de cara
Para los perfiles con experiencia: el argumento verdadero
Coda
1. La promesa rota — y por qué la culpa no es de la IA
Hay un patrón que se repite en casi todos los equipos que dicen que la IA "no les sirve para programar": le dan un fichero de 800 líneas, sin tipos explícitos, con tres responsabilidades mezcladas y efectos secundarios desperdigados por toda la función. Y se sorprenden cuando el output no es lo que esperaban.
La IA no es mala generando código. Es muy buena, de hecho. Pero un LLM razona sobre un espacio de contexto acotado. Si ese espacio está lleno de ruido —dependencias implícitas, acoplamiento invisible, convenciones no escritas— el modelo hace lo que puede. Que no es poco. Pero tampoco es lo que necesitas.
Un LLM y un developer nuevo en el equipo tienen exactamente el mismo problema: los dos necesitan entender el sistema para poder operar sobre él. El código mal diseñado arruina a los dos.
La diferencia es que el developer nuevo puede preguntar. El LLM infiere. Y la inferencia sobre código acoplado produce código aún más acoplado.
2. El coste real medido en tokens
Esto no es metáfora. Es literal. Cuando le das contexto basura a un modelo, estás pagando en dos monedas distintas.
La primera es directa: tokens. Cada fichero extra que el modelo tiene que procesar para entender el contexto es coste. Cada iteración para corregir un output incorrecto es coste. Con arquitectura limpia, el modelo necesita un módulo. Con arquitectura acoplada, necesita cuarenta.
La segunda es más cara: calidad. El output refleja fielmente la calidad del input. Código acoplado produce código acoplado. No porque la IA sea mala, sino porque está aprendiendo el patrón que le das.
La deuda arquitectónica que antes absorbía el senior que "conocía el sistema" ahora la paga el modelo en tokens. Coste directo, medible, en calidad y en dinero.
La trampa invisible: No es que la IA genere mal código. Es que tu arquitectura amplifica sus limitaciones —y las tuyas. Un módulo bien delimitado entra entero en el contexto. Un módulo mal delimitado arrastra a otros veinte.
3. Por qué DDD y los LLMs se entienden tan bien
Domain-Driven Design lleva décadas resolviendo exactamente este problema para los humanos. Resulta que la IA agradece exactamente lo mismo, y no es coincidencia.
Ambos —un desarrollador nuevo y un LLM— necesitan razonar sobre un espacio acotado para dar su mejor output. DDD te da las herramientas para crear esos espacios:
Bajo acoplamiento = contexto pequeño. Un módulo bien delimitado entra en el contexto del modelo sin arrastrar dependencias implícitas de otros treinta ficheros.
Contratos explícitos = menos inferencia. Interfaces claras, casos de uso nombrados, eventos tipados. El modelo no adivina: lee.
Ubiquitous language = semántica que el modelo reconoce. Cuando tu código habla el mismo idioma que el dominio, el modelo puede razonar sobre él con mucho menos contexto auxiliar.
La arquitectura hexagonal lo hace especialmente visible. Cuando el dominio no sabe nada de infraestructura, puedes darle al modelo solo el domain/ y el application/ de un módulo. Sin Prisma, sin Express, sin configuración. Solo lógica de negocio sobre contratos explícitos.
4. Scaffolding on the fly — el cambio de mentalidad
Aquí está el punto que más me importa defender, y el que menos se discute.
Si una tarea es repetitiva, no es trabajo para la IA. Es trabajo para un script.
La IA orquesta. El CLI ejecuta.
¿Por qué importa esta distinción? Porque ensuciamos el contexto del LLM con operaciones mecánicas que un comando puede hacer en milisegundos. Generar un fichero con una estructura conocida no es una tarea creativa. Es una tarea de plantilla. Y tratarla como si fuera creativa tiene dos costes:
Coste 1 — Tokens: cada token que gasta el modelo describiendo la estructura de un fichero es un token que no está usando para razonar sobre la lógica de negocio.
Coste 2 — Inconsistencia: la IA puede generar variaciones sutiles del patrón entre módulos. Un comando CLI siempre produce exactamente la misma estructura. La consistencia que los humanos pierden cuando hay prisa, un script no la pierde.
El modelo correcto de trabajo:
Developer escribe:
npx create-module billing create-invoice
│
▼
CLI Script genera la estructura vacía pero tipada
│
▼
LLM recibe 1 módulo con contratos explícitos
y rellena solo la lógica de negocio
│
▼
Output correcto, consistente y revisable
El CLI no ensucia el contexto. El LLM solo trabaja sobre lógica.
5. Un ejemplo real en TypeScript
Supongamos que tienes un módulo de facturación. El comando:
npx create-module billing create-invoice
genera esta estructura automáticamente:
src/modules/billing/
├── domain/
│ ├── Invoice.ts ← aggregate root generado
│ ├── InvoiceEvents.ts ← domain events tipados
│ └── InvoiceRepository.ts ← puerto (interface)
├── application/
│ └── CreateInvoice.ts ← use case vacío, tipado
└── infrastructure/
└── PrismaInvoiceRepo.ts ← adapter generado
Lo que el CLI genera es estructura vacía pero correctamente tipada. Ahora le pasas ese módulo al LLM con un prompt simple:
Implementa el use case CreateInvoice.
Las reglas de negocio son:
- Una factura requiere al menos una línea
- El total no puede ser negativo
- Al crearse, emite el evento InvoiceDraft
Usa los tipos ya definidos en el aggregate.
El modelo no tiene que inferir la estructura. No tiene que adivinar cómo nombrar los ficheros. No tiene que decidir dónde poner la lógica. Solo tiene que implementar las reglas de negocio sobre un contrato ya definido.
Y lo hace perfectamente:
import { Invoice, InvoiceLine } from '../domain/Invoice';
import { InvoiceRepository } from '../domain/InvoiceRepository';
import { InvoiceDraft } from '../domain/InvoiceEvents';
interface CreateInvoiceCommand {
customerId: string;
lines: InvoiceLine[];
}
export class CreateInvoice {
constructor(private readonly repo: InvoiceRepository) {}
async execute(cmd: CreateInvoiceCommand): Promise<Invoice> {
if (cmd.lines.length === 0) {
throw new Error('Una factura requiere al menos una línea');
}
const total = cmd.lines.reduce((sum, l) => sum + l.amount, 0);
if (total < 0) {
throw new Error('El total de la factura no puede ser negativo');
}
const invoice = Invoice.create(cmd);
invoice.addEvent(new InvoiceDraft(invoice.id));
await this.repo.save(invoice);
return invoice;
}
}
Correcto a la primera. Sin iteraciones. Sin tener que corregir la estructura. El LLM no tuvo que inferir nada: leyó un contrato explícito y rellenó la lógica de negocio.
6. El overengineering ha cambiado de cara
Durante años, DDD tuvo un problema de fricción de entrada. No conceptual —cualquier developer senior entiende bounded contexts—. El problema era de costes variables:
Para un CRUD de facturas: crear a mano el aggregate, el usecase, el repositorio, los tipos, los eventos del dominio, el mapper... 4-6 ficheros, 45 minutos, y eso con experiencia. Para un developer menos familiarizado con el patrón: el doble de tiempo y con errores de estructura.
Ese era el argumento real contra DDD en proyectos medianos. No que fuera difícil de entender, sino que el coste de repetirlo a mano no compensaba frente a un service con la lógica dentro que tardaba diez minutos.
Ese argumento ya no existe.
Con un CLI que conoce tu arquitectura, esos 6 ficheros se generan en 2 segundos siguiendo exactamente el patrón del módulo anterior. El coste fijo de diseñar la arquitectura se aplica una vez. El coste variable de cada módulo es prácticamente cero.
El overengineering de antes era montar DDD para un proyecto que no lo necesitaba, porque el setup era tan caro que no compensaba. Ahora el overengineering es no aplicar estructura cuando tienes las herramientas para hacerlo sin coste.
La dificultad de DDD nunca fue entenderlo. Fue el coste de repetirlo a mano. Eso ya no es un problema.
7. Para los perfiles con experiencia: el argumento verdadero
Este artículo no es para quien no conoce DDD. Si no lo conoces, la IA te lo puede explicar ahora mismo: esa barrera ya no existe.
Este artículo es para quien ya lo conoce y aun así no lo aplica sistemáticamente.
Y la razón habitual no es técnica. Es de ROI. El argumento "para un CRUD sencillo no voy a montar esto a mano" era completamente válido. Funcionaba. La deuda se pagaba más tarde, pero el corto plazo ganaba.
Ahora hay una segunda dimensión que importa más: la velocidad de entrega con calidad. Con scaffolding más LLM sobre arquitectura limpia, no eliges entre velocidad y estructura. Tienes las dos.
El senior que antes justificaba un service monolítico porque "era más rápido" ahora puede tener la misma velocidad con la estructura correcta. Y el developer junior del equipo puede seguir el patrón sin entenderlo al 100%, porque el CLI le da el andamiaje correcto desde el primer momento.
Es el argumento de negocio que siempre faltó para justificar DDD más allá de proyectos grandes: entregas más rápidas, mejor output, y un retorno real y medible para el negocio.
El código que escribes hoy con IA dice más de tu arquitectura que de la IA.
Si el output es malo, la pregunta correcta no es "¿qué prompt usaste?" Es "¿cómo está organizado tu código?"
La IA no amplifica tu velocidad. Amplifica tu arquitectura. Si es buena, el output es extraordinario. Si es mala, el output escala el problema.
DDD siempre fue la respuesta correcta para código que tiene que sobrevivir. Ahora el coste de aplicarlo correctamente es casi cero. No hay excusa técnica.
Solo de criterio. Y el criterio —saber cuándo aplicar estructura y cuándo no— sigue siendo lo que separa a un engineer senior de alguien que escribe código que funciona hoy y duele mañana.
¿Quieres ver cómo construir el CLI de scaffolding desde cero? En el próximo artículo montamos el script completo en TypeScript, con templates por módulo y soporte para distintos bounded contexts.