¿Quién tomó esta decisión?

Construimos en dos meses un producto que habría llevado un año. Cuando lo revisamos, un cambio chico tocaba 80 archivos. La IA no escribió mal código: tomó decisiones de diseño que nadie revisó.

Construimos un producto que, al ritmo de antes, habría tardado un año en empezar a mostrarse. Lo tuvimos en dos meses. Funcionaba, se veía bien, y cada feature que se pedía aparecía.

Después tocó evaluar un cambio chico. Había que tocar 80 archivos.

No porque el cambio fuera grande, sino porque el código no tenía estructura. Era una gran masa que había crecido con un solo criterio: que el feature exista. Había código muerto que quedaba de un cambio al siguiente. Había stores desperdigados: a veces se usaba uno, otras veces se había creado otro que se acoplaba al anterior pero se comportaba distinto. Y un feature central, que se puede resolver con varias tecnologías de naturaleza muy diferente, no tenía ninguna abstracción que separara qué hace de cómo lo hace. Nada impedía que se violaran las reglas de dependencia, y se violaban.

Y había tests por todos lados. Miles. Muchos probaban cosas como que una clase de CSS estuviera en el HTML, y se iban a romper apenas quisiéramos cambiar la interfaz. Otros existían porque el ciclo de TDD, aplicado sin criterio, obligaba a corregirlos y ampliarlos con cada cambio. Más que una red de seguridad, eran un peaje.

Mirando ese código, la pregunta que me quedó no fue “¿quién escribió esto?”. Fue otra: ¿quién tomó estas decisiones?

Nadie. O, mejor dicho, el modelo, feature por feature, sin que nadie las revisara.

⚡ La velocidad es real

Conviene empezar por acá, porque es fácil leer lo anterior como una queja contra la IA. No lo es.

Dos meses contra un año no es marketing. La IA escribe código rápido, y la mayoría de las veces lo escribe bien. Hay algo de romanticismo en los que llevamos años en esto, y me incluyo, cuando nos resistimos a aceptar que un agente puede escribir todo nuestro código. Puede.

Los datos acompañan. El reporte DORA 2025 de Google encontró que, por primera vez, la adopción de IA se asocia con más throughput: los equipos entregan más. Pero en el mismo reporte hay otro dato que suele quedar en segundo plano: la adopción de IA sigue asociada con menos estabilidad en producción. El tiempo que se ahorra escribiendo se va en auditar, verificar y corregir.

La conclusión de DORA es que la IA funciona como un amplificador. Multiplica lo que ya tenés. Si hay criterio, lo multiplica. Si no lo hay, también.

El problema no es quién escribe el código. Es quién decide.

🔍 Lo que no se ve en la demo

Cada feature que un agente implementa trae decisiones implícitas. Dónde vive el estado. Qué módulo depende de cuál. Si reutilizar lo que existe o crear algo nuevo. Si una tecnología se usa directo o detrás de una interfaz.

Nadie le pidió al agente que tomara esas decisiones. Las tomó porque tenía que tomarlas para cerrar la tarea. Y un agente optimiza para cerrar la tarea.

GitClear analizó 623 millones de cambios de código entre 2023 y 2026, y lo resumió mejor que yo:

“The headline is not ‘AI writes bad code.’ It is that today’s default AI workflow is incentivized to deliver atomic code (a happy path, a passing test, a closed ticket) while quietly taxing the invisible and the deferred.”

Los números de ese mismo estudio describen casi exactamente el producto del que hablaba:

SeñalCambio
Refactoring (% de líneas modificadas)21% en 2022 → 3,8% en 2026
Bloques de código duplicados+81% desde 2023
Llamadas entre archivos (reutilización)−35% desde 2023
Construcciones que ocultan errores+47%

Los stores desperdigados salen de ahí. Para un agente, crear un store nuevo es más barato que entender el que ya existe. Lo hace cien veces, y cada una tiene sentido por separado. Juntas, son los 80 archivos de un cambio chico.

Y no las estamos leyendo. En la encuesta State of Code 2026 de Sonar, el 96% de los developers dice que no confía del todo en el código que genera la IA. Solo el 48% lo verifica siempre antes de commitear. Desconfiamos, pero aceptamos igual.

Addy Osmani le puso nombre a lo que se acumula: comprehension debt, deuda de comprensión. Es la distancia entre cuánto código hay en tu sistema y cuánto entiende de verdad algún humano. La deuda técnica avisa: builds lentos, módulos que da miedo tocar. La deuda de comprensión no avisa. Te deja avanzar con confianza hasta el día en que un cambio chico toca 80 archivos.

