Arquitectura de código generado con IA: la Casa Winchester
Hace tres semanas abrí un repo que llevaba cuatro meses construyendo casi entero con agentes. Buscaba una función para formatear fechas.
Encontré tres.
formatDate en src/utils/date.ts. toDisplayDate en src/lib/format.ts. Y humanDate en src/shared/helpers/dates.ts. Las tres hacían lo mismo. Las tres estaban bien escritas. Las tres tenían tests. Ninguna estaba rota.
Ese es el problema de la arquitectura de código generado con IA: no se rompe. Se desparrama. La arquitectura de código generado con IA es la forma que toma un repositorio cuando la mayor parte del código la escribe un agente y no una persona: cada pieza es correcta por separado, pero nadie sostiene el conjunto en la cabeza. El CI sigue verde mientras el repositorio se convierte en otra cosa.
Se ha hablado mucho del "Deep Blue" — el término que se acuñó en el podcast Oxide and Friends, con crédito principal a Adam Leventhal y con Simon Willison en ese mismo episodio, que después lo difundió en su blog: esa mezcla de desánimo y vértigo existencial que sienten muchos developers ante los LLM. Este post no va de eso.
Va de lo que le está pasando a tu repositorio ahora mismo, mientras tú lo miras.
Qué es la Casa Winchester y por qué se parece a tu repo
La Casa Winchester, en software, es el modelo que describe un repositorio construido a base de decisiones correctas tomadas de una en una, sin que nadie sostenga el plano general: cada pieza está bien hecha y el conjunto no tiene sentido. El nombre viene de una mansión real, y encaja con lo que produce hoy un agente de codificación.
Sarah Winchester construyó durante décadas una mansión en San José, California. Sin arquitecto director y sin plano general. Cada obra se hacía bien: buena carpintería, buenos materiales, habitaciones perfectamente terminadas.
El resultado tiene escaleras que acaban en el techo y puertas que abren al vacío.
Ninguna decisión individual fue estúpida. Falló el conjunto. Nadie tenía en la cabeza la casa entera, así que nunca llegó a ser una casa: fue la suma de muchas obras correctas.
Abre tu repo generado con agentes y busca ese patrón. No busques bugs. Busca escaleras que no llevan a ningún sitio.
El tercer modelo: ni catedral ni bazar
En 1997 Eric S. Raymond presentó La catedral y el bazar, el ensayo que definió los dos modelos con los que llevamos treinta años pensando el software. La catedral: planificada, cerrada, con arquitectos que controlan la forma. El bazar: abierto, caótico en la superficie, ordenado por muchos ojos mirando.
Drew Breunig propuso en marzo de 2026 un tercero: la Casa Winchester. Su tesis es incómoda y precisa: "AI is making code cheap and kicking off a new era filled with idiosyncratic, sprawling, cobbled-together software".
La clave no es que la IA escriba mal. Es esta otra frase suya: "Feedback hasn't gotten cheaper; the 'eyeballs' that guided the software developed by the bazaar haven't caught up to AI". El código bajó de precio. Todo lo demás —incluidos los ojos que Raymond puso en el centro del bazar— cuesta exactamente lo mismo que antes.
Y remata: "There is only one source of feedback that moves at the speed of AI-generated code: yourself".
Ahí está el problema entero. La revisión de un compañero, el diseño de una API, la discusión sobre si esto merece ser un módulo nuevo: todo eso sigue a velocidad humana. Lo único que se aceleró fue la producción.
La entropía arquitectónica es la degradación progresiva de la forma de un repositorio —duplicación semántica, abstracciones sin uso, patrones incoherentes— sin que aparezca un solo fallo funcional. No es un problema de calidad de código: es un problema de caudal de feedback. Tu repo se desparrama porque el código llega más rápido de lo que nadie puede juzgarlo.
Una catedral no se defiende sola: necesita a alguien mirando. El bazar tampoco, porque funcionaba gracias a que se leía despacio. Cuando el que escribe se acelera un orden de magnitud y el que lee sigue exactamente igual, no te queda ni catedral ni bazar. Te queda una casa con escaleras al techo.
Las 4 señales de que tu arquitectura de código generado con IA ya es una Casa Winchester
Esto no se detecta leyendo. Se detecta midiendo. Cuatro señales, cada una con su forma de verla hoy mismo.
| # | Señal | Cómo la mides |
|---|---|---|
| 1 | La misma utilidad con tres nombres distintos | Censo de exports con rg + jscpd |
| 2 | Abstracciones con un solo consumidor | Grafo de madge + in-degree |
| 3 | Código muerto que nadie borra | knip --reporter compact |
| 4 | Patrones incoherentes entre sesiones | Conteo de librerías rivales cruzado con git log |
1. La misma utilidad con tres nombres distintos
Es la señal madre. El agente no encontró tu helper porque no estaba en su contexto, así que escribió otro. Correcto, con tests, y duplicado.
# Censo de funciones exportadas: los duplicados semánticos saltan a la vista
rg -o --no-filename 39;export (?:async )?(?:function|const) (\w+)39; -r 39;$139; src \
| sort | uniq -c | sort -rn | head -30
# Duplicación literal de bloques
npx jscpd src --min-lines 5 --min-tokens 60 --reporters console
El censo de nombres rinde más de lo que parece. Cuando ves formatDate, toDisplayDate y humanDate seguidos en la misma lista, el diagnóstico es inmediato.
2. Abstracciones con un solo consumidor
El agente te construye un UserRepository, un NotificationService y un PaymentGateway porque son buenas prácticas. Luego resulta que cada uno se usa exactamente desde un sitio.
Una abstracción con un consumidor no es arquitectura. Es una capa de indirección que te cobra peaje cada vez que lees el código.
npx madge --extensions ts,tsx --json src > deps.json
Si tu repo usa path aliases (@/…), añade --ts-config tsconfig.json o madge no resolverá esos imports y el grafo saldrá incompleto — con lo que el in-degree te mentirá.
Y un script de veinte líneas que cuenta cuántos módulos importan a cada módulo:
// scripts/in-degree.mjs
import { readFileSync } from 39;node:fs39;
const graph = JSON.parse(readFileSync(39;deps.json39;, 39;utf839;))
const inDegree = new Map(Object.keys(graph).map((file) => [file, 0]))
// Los tests no cuentan como consumidor: si el único importador de un módulo
// es su test, ese módulo tiene cero consumidores de producción, no uno.
for (const [file, deps] of Object.entries(graph)) {
if (file.includes(39;.test.39;) || file.includes(39;.spec.39;)) continue
for (const dep of deps) {
inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1)
}
}
const suspects = [...inDegree]
.filter(([file, count]) => count === 1 && !file.includes(39;.test.39;))
.map(([file]) => file)
.sort()
console.log(`Módulos con un único consumidor: ${suspects.length}`)
console.log(suspects.join(39;\n39;))
Ejecútalo con node scripts/in-degree.mjs. Esa lista es tu deuda de indirección con nombres y apellidos.
3. Código muerto que nadie borra
Un agente borra cuando se lo pides. Nunca por iniciativa propia, porque borrar es arriesgado y su incentivo es que la tarea pase. Así que el código viejo se queda ahí, acumulándose y ensuciando el contexto de la siguiente sesión.
npx knip --reporter compact
knip te da ficheros, exports y dependencias que nadie usa. Apunta el número de hoy en algún sitio del repo. Si dentro de un mes ha subido, ya tienes tu métrica de entropía.
4. Patrones incoherentes entre sesiones
Esta es la más silenciosa. El módulo que escribiste en junio usa fetch a pelo. El de julio usa TanStack Query. El de agosto se trajo axios porque el agente decidió que era lo estándar.
for p in "axios" "fetch(" "@tanstack/react-query" "HttpClient"; do
printf "%-24s %s\n" "$p" "$(rg -l --fixed-strings "$p" src | wc -l)"
done
Si más de una fila devuelve un número mayor que cero, tienes dos maneras de hacer lo mismo conviviendo en el repo. Cruza el resultado con git log --diff-filter=A --format='%ad' --date=short -- <fichero> y verás que cada patrón corresponde a una tanda distinta de trabajo.
Escaleras que dan al techo: por qué se degrada la arquitectura de código generado con IA
Tres causas, y ninguna es "la IA escribe mal".
El agente empieza cada sesión con amnesia parcial. No lee tu repo entero: lee lo que le cabe en la ventana y lo que sabe buscar — el mismo mecanismo que provoca el context drift. Si tu helper de fechas no aparece en esa muestra, para el agente no existe. Y lo que no existe, se escribe.
Escribir se volvió más barato que entender. Esto siempre fue verdad, pero antes tecleabas tú, y el coste de escribir 200 líneas te empujaba a reutilizar. Esa fricción desapareció. Hoy reutilizar exige buscar, leer y decidir; crear exige una frase. El camino de menor resistencia lleva al código nuevo.
El CI que ya tienes no ve nada de esto. Ningún test se pone rojo porque tengas tres formas de formatear fechas. Ningún linter falla porque una capa tenga un solo consumidor. Tus tests miden comportamiento; la entropía es un problema de forma. Es un punto ciego distinto del que conté en los 5 fallos del código generado por IA que un code review no puede ver: allí el diff esconde el fallo, aquí no hay fallo que esconder. Por eso el repo se degrada durante meses con el pipeline en verde.
Aquí mucha gente responde con más proceso humano: más revisión, más reuniones de arquitectura. No funciona, y Breunig ya te dijo por qué: tú eres el único feedback que va a la velocidad del código, y tú no escalas.
La respuesta tiene que ir a la misma velocidad que el problema. Es decir: automática.
Guías y sensores: el plano que le falta al agente
Birgitta Böckeler publicó el 2 de abril de 2026 en martinfowler.com un artículo sobre harness engineering con la formulación más clara que he leído del asunto. El harness engineering es la disciplina de diseñar todo lo que rodea al modelo —contexto, herramientas, verificaciones y bucles de corrección— para que el agente necesite menos supervisión humana. Su punto de partida: Agent = Model + Harness. El modelo no lo controlas. El arnés agéntico sí, y es tuyo entero.
El arnés tiene dos mitades.
Guías (feedforward). Fijan expectativas antes de que el agente actúe. Suben la probabilidad de que acierte a la primera. Documentación de arquitectura, convenciones, instrucciones de arranque.
Sensores (feedback). Observan la salida después y permiten autocorrección. Böckeler insiste en un detalle que casi todo el mundo se salta: los sensores deben estar "optimised for LLM consumption" — mensajes que le digan al agente qué hacer, no solo qué falló.
Y cada mitad puede ser computacional (determinista y rápida: tipos, lint, tests, build; milisegundos y resultado fiable) o inferencial (semántica: revisión por LLM, LLM-as-judge; más lenta, más cara y no determinista).
| Guías (antes de actuar) | Sensores (después de actuar) | |
|---|---|---|
| Computacional (determinista, ms) | Tipos, esquemas, plantillas, AGENTS.md con el mapa del repo |
tsc, ESLint, tests, knip, jscpd |
| Inferencial (semántico, lento y caro) | How-tos y ejemplos escritos para consumo del LLM | Revisión por LLM en el PR, LLM-as-judge |
La conclusión que saco de su artículo es la parte que importa: ninguna mitad vale sola. Solo sensores y tienes un agente que repite siempre los mismos errores. Solo guías y tienes un agente que memoriza reglas sin enterarse nunca de si funcionaron.
La guía: tu fichero de instrucciones no es un style guide
El error más común en AGENTS.md o CLAUDE.md es llenarlo de preferencias de formato. Eso ya lo hace Prettier.
La guía debe contener lo que el agente no puede deducir mirando un fichero suelto: dónde vive cada cosa, quién puede importar a quién y qué existe ya.
MAPA DEL REPO — no crees carpetas de primer nivel sin preguntar
- `src/domain/` — tipos y reglas de negocio. No importa NADA de `src/infra/`.
- `src/infra/` — HTTP, DB, colas. Implementa los puertos de `src/domain/`.
- `src/app/` — casos de uso. Único sitio que orquesta domain + infra.
- `src/shared/` — fuente ÚNICA de fechas, dinero y formateo de strings.
ANTES DE ESCRIBIR CUALQUIER UTILIDAD NUEVA
Ejecuta esto y lee la salida. Si algo cubre el 80% del caso, extiéndelo:
rg -n "export (async )?function" src/shared
REGLAS DURAS
- Una sola librería de fetching: `@tanstack/react-query`. Nada de `axios`.
- No crees una abstracción con menos de dos consumidores reales.
- Si un código sobra, bórralo. No lo comentes ni lo marques `@deprecated`.
DEFINICIÓN DE "HE TERMINADO"
pnpm agent:check
Ese último bloque es la bisagra entre la guía y los sensores. Si tu definición de "terminado" es "el agente dijo que estaba", no tienes arnés: tienes fe.
Si quieres un AGENTS.md ya escrito para copiar y adaptar, lo tienes entero en Revisión por Contrato, un ebook gratuito de 30 páginas donde desarrollo el contrato, el carril y el veredicto que le pones a un agente antes de dejarle tocar el repo.
Fijar la forma antes de que exista el código es el mismo músculo que entrenas con Spec-Driven Development. Si quieres el método completo, lo desarrollo entero en el libro Spec Driven Development.
Los sensores computacionales: que el agente se corrija solo
Un único comando que el agente pueda ejecutar sin pedirte permiso:
{
"scripts": {
"typecheck": "tsc --noEmit",
"lint": "eslint . --max-warnings 0",
"test": "vitest run",
"dead": "knip --reporter compact",
"dupes": "jscpd src --min-tokens 60 --threshold 1 --reporters console,threshold",
"agent:check": "pnpm typecheck && pnpm lint && pnpm test && pnpm dead && pnpm dupes"
}
}
knip y jscpd son los dos que faltan en casi todos los repos, y son justo los que detectan entropía en vez de bugs. jscpd con --threshold 1 sale con código 1 si la duplicación pasa del 1%, pero solo si añades el reporter threshold: el flag por sí solo no cambia el código de salida. Con los dos juntos, "hay algo duplicado" pasa de ser un texto en consola a una señal que el agente lee y sobre la que puede actuar.
El sensor que más me ha servido es otro: convertir la dirección de dependencias en una regla de lint cuyo mensaje explique el arreglo.
// eslint.config.js
export default [
{
files: [39;src/domain/**/*.ts39;],
rules: {
39;no-restricted-imports39;: [39;error39;, {
patterns: [{
group: [39;**/infra/**39;, 39;axios39;, 39;node:fs39;],
message:
39;domain/ no puede importar de infra/. Define un puerto (interfaz) en 39; +
39;src/domain/ports/, impleméntalo en src/infra/ e inyéctalo desde el 39; +
39;caso de uso en src/app/. No muevas el fichero: mueve la dependencia.39;,
}],
}],
},
},
]
Fíjate en el mensaje. No dice "import restringido". Dice qué hacer, en qué orden y con qué carpetas. El agente lo lee, lo aplica y no te interrumpe. Eso es un sensor optimizado para consumo de LLM.
Dos detalles que te ahorran un rato: export default en eslint.config.js exige "type": "module" en el package.json —o renombrar el fichero a eslint.config.mjs—, y si quieres bloquear también los import type de TypeScript necesitas @typescript-eslint/no-restricted-imports en vez de la regla core. La regla en sí no es más que Clean Architecture convertida en algo que el agente puede ejecutar.
La misma lógica aplicada a la ejecución del agente la desarrollo en el post sobre guardrails para agentes con acceso a terminal y base de datos, y llevada a testear al propio agente en el de test harness para agentes de IA.
Los sensores inferenciales: para lo que ningún linter ve
Hay preguntas que ninguna regla determinista responde. ¿Esta función duplica algo que ya existe con otro nombre? ¿Esta abstracción tiene razón de ser? ¿Este módulo sigue el patrón del resto del repo?
Eso es trabajo de un revisor LLM en el PR, con un prompt que pregunte por coherencia y no por corrección. La corrección ya la cubren los tipos y los tests. Lo que te falta es alguien que mire la casa entera. Cómo montarlo lo cuento en el post de agentic code review.
Es más lento y no determinista, sí. Por eso va en el PR y no en cada guardado.
Qué revisar en tu repo esta semana
Cinco cosas, por orden. Ninguna te lleva más de una tarde.
- Saca tu línea base. Ejecuta
npx knipynpx jscpd srchoy. Apunta los dos números en un fichero del repo con la fecha. Sin línea base no sabes si mejoras o empeoras. - Abre tu
AGENTS.mdoCLAUDE.md. Si lo que hay dentro es un style guide, reescríbelo como mapa: dónde vive cada cosa y quién importa a quién. - Añade
agent:checkalpackage.jsony ponlo en la guía como definición literal de "he terminado". - Convierte una regla de arquitectura en lint, con mensaje accionable. Una sola. La dirección de dependencias es la que más rinde.
- Aplica la regla de los dos consumidores. Coge la salida del script de in-degree, elige una abstracción que solo se use una vez y bórrala metiendo el código donde se usa. Vas a respirar mejor.
La conclusión después de cuatro meses generando código con agentes es esta: el agente no tiene criterio arquitectónico, tiene contexto. Si tu criterio no está escrito en la guía y no lo verifica un sensor, para el agente no existe. Y lo que no existe se reinventa cada sesión con un nombre distinto.
Sarah Winchester tenía dinero, buenos carpinteros y décadas por delante. Le faltó el plano. Tú tienes agentes que escriben más rápido de lo que nadie puede leer. El plano ya no es opcional.
Si quieres ver el flujo completo funcionando — guías, sensores y agentes dentro de un arnés que aguanta — lo monto paso a paso en el curso Construye con IA: de la idea al producto con Claude Code. Y si prefieres trabajarlo sobre proyectos reales, eso pasa en Dominicode Labs.
Preguntas frecuentes
¿Qué es la entropía arquitectónica en código generado con IA?
Es la degradación de la forma de un repositorio sin que aparezca ningún fallo funcional: tres funciones que hacen lo mismo con nombres distintos, abstracciones con un único consumidor, código muerto que nadie borra y patrones que cambian según la sesión en que se escribió cada módulo. Se distingue de un bug en que ningún test la detecta: los tests miden comportamiento y la entropía es un problema de estructura. Se mide con herramientas de duplicación (jscpd), de código muerto (knip) y de grafo de dependencias (madge).
¿Esto no es simplemente deuda técnica de toda la vida?
Misma familia, otra dinámica. La deuda técnica clásica la generas tú y la sientes al escribirla: sabes que estás tomando un atajo. Esta la genera un agente que hace las cosas bien en cada tarea individual, así que nunca hay atajo consciente ni sensación de deuda. Se acumula sin fricción y sin señal. Por eso hay que medirla, no intuirla.
Si trabajo solo, ¿esto me afecta igual?
Más. El modelo de la Casa Winchester describe precisamente proyectos personales donde el bucle de feedback se colapsa dentro de una sola cabeza. Sin nadie que revise, tu única defensa son los sensores automáticos. Un equipo grande al menos tiene pull requests con humanos delante; tú tienes exactamente lo que hayas automatizado.
¿Cuánto debe ocupar el fichero de instrucciones del agente?
Corto y denso. Si pasa de una pantalla y media, el agente empieza a ignorar partes. Prioriza el mapa del repo, tres o cuatro reglas duras y el comando de verificación. Todo lo que se pueda comprobar con un linter, sácalo del fichero y ponlo como sensor: ahí sí se cumple siempre.
¿Los sensores inferenciales sustituyen al code review humano?
No, lo reordenan. El revisor LLM absorbe el volumen y filtra lo obvio: duplicación, incoherencia de patrones, abstracciones sin uso. Tú te quedas con lo que exige criterio de producto y de negocio. Si intentas leer cada línea que produce un agente, vuelves al cuello de botella del que veníamos.
Mi repo ya es una Casa Winchester. ¿Reescribo?
No. Las reescrituras completas con agentes fallan por la misma razón que falló el repo original: mucho código y poco feedback. Congela primero — mete los sensores y la guía para que la entropía deje de crecer. Después ataca una zona por semana, empezando por las utilidades duplicadas: son las más baratas de unificar y las que más contexto sucio limpian para las sesiones siguientes.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
¿Te resultó útil este artículo?
Compártelo con tu comunidad y ayuda a otros desarrolladores.
