El problema de TypeScript type safety codigo generado IA no es que TypeScript falle, sino que el agente puede escribir código que parece tipado mientras confía en datos que nunca fueron validados. Compila, pasa lint, incluso puede verse elegante, pero se rompe cuando una API devuelve null, una herramienta responde otro shape, un JSON llega incompleto o un modelo inventa una propiedad.
La solución no es "más tipos" en abstracto: es validar en runtime las fronteras del sistema, reducir casts, modelar errores y tratar toda salida externa como unknown hasta demostrar lo contrario.
Los datos son contundentes: el 94% de los errores de compilación en código generado por LLMs son fallas de type-check, según un estudio de 2025 (YUV.AI). Un error de tipo atrapado en compilación cuesta en promedio USD 25 en tiempo de desarrollo; el mismo error en producción cuesta entre USD 750 y USD 1.500. TypeScript no es overhead de desarrollo: es seguro de producción.
El falso confort del TypeScript correcto
TypeScript nos acostumbró a una promesa razonable: si el programa compila, muchas clases de errores desaparecen antes de llegar a producción. Esa promesa sigue siendo valiosa. El problema aparece cuando incorporamos agentes que generan código a alta velocidad y con una tendencia natural a completar huecos.
Un agente puede inferir una interfaz plausible desde el nombre de una función. Puede asumir que una respuesta de API siempre trae items. Puede convertir una respuesta de un modelo en un objeto tipado con un cast.
Ahí aparece una grieta importante: TypeScript valida el programa según la información disponible en tiempo de compilación. Pero muchas aplicaciones viven de datos que llegan en runtime: formularios, webhooks, respuestas HTTP, eventos, feature flags, tool calls y contenido generado por modelos.
Si el código generado por IA transforma esas entradas en tipos confiables sin validación, el compilador queda satisfecho. El sistema, no.
El patrón de falla más frecuente
const response = await fetch('/api/customer');
const customer = (await response.json()) as Customer;
return customer.plan.toUpperCase();
La línea crítica es el as Customer. No convierte nada. No valida nada. Solo le dice al compilador que deje de preguntar. Si plan llega como null, si viene como "enterprise", si falta email, o si el endpoint devuelve un objeto de error, el tipo está mintiendo.
Los agentes tienden a usar este patrón porque resuelve la presión local del compilador. Pero ese puente no existe en runtime.
Tipos estáticos y datos externos
La regla práctica es simple: todo dato que cruza una frontera debe entrar como desconocido.
Una frontera puede ser: una respuesta HTTP, un webhook, un archivo JSON, una variable de entorno, un parámetro de URL, una respuesta de una herramienta llamada por un agente, una salida estructurada de un modelo o una fila histórica de base de datos con migraciones parciales.
Una mejor versión del ejemplo anterior:
import { z } from 'zod';
const CustomerSchema = z.object({
id: z.string(),
email: z.string().email(),
plan: z.enum(['free', 'pro']),
});
type Customer = z.infer<typeof CustomerSchema>;
const response = await fetch('/api/customer');
const payload: unknown = await response.json();
const customer = CustomerSchema.parse(payload);
return customer.plan.toUpperCase();
Ahora el tipo Customer no nace de una afirmación. Nace de una validación. Si el dato no cumple el contrato, el sistema falla en el lugar correcto, con un error más cercano a la causa.
Validar en los bordes, no en todas partes
| Frontera | Riesgo sin validación | Solución con Zod/Valibot |
|---|---|---|
| Respuesta HTTP / API externa | El shape puede cambiar sin aviso | Schema parse en el adapter |
| Tool calls de agentes | El agente puede inferir un shape incorrecto | Schema parse en el tool handler |
| Webhooks entrantes | Cualquier payload es posible | Schema parse antes de procesar |
| Variables de entorno | undefined silencioso en producción | z.string().min(1) en startup |
| Filas de DB con migraciones parciales | Columnas viejas pueden faltar | Schema parse al leer |
El objetivo no es llenar la aplicación de esquemas. El objetivo es ubicar los puntos donde el mundo externo entra al sistema y convertir datos desconocidos en valores confiables. Después de esa conversión, el dominio interno puede trabajar con tipos fuertes sin repetir la misma defensa en cada línea.
unknown externo -> validación -> tipo de dominio -> lógica interna
Modelar errores como parte del contrato
Otro problema frecuente en código generado por agentes es el manejo optimista de errores. El agente escribe el camino feliz, agrega un try/catch, y en el catch devuelve null o un mensaje genérico. Ese patrón borra información importante.
Una alternativa más segura:
type Result<T> =
| { ok: true; value: T }
| { ok: false; reason: 'network' | 'invalid_payload' | 'not_found' };
Esto obliga al consumidor a decidir qué hacer. TypeScript vuelve a ser útil porque el estado del programa queda representado en el tipo.
Este tipo de problema se conecta con los anti-patrones de agentes IA en producción: el código generado que "compila" pero opera sobre suposiciones inválidas es una forma más sutil del mismo problema. Y en el contexto de edge functions para agentes, la validación en las fronteras es exactamente el patrón de seguridad que protege al sistema.
Cómo pedirle mejor código a un agente
El prompt importa. "Implementa esta integración en TypeScript" deja demasiado espacio para el camino feliz. Una instrucción mejor define las expectativas de seguridad:
"Tratá toda respuesta externa como unknown, validá con el esquema existente, no uses casts para silenciar el compilador, modelá errores esperados y agregá tests para payload inválido."
Eso no garantiza perfección, pero cambia el criterio de éxito. El agente ya no intenta solo compilar. Intenta preservar un contrato.
La revisión humana cambia de foco
Tres zonas donde el código generado por agentes falla con más frecuencia:
- Respuestas de APIs externas: el agente infiere el shape desde el nombre del endpoint en lugar de verificar el contrato real.
- Tool calls y salidas de modelos: resultados que el agente trata como tipados cuando deberían entrar como unknown hasta validarlos.
- Migraciones de base de datos parciales: filas históricas con columnas que ya no existen en el schema actual.
La revisión de código generado por agentes no debería concentrarse solo en estilo. La pregunta central es otra: ¿dónde está confiando este código?
Cada vez que el código cruza una frontera, hay que buscar el punto exacto donde el dato se vuelve confiable. Si no existe, falta una pieza.
Preguntas frecuentes
¿Por qué TypeScript puede compilar y aun así fallar en runtime? Porque TypeScript verifica lo que el programa sabe en tiempo de compilación, no lo que realmente llega desde una API, webhook o modelo. Si aceptás datos externos como si ya fueran confiables, el compilador puede quedar satisfecho mientras el sistema sigue expuesto.
¿Cuál es el riesgo de usar casts en código generado por agentes? Un cast no transforma ni valida datos: solo le dice al compilador que confíe. Podés ocultar errores hasta que una propiedad falte o llegue un objeto inesperado.
¿Dónde conviene validar los datos en una aplicación TypeScript? En las fronteras del sistema: respuestas HTTP, tool calls, webhooks, formularios, archivos, eventos y salidas de modelos.
¿La solución es agregar más tipos al código generado por IA? No necesariamente. Más tipos ayudan solo si representan datos ya validados. Lo importante es combinar tipos estáticos con validación en runtime y manejo explícito de errores.
¿Cómo podés reducir fallos de type safety en código generado por agentes? Tratando toda entrada externa como desconocida hasta validarla, evitando casts innecesarios y modelando los casos de error.