🧩 El feature sin abstracción

El caso más claro fue el del feature central. Lo simplifico con un ejemplo que no es el código real, pero tiene la misma forma. Supongamos que guardar un borrador puede ir a la nube o quedar en local, según el contexto.

Así queda cuando nadie decide la abstracción:

// Cada consumidor conoce las tecnologías concretas
import { uploadToS3 } from '../infra/s3';
import { saveToIndexedDb } from '../infra/idb';

export async function saveDraft(draft: Draft, online: boolean): Promise<void> {
  if (online) {
    await uploadToS3(`drafts/${draft.id}.json`, JSON.stringify(draft));
  } else {
    await saveToIndexedDb('drafts', draft);
  }
}

Nada de esto está mal por sí solo. El problema aparece cuando esa función existe en diez lugares, cada uno con su variante, y agregar una tercera tecnología implica encontrarlos todos.

Así queda cuando alguien decide que el dominio define el comportamiento y la infraestructura lo implementa:

// El dominio define qué necesita
export interface DraftStorage {
  save(draft: Draft): Promise<void>;
  load(id: DraftId): Promise<Draft | null>;
}

// La infraestructura decide cómo
export class CloudDraftStorage implements DraftStorage {
  // ...
}

export class LocalDraftStorage implements DraftStorage {
  // ...
}

Un agente puede escribir cualquiera de las dos versiones. Si nadie le dice que la segunda es la regla, casi siempre escribe la primera, porque es el camino más corto para cerrar la tarea.

Y decírselo en un prompt no alcanza. Una regla escrita en un markdown, el agente la puede ignorar. Un check que falla, no:

// .dependency-cruiser.cjs
module.exports = {
  forbidden: [
    {
      name: 'domain-does-not-know-infra',
      severity: 'error',
      from: { path: '^src/domain' },
      to: { path: '^src/infra' },
    },
  ],
};

Con esa regla en CI, el atajo deja de ser el camino más corto.

🧪 Miles de tests, poca confianza

Con los tests pasó algo parecido, y es más difícil de ver porque parece una virtud. Un proyecto con miles de tests suena a un proyecto seguro.

Pero la mayoría de los tests que encontramos probaban detalles de implementación:

// Prueba que el HTML tiene una clase de CSS
it('renders the save button', () => {
  const { container } = render(<Editor />);
  expect(container.innerHTML).toContain('btn-primary');
});

Ese test no protege nada que le importe a un usuario. Lo único que garantiza es que, el día que cambie el diseño del botón, alguien va a tener que arreglarlo. Multiplicalo por mil y cada cambio de interfaz arrastra una corrección masiva de tests que no estaban probando comportamiento.

El mismo caso, probando lo que importa:

// Prueba que el usuario puede guardar un borrador
it('saves the draft when the user clicks save', async () => {
  const saved: Draft[] = [];
  const storage: DraftStorage = {
    save: async (draft) => { saved.push(draft); },
    load: async () => null,
  };

  render(<Editor storage={storage} />);
  await userEvent.click(screen.getByRole('button', { name: /save/i }));

  expect(saved).toHaveLength(1);
});

Este test sobrevive a cualquier rediseño y se rompe solo si guardar deja de funcionar. Y fijate que existe gracias a la abstracción de la sección anterior: sin DraftStorage, no habría nada que reemplazar en el test.

No es culpa de TDD. Es TDD sin criterio: el agente aplica la regla “primero el test” a cada detalle, incluido el color de un botón, porque nadie le dijo qué comportamiento vale la pena proteger. La cantidad de tests se vuelve una métrica de vanidad, y cada cambio paga el costo.

🏭 No todo software es igual

Nada de esto importa demasiado en un prototipo. Una prueba de concepto existe para validar una idea, y si la idea sirve, se tira y se construye bien. Ahí el vibe coding es exactamente la herramienta correcta.

Prototipo o PoCProducto con 100.000 usuarios diarios
Para qué existeValidar una ideaSostener el cambio durante años
Vida esperadaSemanasAños
Costo de un mal diseñoTirarlo y empezar de nuevoCada feature cuesta más que la anterior
Qué delegarCasi todoLa implementación, dentro de límites definidos
Qué revisarQue funcioneQué decidió el agente para que funcione

El riesgo real está en el medio: cuando el prototipo se convierte en producto sin que nadie lo decida. Eso fue lo que nos pasó. El código de los primeros días, escrito para mostrar algo rápido, se volvió la base sobre la que se construyó todo lo demás.

🧭 El presupuesto de decisiones

