TL;DR
- Programar con asistencia de IA no hace que el código sea automáticamente más difícil de mantener, pero la evidencia más sólida disponible indica que los flujos de trabajo rápidos con IA necesitan mejores controles.
- Escribe las pruebas relacionadas en el mismo cambio que la funcionalidad y usa la cobertura para encontrar brechas en lugar de perseguir el 100%.
- Mantén pequeños los diffs generados con IA para que sean más fáciles de revisar y comprender, y menos propensos a introducir bugs.
- Activa el tipado estricto, el linting y el formateo desde el principio para detectar código deficiente generado por IA antes de que llegue a la base de código.
- Revisa el código generado por IA como cualquier otro: verifica los nombres, las responsabilidades, la duplicación, los casos límite y el manejo de errores antes de hacer commit.
El vibe coding te permite entregar software a una velocidad ridícula. Describes lo que quieres, la IA lo escribe y, de repente, tienes una aplicación funcional. El riesgo no está en programar con asistencia de IA en sí. El riesgo está en asumir que la velocidad produce automáticamente código fácil de mantener.
La investigación ofrece un panorama más matizado que el entusiasmo del momento. Algunos estudios controlados han encontrado que el código generado con asistencia de IA puede ser tan fácil de mantener como el código escrito sin IA en ciertas tareas, y el estudio de GitHub sobre la calidad de Copilot reportó mejoras modestas en legibilidad y mantenibilidad en un ejercicio acotado. Al mismo tiempo, un análisis de GitClear a escala de repositorios encontró más código duplicado, más cambios a corto plazo y menos refactorización a medida que aumentaba el uso de IA. La conclusión no es que «la IA arruina el código», sino que la iteración rápida necesita mejores controles.
Esto no tiene por qué pasar. Puedes hacer vibe coding y mantener la calidad. Solo necesitas adoptar algunos hábitos de manera consciente.
El verdadero costo de descuidar la calidad
Cuando dejas que una IA genere cientos de líneas sin supervisión, acumulas lo que llamo deuda silenciosa:
- Funciones que hacen demasiadas cosas y no se pueden reutilizar
- Lógica duplicada y dispersa en distintos archivos porque la IA no conocía las utilidades existentes
- Ausencia de pruebas, de modo que cada cambio es una apuesta
- Nombres inconsistentes que hacen que la base de código parezca escrita por cinco personas diferentes (porque, en la práctica, así fue)
Esta deuda se acumula. Cada funcionalidad nueva es más difícil de agregar porque estás construyendo sobre cimientos frágiles. Con el tiempo, la propia IA empieza a producir peores resultados porque el contexto que lee está lleno de ruido y contradicciones.
La solución es sencilla: trata la calidad como una práctica continua, no como una fase de limpieza.
Escribe pruebas unitarias mientras avanzas
Este es el hábito con mayor impacto que puedes adoptar durante el vibe coding. No después. No en un «sprint de pruebas» dedicado. Mientras avanzas.
Por qué las pruebas importan aún más con IA
Cuando escribes código a mano, tienes un modelo mental implícito de cómo funciona. Lo probaste mentalmente mientras lo escribías. Con el código generado por IA, ese modelo mental no existe: la IA lo tuvo por un momento y luego siguió adelante.
Las pruebas son el artefacto que captura ese entendimiento. Documentan lo que se supone que debe hacer el código y detectan cuando deja de hacerlo.
Cómo integrar las pruebas en tu flujo de vibe coding
Después de que la IA genere una funcionalidad, pídele de inmediato que escriba pruebas:
- «Escribe pruebas unitarias para la función
calculateStreak. Cubre el caso exitoso, los casos límite con datos vacíos y los límites entre zonas horarias». - «Agrega pruebas para el handler de la API. Simula la capa de base de datos y prueba tanto las respuestas exitosas como las de error».
- «Escribe pruebas de integración para el flujo de checkout, desde el carrito hasta la confirmación del pago».
Sé específico sobre lo que quieres probar. Si solo dices «escribe pruebas», obtendrás pruebas superficiales que verifican cosas obvias. Pide casos límite, flujos de error y condiciones de frontera. Siempre que sea posible, incluye las pruebas relacionadas en el mismo cambio que el código de producción.
Estructura las pruebas para facilitar su lectura
Las buenas pruebas siguen el patrón Arrange-Act-Assert:
describe("calculateStreak", () => {
it("returns zero when no completions exist", () => {
const completions: Completion[] = [];
const streak = calculateStreak(completions);
expect(streak).toBe(0);
});
it("counts consecutive days ending today", () => {
const completions = [
{ date: "2026-03-14", completed: true },
{ date: "2026-03-15", completed: true },
{ date: "2026-03-16", completed: true },
];
const streak = calculateStreak(completions);
expect(streak).toBe(3);
});
it("breaks streak on missed days", () => {
const completions = [
{ date: "2026-03-13", completed: true },
{ date: "2026-03-15", completed: true },
{ date: "2026-03-16", completed: true },
];
const streak = calculateStreak(completions);
expect(streak).toBe(2);
});
});
Cada prueba se lee como una especificación. Meses después, alguien (o alguna IA) puede revisar estas pruebas y entender exactamente qué se supone que debe hacer calculateStreak.
Busca una cobertura significativa
No persigas el 100% de cobertura de código. La guía de pruebas de Google señala explícitamente que maximizar el porcentaje por sí solo puede crear una falsa sensación de seguridad y generar pruebas de poco valor. Usa la cobertura para encontrar brechas, no para declarar victoria. En cambio, concéntrate en:
- Lógica de negocio: cálculos, transformaciones, máquinas de estados
- Casos límite: entradas vacías, valores nulos, condiciones de frontera
- Manejo de errores: qué sucede cuando falla la red, la base de datos no está disponible o la entrada tiene un formato incorrecto
- Puntos de integración: contratos de API, consultas a bases de datos, interacciones con servicios de terceros
No pruebes el layout puramente visual de la interfaz ni el código boilerplate del framework. Ese código cambia con demasiada frecuencia y probarlo agrega fricción sin detectar bugs reales.
Aplica patrones de código limpio
El código generado por IA tiende a ser funcional, pero desordenado. Funciona, pero a menudo es verboso, está mal organizado y es difícil de modificar. Los patrones de código limpio corrigen esto.
Responsabilidad única
Cada función y módulo debe hacer una sola cosa. Cuando la IA genera una función de 60 líneas que obtiene datos, los transforma, los valida y los renderiza, eso son cuatro responsabilidades. Divídela.
Pídele a la IA que la refactorice: «Divide esta función en funciones separadas para obtener, transformar, validar y renderizar los datos. Cada función debe tener un nombre claro y un tipo de retorno».
Nombres significativos
A la IA le encantan los nombres genéricos: data, result, item, handleClick. Casi siempre son inadecuados. Los nombres deben revelar la intención:
data→userCompletionsresult→streakCountitem→habitEntryhandleClick→toggleHabitForDay
Si no puedes ponerle un nombre claro, probablemente todavía no entiendes qué hace. Es una señal de que debes detenerte y pensar antes de continuar.
Mantén las funciones pequeñas
Una buena regla general: si una función no cabe en tu pantalla, es demasiado larga. Las funciones largas son difíciles de entender, probar y reutilizar.
Cuando la IA genere una función grande, pídele que la descomponga: «Esta función hace demasiadas cosas. Extrae la lógica de validación a una función validateHabitInput y la lógica de persistencia a una función saveHabitEntry».
Elimina la duplicación
La IA no recuerda perfectamente toda tu base de código. A menudo volverá a generar lógica que ya existe en otra parte. Presta atención a:
- Wrappers similares para obtener datos en diferentes archivos
- Lógica repetida para formatear fechas
- Reglas de validación duplicadas
- Patrones de manejo de errores copiados y pegados
Cuando detectes duplicación, consolídala en una utilidad compartida. Luego infórmaselo a la IA: «Tenemos una utilidad formatDate en src/utils/date.ts. Úsala en lugar de formatear la fecha directamente».
Usa los tipos como documentación
En los proyectos de TypeScript, los tipos sirven como documentación viva. En lugar de permitir que la IA use any o defina estructuras de objetos en línea, define interfaces explícitas:
interface Habit {
id: string;
name: string;
frequency: "daily" | "weekly";
createdAt: Date;
}
interface HabitCompletion {
habitId: string;
date: string;
completed: boolean;
}
interface StreakResult {
current: number;
longest: number;
lastCompletedDate: string | null;
}
Estos tipos mejoran los resultados futuros de la IA porque le dan un contexto más claro sobre la estructura de tus datos. En TypeScript, las comprobaciones estrictas como noImplicitAny y strictNullChecks también detectan ciertos tipos de errores durante la compilación, en lugar de hacerlo en tiempo de ejecución.
Establece convenciones para el proyecto desde el principio
Una de las principales fuentes de desorden en los proyectos creados con vibe coding es la inconsistencia. La IA escribe cada parte de manera aislada, por lo que los patrones terminan divergiendo.
Crea una estructura para el proyecto
Antes de empezar a desarrollar, establece una estructura clara de carpetas:
src/
components/ # Reusable UI components
pages/ # Route-level components
hooks/ # Custom React hooks
utils/ # Pure utility functions
services/ # API calls and external integrations
types/ # TypeScript interfaces and types
constants/ # App-wide constants and config
__tests__/ # Test files (or colocate with source)
Infórmale a la IA sobre esta estructura. Cuando cree un archivo nuevo, asegúrate de que lo coloque en el lugar correcto.
Define patrones para el manejo de errores
Decide desde el principio cómo maneja los errores tu aplicación y sé consistente:
class AppError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number = 500
) {
super(message);
this.name = "AppError";
}
}
function handleApiError(error: unknown): AppError {
if (error instanceof AppError) return error;
if (error instanceof Error) {
return new AppError(error.message, "INTERNAL_ERROR");
}
return new AppError("An unexpected error occurred", "UNKNOWN_ERROR");
}
Una vez que tengas un patrón, pídele a la IA que lo siga. La consistencia en el manejo de errores previene categorías enteras de bugs.
Usa linting y formateo
Configura ESLint y Prettier antes de empezar a hacer vibe coding. ESLint está diseñado para identificar patrones que hacen que el código sea inconsistente o propenso a bugs, y Prettier elimina los debates de estilo al mantener una misma convención de formato en toda la base de código.
A veces, la IA generará código que incumple tus reglas de lint. No hay problema: el linter lo detecta y tú lo corriges antes de hacer commit. La idea es contar con una red de seguridad.
Mantén pequeños los cambios generados con IA
Una de las formas más sencillas de proteger la calidad es pedirle a la IA un cambio autocontenido a la vez. Los diffs gigantes generados con IA son difíciles de comprender y revisar, y es fácil aprobarlos sin el análisis necesario.
Unos buenos prompts se ven así:
- «Cambia únicamente la capa de validación y sus pruebas. No toques los archivos de la interfaz».
- «Implementa el endpoint de la API en un diff del tamaño de un PR. Mantén separados los cambios de React».
- «Refactoriza este helper en una utilidad independiente sin cambiar su comportamiento».
La guía de revisión de código de Google es directa al respecto: los cambios pequeños se revisan más rápido y con mayor profundidad, y son menos propensos a introducir bugs. También deben incluir las pruebas relacionadas.
Revisa críticamente el código generado por IA
La práctica más importante no es técnica, sino de comportamiento. Lee lo que escribe la IA. No te limites a comprobar si funciona. Comprueba si es correcto.
Si el cambio abarca varios archivos, empieza por entender la estructura general del diff. A veces ayuda leer primero las pruebas para conocer el comportamiento esperado antes de inspeccionar la implementación.
Preguntas que debes hacerte durante la revisión
- ¿Esta función hace una sola cosa?
- ¿Los nombres son claros y revelan la intención?
- ¿Hay casos límite que no se están manejando?
- ¿Existe código que haga algo similar?
- ¿Me sentiría cómodo depurando esto a las 2 AM?
- ¿Puedo explicar qué hace este código sin leer cada línea?
Señales de alerta
- Funciones que lo hacen todo: cualquier función de más de 30-40 líneas merece una revisión más detallada
- Números y cadenas mágicos: valores escritos directamente en el código que deberían ser constantes
- Errores ignorados: bloques
catchvacíos o errores registrados pero no manejados - Dependencias implícitas: funciones que acceden al estado global o dependen de efectos secundarios
- Falta de comprobaciones de valores nulos: la IA suele asumir que las entradas corresponden al caso exitoso
Cuando detectes estos problemas, no los corrijas a mano: pídele a la IA que los corrija. Así le enseñas tus estándares para el resto de la sesión.
Refactoriza con regularidad
El vibe coding genera código en ráfagas. Después de cada ráfaga, dedica un momento a limpiar. No es tiempo perdido: es una inversión que acelera la siguiente ráfaga.
El patrón de prompt para refactorizar
Después de completar una funcionalidad, haz una pasada de refactorización:
- «Revisa los archivos que acabamos de crear. Busca lógica duplicada, funciones demasiado complejas, nombres inconsistentes y tipos faltantes. Sugiere mejoras de refactorización».
- «Extrae la lógica compartida entre
HabitListyHabitDetaila un hook personalizado». - «El manejo de errores en las rutas de la API es inconsistente. Crea un patrón de middleware y aplícalo en todas partes».
Cuándo refactorizar
Refactoriza cuando notes que:
- Le estás explicando a la IA qué hace una función existente (debería ser evidente a partir del código)
- Agregar una funcionalidad pequeña requiere modificar muchos archivos
- Estás copiando código entre componentes
- Las pruebas fallan por cambios que no están relacionados
No refactorices por refactorizar. Hazlo cuando la estructura actual realmente te esté frenando.
Lista de verificación de calidad para vibe coding
Antes de hacer commit de cualquier funcionalidad creada con vibe coding, revisa esta lista:
- Hay pruebas para la lógica de negocio y los casos límite
- Las funciones son pequeñas y cada una hace una sola cosa
- Los nombres son significativos: nada de
data,result,tempohandleClick - Los tipos son explícitos: evita el
anyimplícito, activa las comprobaciones estrictas de valores nulos y prefiere tipos compartidos con nombre en los límites importantes - Los errores se manejan: no hay bloques catch vacíos ni fallas ignoradas
- La duplicación es mínima: la lógica compartida vive en utilidades o hooks
- El linter pasa: no hay advertencias ni supresiones sin comentarios que expliquen el motivo
- Puedes explicarlo: si no puedes describir qué hace una parte del código, reescríbela hasta que puedas hacerlo
La calidad es velocidad
Parece contradictorio, pero mantener la calidad del código durante el vibe coding en realidad te permite avanzar más rápido. El código limpio es más fácil de entender y extender para la IA. Las pruebas detectan regresiones antes de que se acumulen. Los patrones consistentes implican menos tiempo explicando el contexto en cada prompt.
Los desarrolladores que más provecho sacan del vibe coding no son quienes aceptan sin cuestionar todo lo que genera la IA. Son quienes la guían hacia un código limpio, probado y bien estructurado, y detectan cuando se desvía.
Haz vibe coding rápido. Pero hazlo con código limpio.
Fuentes
- Ecos de la IA: investigación de los efectos posteriores de los asistentes de IA en la mantenibilidad del software - Estudio controlado sobre si el código generado con asistencia de IA es más difícil de evolucionar para otros desarrolladores.
- ¿GitHub Copilot mejora la calidad del código? Esto es lo que dicen los datos - Estudio controlado aleatorizado de GitHub sobre funcionalidad, legibilidad, mantenibilidad y tasas de aprobación.
- Calidad del código con AI Copilot: los datos de 2025 sugieren un crecimiento de 4x en los clones de código - Análisis a escala de repositorios que muestra más duplicación, más cambios a corto plazo y menos refactorización en la era de la IA.
- Qué buscar en una revisión de código - Guía de Google sobre la revisión de pruebas, nombres, diseño y salud del código.
- CL pequeños - Los argumentos de Google para mantener los cambios pequeños, autocontenidos y acompañados de las pruebas relacionadas.
- Cómo recorrer un CL durante la revisión - Guía de revisión que señala explícitamente que puede ser útil leer primero las pruebas.
- Buenas prácticas de cobertura de código - Por qué la cobertura es útil como señal, pero es un objetivo deficiente cuando se persigue por sí misma.
- TSConfig de TypeScript: noImplicitAny - Documentación oficial sobre cómo evitar el
anyimplícito. - TSConfig de TypeScript: strictNullChecks - Documentación oficial sobre cómo detectar problemas con valores nulos e indefinidos antes del tiempo de ejecución.
- Primeros pasos con ESLint - Explicación de ESLint sobre cómo el linting mejora la consistencia y ayuda a evitar bugs.
- ¿Por qué Prettier? - Explicación de Prettier sobre por qué un estilo de formato compartido reduce la fricción y el ruido durante la revisión.