30 días programando con IA sin tocar el código: cinco trampas de arquitectura en un SaaS real
Un mes dejando que la IA escribiera todo el código de un micro-SaaS en producción destapó deriva, webhooks mal gestionados, bucles de depuración, código inflado y un problema con los juniors.
Finales de 2026 trajo el griterío de siempre: la ingeniería de software ha muerto, la IA ya escribe todo el código, el lenguaje natural es la única sintaxis que necesitas. Para ver dónde termina el hype y empieza la producción, un experimento de 30 días impuso una regla estricta: ni una línea de código de la aplicación escrita a mano. La IA no fue aquí una ayuda para teclear ni un autocompletado. La persona actuó como supervisora de arquitectura y revisora: definía esquemas, escribía requisitos, leía diffs y aprobaba cambios, mientras el modelo escribía la lógica, las consultas y las integraciones.
El proyecto no era una lista de tareas. Era un micro-SaaS real: login, migraciones, paneles de usuario, webhooks de Stripe y APIs públicas con límites de peticiones. Funciona, está desplegado y atiende tráfico real. El primer 40% del desarrollo pareció magia. Luego el repositorio superó las 15.000 líneas, llegó el estado distribuido y la idea de que «la IA lo escribe todo» chocó con límites de ingeniería muy duros. Se registraron siete tipos de fallo. Aquí van cinco, con las lecciones que dejan.
Los primeros días sí son rápidos
Primero, seamos justos: montar la base de un proyecto nuevo desde cero es un problema prácticamente resuelto.
- Un stack full-stack moderno con Next.js, Tailwind, PostgreSQL, Drizzle ORM, middleware de autenticación y configuración de Docker llevó una tarde.
- Los esquemas repetitivos de Zod, los helpers de validación, las tablas ordenables y la paginación salieron sin esfuerzo, y los endpoints CRUD predecibles llegaron limpios, sin una sola errata.
- Cuando una consulta aislada tenía un mal plan de ejecución, el modelo detectó enseguida una clave foránea sin índice y propuso la migración exacta para crearlo.
Durante seis días pareció que el desarrollo tradicional quizá estaba obsoleto. Luego empezó la segunda semana.
Trampa 1: foco local, deriva global
La debilidad más traicionera es la miopía local. Un modelo juzga con una precisión notable el archivo o la función que tiene en su ventana de contexto, pero no ve las convenciones fijadas tres directorios más allá.
Architectural Drift Anti-Pattern
┌─────────────────────────────────────────────────────────────┐
│ Core Utilities (/src/lib/utils/format.ts): │
│ └── export const formatCurrency = (cents: number) => ... │
└─────────────────────────────────────────────────────────────┘
▲
(The AI ignores this file)
│
┌─────────────────────────────────────────────────────────────┐
│ New Billing Module (/src/modules/billing/invoice.ts): │
│ └── export const formatPriceUSD = (amount: number) => ... │
│ (Re-implements logic with different rounding edge cases)│
└─────────────────────────────────────────────────────────────┘
En dos semanas, el agente había escrito tres helpers de formato de moneda ligeramente distintos en módulos separados, había reimplementado una comprobación de sesión en línea dentro de un nuevo manejador de ruta en vez de usar el pipeline de middleware existente y había declarado dos interfaces UserProfile contradictorias en paquetes distintos que compartían el 90% de los campos pero serializaban las fechas de forma diferente.
Nada de eso provocó un error de compilación, y los tests seguían en verde. Pero en el código se iba acumulando una deriva de arquitectura silenciosa, esa podredumbre estructural que puede convertir un proyecto ágil en un legado inmantenible en unos seis meses.
Trampa 2: código plausible pero erróneo
Los bugs más peligrosos del código escrito por IA no son caídas ni errores de sintaxis, porque los primeros los atrapa el compilador y los segundos el linter. Los que asustan se leen bien, pasan sin problema las pruebas locales y rompen en silencio las garantías transaccionales. Este es el patrón real que produjo la IA para un webhook de facturación de Stripe:
// The AI's Initial Webhook Handler (Dangerous Anti-Pattern)
export async function handleStripeWebhook(req: Request) {
const event = await parseStripeEvent(req);
if (event.type === 'checkout.session.completed') {
// 1. Immediately acknowledge event to Stripe
sendImmediate200Response(); // 2. Perform async database update in unhandled background promise
db.users.update({
where: { customerId: event.data.object.customer },
data: { isSubscribed: true }
}).catch(err => logError(err));
}
}
En una máquina de desarrollo con datos de prueba, pasa todos los tests. Confirma el evento al instante, devuelve 200 OK y actualiza la base de datos SQLite local. En producción y con carga, es un desastre anunciado:
- Si la base de datos sufre un timeout de conexión transitorio o un deadlock dentro de esa promesa en segundo plano sin await, el error desaparece.
- Se cobra la tarjeta del cliente y Stripe da el webhook por entregado, pero la cuenta nunca se actualiza.
- Como se devolvió un
200, Stripe nunca reintenta, así que la transacción se pierde para siempre.
Cualquiera que haya depurado condiciones de carrera en pagos en producción lo ve al momento: los webhooks tienen que ser idempotentes, el procesamiento tiene que terminar dentro de una transacción atómica y 200 OK solo puede devolverse cuando el estado está guardado de forma segura. Un junior que copiara ese diff de aspecto pulcro lo habría aprobado sin pensárselo dos veces.
Trampa 3: el bucle infinito de autodepuración
Cada vez que un bug dependía de un estado modificado fuera del archivo a la vista, pedirle a la IA que arreglara su propio código desataba un ciclo desesperante:
The Debugging Despair Loop
┌────────────────────────────────────────────────────────┐
│ 1. Bug Identified: State race condition in worker │
└──────────────────────────┬─────────────────────────────┘
│
▼
[Prompt AI to Fix] ──► Confidently rewrites worker.ts
│
▼
[Run Tests] ─────────► Bug disappears; introduces memory leak
│
▼
[Point out Leak] ────► Confidently rewrites worker.ts again
│
▼
[Run Tests] ─────────► Re-introduces the original race condition!
El modelo no tenía un depurador en tiempo de ejecución para recorrer trazas, inspeccionar el heap u observar tiempos distribuidos, así que se apoyaba en conjeturas probabilísticas sacadas de los mensajes de error. Declaraba alegremente el bug corregido, mostraba un diff limpio y dos iteraciones después traía de vuelta el fallo original. Resolver estos problemas exigía intervenir a mano con un depurador interactivo para mirar el estado bruto de los hilos.
Trampa 4: incapaz de decir que no
La buena ingeniería depende tanto de lo que te niegas a construir como de lo que entregas. Cuando se le pedía diseñar una funcionalidad, como una pantalla de ajustes de la cuenta, el modelo tendía por defecto a expandirlo todo sin control. Generó 14 interruptores configurables, elaboradas matrices de preferencias de notificaciones y pestañas anidadas que ningún usuario había pedido. Si le pedías gestión de errores, envolvía cada función interna en bloques genéricos try/catch que se tragaban la excepción original y devolvían "Something went wrong", acabando con la observabilidad.
Un modelo no tiene intuición de producto y trata más código como más valor. Mantener las interfaces ligeras, podar abstracciones innecesarias y conservar trazas de pila legibles siguen siendo habilidades humanas.
Trampa 5: el último 10% se lleva el tiempo
El software obedece una regla empírica dura: aproximadamente el primer 80% de las funcionalidades consume el 20% del calendario, y el 20% final consume el otro 80%. La IA automatiza por completo la primera parte. La estructura de la interfaz, los endpoints REST y los formularios estándar llevaron menos de una semana. Las otras tres semanas se fueron en el trabajo desordenado del mundo real:
- Jitter de red y reintentos con backoff exponencial
- Escrituras idempotentes en la base de datos a través de webhooks distribuidos
- Estados de recuperación para casos límite, como un usuario que pulsa Recargar a mitad de un callback de OAuth
- Fugas de memoria en workers de segundo plano de larga duración
La IA no piensa en los casos límite a menos que sepas que tienes que pedirle que los busque, y saberlo cuesta años de cicatrices en producción.
La paradoja del junior
El experimento desmontó la afirmación simplista de que la IA sustituirá a los programadores. Lo que automatizó fue el trabajo de nivel inicial que antes iba a los juniors, no la ingeniería senior.
The Knowledge Pipeline Dilemma
Traditional Career Path:
[Junior: Scaffolding / CRUD] ──► [Mid-Level: Debugging / APIs] ──► [Senior: System Design]
▲
Today's Reality: │
[AUTOMATED BY AI] ──► [AUTOMATED BY AI] ──► [Senior Judgment Required]
│
Question: Where do tomorrow's senior engineers build their intuition? ──┘
La configuración de boilerplate, el andamiaje de tests unitarios, los layouts con CSS grid y las consultas CRUD sencillas son justo donde los principiantes construían antes su modelo mental de los sistemas de software. No puedes saltar directamente a la arquitectura de sistemas distribuidos sin cientos de horas peleando con la sintaxis, persiguiendo errores por uno y aprendiendo por qué los esquemas mal diseñados se hunden con carga. La IA ha elevado el valor del criterio senior: detectar código plausiblemente erróneo, rechazar abstracciones innecesarias y tener toda la arquitectura del sistema en la cabeza para evitar la deriva. Al mismo tiempo, ha quitado la escalera por la que los juniors subían hacia esa experiencia.
Qué cambió en el flujo de trabajo
La IA se sigue usando, incluso para la mayor parte de la generación de texto en bruto, pero la ilusión de que producir código es lo mismo que hacer ingeniería se ha esfumado. Ahora rigen tres reglas:
- El autor nunca es el revisor. Un agente que escribe un diff no debería ser quien lo valide. Usa un agente revisor aparte y adversarial, al que se le pida encontrar fallos en casos límite, o lee cada línea tú mismo con mentalidad escéptica.
- Pasar los tests es solo la línea de salida. Nunca aceptes un pull request solo porque los tests estén en verde, ya que los modelos son buenos escribiendo tests que confirman sus propias suposiciones erróneas.
- La IA teclea, las personas son dueñas de la arquitectura. Deja que la máquina produzca la sintaxis repetitiva, pero mantén los modelos de datos, los límites del sistema, las garantías transaccionales y los invariantes operativos bajo dirección humana.
La máquina escribe código más rápido que cualquier persona de la historia. Aun así, el código es un pasivo, y el sistema que forma es el activo.