Revisión de Código: Leer Lo Que Escribe la IA
Por Qué Importa la Revisión
El código generado por IA usualmente funciona. Se ejecuta, compila, hace aproximadamente lo que pediste. Pero "funciona" y "funciona bien" son cosas diferentes. El código puede funcionar correctamente mientras es inseguro, ineficiente, difícil de mantener o lleno de bugs sutiles esperando a surgir en producción.
Revisar el resultado de la IA no se trata de desconfianza — se trata de aseguramiento de calidad. Los equipos de desarrollo profesionales revisan cada línea de código antes de publicarla, incluso el código escrito por ingenieros senior. El mismo principio aplica al código generado por IA, quizás incluso más porque la IA optimiza para "correcto ahora" en lugar de "mantenible a largo plazo."
Hay un beneficio compuesto en revisar código: aprendes mientras revisas. Cada vez que lees código generado por IA y preguntas "¿por qué lo hizo de esta manera?", profundizas tu comprensión de cómo funciona el software. Después de meses de vibe coding con revisión activa, desarrollarás verdadera intuición de programación — no porque lo estudiaste formalmente, sino porque te involucraste con el código que tu compañero de IA escribió.
El objetivo no es entender cada línea. El objetivo es desarrollar un conjunto de habilidades de reconocimiento de patrones que te permitan detectar problemas de un vistazo. Piénsalo así: no necesitas ser chef para saber que un plato está demasiado salado. No necesitas ser programador para notar que un archivo de 500 líneas probablemente debería dividirse en piezas más pequeñas.
Leer Código a Alto Nivel
Cuando la IA genera código, resiste la tentación de aceptarlo ciegamente o de intentar entender cada carácter. En su lugar, léelo a alto nivel, enfocándote en la estructura y la intención.
Qué Buscar
Nombres y ubicaciones de archivos. ¿La IA creó archivos en los directorios correctos? Un archivo de componente en components/ tiene sentido. Un archivo de componente en lib/ no. Si tu proyecto tiene una estructura consistente, los archivos nuevos deben seguirla.
Nombres de funciones y variables. El buen código se lee casi como inglés. Una función llamada getUserProfile se explica sola. Una función llamada processData es vaga. Variables llamadas user, email e isAuthenticated cuentan una historia clara. Variables llamadas x, temp y flag son señales de alerta.
Comentarios. La IA a veces agrega comentarios útiles que explican por qué el código está estructurado de cierta manera. Léelos — son la explicación de la IA sobre su propio razonamiento. Si no hay comentarios en lógica compleja, ese podría ser un lugar donde pedir aclaraciones.
Imports al inicio del archivo. La sección de imports te dice qué herramientas externas y componentes depende este archivo. Si ves nombres de paquetes desconocidos, pregunta qué son. Si ves imports de paquetes que no instalaste, la IA podría estar alucinando una dependencia.
La longitud total. Un componente de 50 líneas es manejable. Un componente de 500 líneas probablemente está haciendo demasiado y debería dividirse en piezas más pequeñas. La longitud del archivo es un indicador rápido de complejidad.
La Analogía de la Arquitectura de Edificios
Puedes evaluar la arquitectura de un edificio sin saber cómo poner ladrillos. Notas si los pasillos son demasiado estrechos, si las habitaciones fluyen lógicamente, si hay suficiente luz natural. De manera similar, puedes evaluar la arquitectura del código sin entender cada detalle de sintaxis:
- ¿Hay demasiados archivos para la complejidad de la funcionalidad? (Sobre-ingeniería)
- ¿Está todo metido en un solo archivo? (Sub-ingeniería)
- ¿Los nombres de archivos describen claramente lo que contienen?
- ¿Hay una organización lógica, o se siente aleatoria?
- ¿Hay repeticiones obvias — el mismo código apareciendo en múltiples archivos?
Entender la Estructura del Proyecto
Si estás trabajando con Next.js (en lo que se enfoca la mayor parte de este curso), esto es lo que significan los directorios clave:
app/ # Páginas y rutas
page.tsx # La homepage
layout.tsx # Layout compartido (header, footer)
about/
page.tsx # La página /about
api/
users/
route.ts # Endpoint de API: /api/users
dashboard/
page.tsx # La página /dashboard
loading.tsx # Skeleton de carga para /dashboard
error.tsx # Boundary de error para /dashboard
components/ # Piezas de UI reutilizables
ui/ # Componentes genéricos (Button, Modal, Card)
features/ # Componentes específicos de funcionalidades
lib/ # Funciones utilitarias, consultas a DB, helpers
db.ts # Conexión a base de datos
auth.ts # Helpers de autenticación
utils.ts # Utilidades generales
public/ # Archivos estáticos (imágenes, fuentes, favicons)
types/ # Definiciones de tipos TypeScript
Cómo se Relacionan los Componentes con las Páginas
Las páginas en app/ son lo que el usuario ve. Están ensambladas a partir de componentes en components/. Piensa en las páginas como recetas y los componentes como ingredientes.
La página /dashboard podría importar y componer los componentes StatsCard, ActivityFeed, UsageChart y Sidebar. Cada componente maneja su propia renderización y estilización. La página solo los organiza en pantalla y les pasa datos.
Los datos típicamente fluyen en una dirección: la página obtiene datos de funciones en lib/ (que hablan con la base de datos o APIs) y los pasa hacia abajo a los componentes como "props" (propiedades). Los componentes reciben datos y los renderizan — usualmente no obtienen sus propios datos.
Al revisar código de IA, verifica que este flujo tenga sentido. Si un pequeño componente de UI profundo en el árbol de componentes está haciendo llamadas directas a la base de datos, ese es un problema estructural. La obtención de datos debería ocurrir a nivel de página y fluir hacia abajo.
Code Smells Comunes que Genera la IA
"Code smell" (mal olor de código) es un término para patrones que sugieren que algo podría estar mal, incluso si el código técnicamente funciona. Estos son los más comunes en código generado por IA.
Sobre-Ingeniería
La IA ama las abstracciones. Pide un simple formulario de contacto y podrías obtener una clase genérica FormBuilder con un FieldFactory, un ValidationEngine y un SubmissionHandler — todo para tres campos de entrada y un botón.
Qué buscar: Más archivos de los que parecen necesarios para la complejidad de la funcionalidad. Clases base abstractas cuando solo hay una implementación. Objetos de configuración para comportamiento que es lo suficientemente directo para codificar directamente.
Qué decir: "This is over-engineered for our needs. Simplify it to a single component with the form fields directly in the JSX. No abstractions needed for a simple contact form."
Imports No Usados y Código Muerto
La IA a veces deja atrás imports que generó durante una iteración anterior pero ya no usa, o funciones que escribió pero nunca llama.
Qué buscar: Imports atenuados en tu editor (la mayoría de los editores atenúan imports no usados). Funciones o variables definidas pero nunca referenciadas. Bloques de código dentro de if (false) o secciones comentadas.
Qué decir: "Remove unused imports and dead code from this file."
El Tipo any en TypeScript
En TypeScript, any es la vía de escape — le dice al compilador "no verifiques esto, me rindo." La IA a veces usa any cuando no puede determinar el tipo correcto, y silenciosamente desactiva la red de seguridad que proporciona TypeScript.
Qué buscar: La palabra any en archivos TypeScript. Especialmente preocupante en parámetros de funciones, tipos de retorno y definiciones de estado.
// Mal: any desactiva la verificación de tipos
const handleSubmit = (data: any) => { /* ... */ }
// Bien: tipo específico proporciona seguridad
const handleSubmit = (data: ContactFormData) => { /* ... */ }
Qué decir: "Replace all any types with proper TypeScript types. Define interfaces for the data structures."
Valores Codificados Directamente
Valores que deberían ser configurables pero están escritos directamente en el código.
// Mal: valores codificados directamente
const API_URL = "https://api.mysite.com/v2";
const MAX_RETRIES = 3;
const ITEMS_PER_PAGE = 25;
// Mejor: variable de entorno para la URL, archivo de constantes para el resto
const API_URL = process.env.NEXT_PUBLIC_API_URL;
Qué buscar: URLs, endpoints de API, números mágicos, direcciones de email y valores de configuración incrustados directamente en archivos de componentes o páginas.
Qué decir: "Move the API URL to an environment variable. Move the constants (MAX_RETRIES, ITEMS_PER_PAGE) to a constants.ts file."
Lógica Duplicada
La IA no siempre recuerda lo que ya escribió en otros archivos. Podrías terminar con la misma función de formateo, la misma lógica de validación o la misma llamada a API en tres lugares diferentes.
Qué buscar: Código copiado y pegado entre archivos. Múltiples funciones que hacen casi lo mismo. El mismo endpoint de API siendo llamado en diferentes componentes.
Qué decir: "The date formatting logic is duplicated in UserProfile, ActivityFeed, and CommentList. Extract it into a shared utility function in lib/utils.ts and import it in all three."
Manejo de Errores Faltante
El código generado por IA a menudo sigue el "happy path" — maneja el caso donde todo funciona pero no tiene en cuenta los fallos.
// Sin manejo de errores: se caerá si la API está caída
const response = await fetch("/api/users");
const users = await response.json();
// Con manejo de errores: fallo elegante
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error(`Failed to fetch users: ${response.status}`);
}
const users = await response.json();
Qué buscar: Llamadas fetch sin verificar response.ok. Consultas a base de datos sin try/catch. Operaciones orientadas al usuario sin estados de carga y error.
Qué decir: "Add error handling to all API calls and database queries. Show the user a meaningful error message if something fails."
Cadenas Condicionales Excesivamente Complejas
// Difícil de seguir
if (user && user.subscription && user.subscription.plan !== "free"
&& user.subscription.status === "active"
&& !user.subscription.cancelledAt) {
// mostrar contenido premium
}
// Más claro: extraer en una función con nombre
const hasActiveSubscription = (user: User): boolean => {
if (!user?.subscription) return false;
const { plan, status, cancelledAt } = user.subscription;
return plan !== "free" && status === "active" && !cancelledAt;
};
Qué buscar: Bloques if/else profundamente anidados, expresiones condicionales largas, operadores ternarios encadenados.
Qué decir: "The conditional logic in the subscription check is hard to follow. Extract it into a named function with a descriptive name."
Fundamentos de Seguridad que Todo Vibe Coder Debe Conocer
Los problemas de seguridad en el código son invisibles hasta que alguien los explota. No necesitas ser un experto en seguridad, pero conocer estos fundamentos te ayudará a detectar los problemas más comunes.
Nunca Expongas Secretos en Código del Cliente
API keys, contraseñas de base de datos y tokens secretos nunca deben aparecer en código que se ejecuta en el navegador. En Next.js, solo las variables de entorno con prefijo NEXT_PUBLIC_ se envían al navegador. Todo lo demás permanece en el servidor.
Qué buscar: Variables de entorno sin el prefijo NEXT_PUBLIC_ siendo usadas en componentes del cliente (archivos con "use client"). Cualquier cadena que parezca una API key codificada directamente en el código fuente.
// PELIGROSO: esta API key será visible en el navegador
const stripe = new Stripe("sk_live_abc123...");
// SEGURO: solo del lado del servidor, nunca enviado al navegador
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
Inyección SQL
La inyección SQL ocurre cuando la entrada del usuario se inserta directamente en una consulta de base de datos, permitiendo a un atacante modificar el significado de la consulta.
// VULNERABLE: entrada del usuario directamente en la cadena de consulta
const query = `SELECT * FROM users WHERE email = '${userEmail}'`;
// SEGURO: consulta parametrizada (Drizzle ORM maneja esto automáticamente)
const user = await db.select().from(users).where(eq(users.email, userEmail));
Si estás usando un ORM como Drizzle o Prisma, la inyección SQL está mayormente manejada por ti. Pero si ves cadenas SQL crudas con template literals (cadenas con backtick que contienen ${}), eso es una señal de alerta.
Cross-Site Scripting (XSS)
XSS ocurre cuando contenido proporcionado por el usuario se renderiza como HTML sin sanitización. Un atacante podría enviar un comentario conteniendo etiquetas de script que roben datos de otros usuarios.
Qué buscar: Cualquier propiedad de React que renderice HTML crudo de entrada del usuario. Cualquier lugar donde contenido del usuario se renderice como HTML crudo en lugar de texto. React escapa el contenido de texto por defecto, lo cual es seguro.
{/* PELIGROSO: renderiza contenido del usuario como HTML ejecutable */}
{/* Busca usos de dangerouslySetInnerHTML con contenido no sanitizado */}
{/* SEGURO: React automáticamente escapa el contenido de texto */}
<div>{comment.body}</div>
Si debes renderizar HTML (por ejemplo, de un editor de texto enriquecido), siempre sanitízalo primero con una librería como DOMPurify.
Cross-Site Request Forgery (CSRF)
CSRF engaña a un usuario logueado para que realice acciones que no tenía intención de hacer. Por ejemplo, hacer clic en un enlace en un email que elimina su cuenta porque casualmente está logueado.
Qué buscar: Operaciones que cambian estado (DELETE, POST, PUT) que no verifican un token CSRF. La mayoría de los frameworks modernos manejan esto automáticamente, pero los server actions y las API routes personalizadas podrían no hacerlo.
Siempre Validar en el Servidor
La validación del lado del cliente es para la experiencia del usuario — muestra errores mientras el usuario escribe. La validación del lado del servidor es para seguridad — previene que peticiones maliciosas eludan la interfaz.
Qué buscar: API routes o server actions que confían en los datos entrantes sin validación. Si no hay un schema de Zod o verificación de validación en la API route, cualquier dato podría ser enviado.
// Sin validación: confía en lo que sea que el cliente envíe
export async function POST(request: Request) {
const data = await request.json();
await db.insert(users).values(data); // peligroso!
}
// Con validación: rechaza datos inválidos
export async function POST(request: Request) {
const body = await request.json();
const data = createUserSchema.parse(body); // lanza error si es inválido
await db.insert(users).values(data);
}
HTTPS en Todas Partes
Cualquier dato enviado por HTTP (sin la S) puede ser interceptado. Esto incluye contraseñas, API keys e información personal. Vercel y la mayoría de las plataformas de hosting modernas imponen HTTPS por defecto, pero verifica que tus llamadas a API usen https:// y no http://.
Señales de Alerta de Rendimiento
Las aplicaciones lentas pierden usuarios. Estos son los problemas de rendimiento a los que debes estar atento en código generado por IA.
Consultas N+1 a Base de Datos
Esto ocurre cuando el código obtiene una lista de elementos, luego hace una consulta adicional a la base de datos por cada elemento de la lista.
// N+1: una consulta para posts, luego una consulta por post para el autor
const posts = await db.select().from(postsTable);
for (const post of posts) {
const author = await db.select().from(usersTable)
.where(eq(usersTable.id, post.authorId));
post.author = author;
}
// Mejor: una sola consulta con un join
const postsWithAuthors = await db
.select()
.from(postsTable)
.leftJoin(usersTable, eq(postsTable.authorId, usersTable.id));
Qué buscar: Consultas a base de datos dentro de bucles. Si ves await dentro de un bucle for o un .map(), verifica si está tocando la base de datos.
Re-renderizados Innecesarios en React
Los componentes React se re-renderizan cada vez que su padre se re-renderiza o su estado cambia. La IA a veces crea estructuras de componentes que causan re-renderizado excesivo.
Qué buscar: Estado que cambia frecuentemente (como posición del ratón o scroll offset) almacenado alto en el árbol de componentes, causando que todo debajo se re-renderice. Props key faltantes en elementos de lista (React no puede optimizar la renderización de listas sin ellos).
Obtener Demasiados Datos
Cargar todo de la base de datos cuando solo necesitas un subconjunto.
Qué buscar: Endpoints de API que devuelven todos los registros sin paginación. Consultas que seleccionan todas las columnas cuando solo se necesitan unas pocas. Cargar todos los datos históricos cuando solo el período actual es relevante.
Qué decir: "Add pagination to the /api/posts endpoint. Return 20 items per page with a cursor parameter for the next page."
Imágenes Grandes sin Optimización
Las imágenes sin optimizar son la causa más común de cargas de página lentas.
Qué buscar: Etiquetas <img> en lugar de componentes <Image> de Next.js. Atributos width y height faltantes. Imágenes cargadas a resolución completa cuando se muestran en tamaño pequeño.
// Lento: sin optimización
<img src="/hero.png" />
// Rápido: Next.js maneja optimización, lazy loading, tamaños responsivos
<Image src="/hero.png" width={1200} height={600} alt="Hero image" />
Bloquear el Hilo Principal
JavaScript de larga ejecución bloquea la UI impidiendo que responda. La página se congela, el scroll se traba y los botones no responden.
Qué buscar: Procesamiento de datos pesado en funciones de renderizado de componentes. Operaciones síncronas que deberían ser asíncronas. Cálculos pesados que podrían moverse a un Web Worker o al servidor.
IA Revisando a la IA
Una de las técnicas más poderosas del vibe coding es pedirle a Claude Code que revise su propio trabajo. Después de generar código, puedes explícitamente pedir una revisión:
Review the code you just generated for:
1. Security vulnerabilities
2. Performance issues
3. Code smells and maintainability concerns
4. Missing error handling
5. Accessibility problems
La IA es sorprendentemente buena detectando sus propios errores cuando se le pide explícitamente que los busque. El modo de generación y el modo de revisión involucran diferentes patrones de razonamiento. Durante la generación, la IA se enfoca en hacer que el código funcione. Durante la revisión, se enfoca en hacer que el código esté bien hecho.
También puedes pedir revisiones específicas:
Check the API route for SQL injection vulnerabilities
and any cases where user input is not validated.
Review this component for accessibility. Check for:
proper ARIA labels, keyboard navigation, focus management,
color contrast, and screen reader support.
Look at the data fetching pattern in this page. Are there
any unnecessary re-fetches, missing caching opportunities,
or potential race conditions?
Haz de la revisión por IA una parte regular de tu flujo de trabajo, especialmente para código sensible en seguridad (autenticación, pagos, manejo de datos de usuario) y funcionalidades orientadas al usuario.
Cuándo Pedir una Reescritura vs. una Corrección
No todos los problemas son iguales. Algunos merecen una corrección específica. Otros necesitan un enfoque fundamentalmente diferente.
Pide una Corrección Cuando:
- El enfoque general es correcto pero un detalle específico está mal
- Es un problema de estilo (espaciado incorrecto, color incorrecto, alineación incorrecta)
- Una sola función tiene un bug pero el resto del código funciona
- Un caso límite no está manejado pero el flujo principal es correcto
- Un import es incorrecto o un nombre de variable tiene un typo
Ejemplo de prompt de corrección:
The date formatting in the ActivityFeed component shows
"2026-03-23T14:30:00Z" instead of "March 23, 2026 at 2:30 PM".
Update the formatDate function to use a human-readable format.
Pide una Reescritura Cuando:
- El enfoque es fundamentalmente incorrecto (del lado del cliente cuando debería ser del servidor)
- El código sigue haciéndose más complejo con cada corrección
- Estás en la tercera iteración de corregir el mismo componente
- La IA eligió el patrón de arquitectura incorrecto
- Los parches se están acumulando sobre otros parches
Ejemplo de prompt de reescritura:
The current approach of managing cart state in localStorage and syncing
it with the server on every change is causing race conditions and data
loss. Let's take a completely different approach: manage the cart
entirely on the server using server actions. Remove the localStorage
logic entirely. The cart should be stored in the database, tied to
the user's session, and updated through server actions.
Señales de que el Enfoque Está Mal
Observa estas señales que indican que se necesita una reescritura en lugar de otra corrección:
- Complejidad creciente. Cada corrección hace el código más largo y difícil de seguir. Las funcionalidades simples no deberían requerir soluciones complejas.
- Parches sobre parches. El código tiene comentarios como "workaround for..." o "temporary fix for..." Múltiples workarounds significan que la base es inestable.
- La IA sugiere "workarounds." Cuando la IA no puede resolver un problema directamente y propone workarounds, la arquitectura podría no soportar lo que necesitas.
- El mismo bug con diferentes disfraces. Corriges un problema de sincronización de datos, luego un problema similar aparece en otro lugar. El problema es sistémico, no local.
Construir Tus Habilidades de Lectura de Código con el Tiempo
La revisión de código es una habilidad que se desarrolla gradualmente. No necesitas entender todo el primer día. Esta es una progresión realista:
Semana 1-2: Conciencia de estructura de archivos. Puedes mirar un proyecto y entender cuáles archivos son páginas, cuáles son componentes y cuáles son utilidades. Notas cuando los archivos están en el lugar equivocado.
Mes 1: Reconocimiento de patrones. Empiezas a reconocer patrones comunes: manejo de formularios, obtención de datos, renderizado condicional. Puedes notar cuando algo parece inusualmente complejo para lo que hace.
Mes 2-3: Detección de problemas. Detectas manejo de errores faltante, código no usado y problemas obvios de seguridad. Puedes leer un mensaje de error y tener una suposición razonable sobre qué lo causó.
Mes 3-6: Pensamiento arquitectónico. Desarrollas opiniones sobre cómo debería organizarse el código. Notas cuando los datos fluyen en una dirección inusual o cuando los componentes tienen demasiadas responsabilidades. Empiezas a sugerir mejoras estructurales.
Mes 6+: Fluidez técnica. Puedes leer la mayoría del código y entender no solo qué hace sino por qué lo hace de esa manera. Puedes evaluar compensaciones entre diferentes enfoques. Ya no solo estás revisando — estás participando en decisiones arquitectónicas.
Esta progresión ocurre naturalmente si te involucras activamente con el código que la IA genera. No lo aceptes ciegamente — léelo, haz preguntas al respecto, y gradualmente desarrollarás la intuición que te hace un mejor constructor.
Acelerar Tu Aprendizaje
Si quieres acelerar esta progresión, aquí hay algunas estrategias:
Pide a la IA que explique su código. Después de generar un componente, pregunta: "Walk me through this code and explain what each section does." La explicación te enseñará patrones que reconocerás la próxima vez.
Lee el diff antes de aceptar. Cuando la IA modifica archivos, mira los cambios. ¿Qué se agregó? ¿Qué se eliminó? ¿Por qué? Este hábito construye reconocimiento de patrones más rápido que cualquier otra cosa.
Enfócate en un concepto a la vez. No intentes entender todo. Esta semana, enfócate en entender cómo los datos fluyen de la base de datos a la página. La próxima semana, enfócate en cómo se manejan los formularios. Construye tu conocimiento incrementalmente.
Compara enfoques. Cuando la IA reescribe código, compara el antes y el después. ¿Qué mejoró? ¿Qué compensaciones se hicieron? Esta comparación te enseña a evaluar la calidad.
Lo Que Sigue
Ahora tienes el kit completo de habilidades fundamentales para el vibe coding: puedes escribir prompts precisos, configurar CLAUDE.md para contexto persistente, usar Git como red de seguridad, iterar y depurar efectivamente, y revisar código generado por IA para asegurar calidad.
En el próximo módulo, entramos en territorio full-stack. Empezaremos a construir aplicaciones completas — layouts de frontend, APIs de backend, bases de datos, autenticación y más. Las habilidades de este módulo se convierten en la base para todo lo que viene. Aplicarás prompting, depuración y revisión en cada lección mientras construimos software real y desplegable.