La forma más útil que encontré de pensarlo es separar las decisiones que no delego de las que sí.

Las que no delego:

  • El modelo de datos y dónde vive el estado. Cuántos stores hay, quién es dueño de cada dato.
  • Los límites entre módulos y la dirección de las dependencias. Qué puede importar a qué.
  • Las abstracciones de los features centrales. Qué es dominio y qué es infraestructura.
  • Los contratos públicos. APIs, eventos, esquemas que otros consumen.
  • Las dependencias nuevas. Cada paquete que entra es una decisión con consecuencias.
  • La seguridad y el manejo de errores. Donde los atajos no se ven hasta que es tarde.
  • Qué se testea. Qué comportamiento vale la pena proteger, y qué es un detalle que puede cambiar.

Las que sí delego:

  • La implementación dentro de esos límites.
  • Escribir los tests, una vez que decidí qué comportamiento prueban.
  • Los refactors mecánicos, con un objetivo claro.
  • El boilerplate.

La IA sigue escribiendo la mayor parte del código. Lo que cambia es que las decisiones que lo estructuran las tomo yo, antes.

🛠️ Cómo lo hago hoy

No es un método cerrado. Es lo que me está funcionando.

1. Diseño antes del prompt. Antes de pedir código escribo una especificación, después un plan, y recién después se implementa. La revisión más importante no es la del diff, es la del plan. Ahí cambiar de rumbo cuesta un párrafo, no 80 archivos.

2. Reglas escritas donde el agente las lee. Las decisiones de arquitectura viven en el CLAUDE.md y en skills del proyecto, no en mi cabeza. Si una regla no está escrita, para el agente no existe.

3. Guardrails que fallan. Tipos estrictos, reglas de lint, reglas de dependencias, todo en CI. Un markdown se puede ignorar. Un build roto, no.

4. Review en dos capas. Un agente revisor, en mi caso Copilot sobre las PRs, encuentra bugs. Pero la pregunta que ningún revisor automático se hace es la importante: ¿qué decidió este cambio que yo no pedí? Un store nuevo, una dependencia nueva, una abstracción que no existía. Esa parte la reviso yo.

5. Pedirle al agente que declare sus decisiones. Antes de implementar, le pido que liste lo que va a decidir por su cuenta:

Before implementing, list every design decision this change needs
that is not already in the plan: new files, new state, new
dependencies, new abstractions. Wait for my approval.

Casi siempre aparece algo que no esperaba. Mejor verlo en una lista que en un diff de 40 archivos.

6. PRs chicas. Un diff que se puede leer entero es un diff que alguien lee.

🐢 Más lento a propósito

Todo esto hace que vaya más lento. Es a propósito.

No lo digo por nostalgia. Dos meses para tener un producto es un resultado enorme. Pero la cuenta completa no es “dos meses contra un año”. Es dos meses, más lo que cuesta después cada cambio sobre una base que nadie diseñó. Tal vez cuatro meses con criterio hubieran sido más rápidos que dos sin él.

Simon Willison propuso un nombre para esta forma de trabajar: vibe engineering. Usar agentes para acelerar, pero siendo responsable de cada línea que entra a producción. Tests, planificación, documentación, review. Lo mismo de siempre, con un colaborador nuevo que escribe muy rápido y decide sin avisar.

Escribí sobre el lado más personal de esto en otro texto. Acá me interesa el lado práctico: la IA como potenciador, no como reemplazo del criterio.

🔭 Lo que viene

Probablemente el futuro de este oficio sea aprender a manejar agentes: diseñar los harnesses que siguen, los guardrails que los contienen, el contexto que necesita cada producto. Ya estamos en eso.

Pero hoy la IA todavía toma decisiones por nosotros, y lo hace de una forma que no se nota en la demo. Se nota tres meses después, cuando alguien intenta cambiar algo chico.

📋 TL;DR

IdeaEn la práctica
La velocidad es realDos meses contra un año no es marketing
El costo está en otro ladoMenos estabilidad, más duplicación, menos refactoring
El problema no es quién escribeEs quién decide
Más tests no es más confianzaUn test que prueba una clase de CSS es un peaje, no una red
No todo software es igualEn una PoC delegás casi todo; en un producto, no
Presupuesto de decisionesEstado, límites, abstracciones, contratos, dependencias, seguridad y qué se testea no se delegan
Guardrails que fallanUna regla en markdown se ignora; un check en CI, no
Más lento a propósitoCuatro meses con criterio pueden ser más rápidos que dos sin él

La IA puede escribir todo nuestro código. Lo que todavía no puede es hacerse cargo de las decisiones que tomó para escribirlo.