El Costo Oculto del Código que “Funciona”

Workflow Sistemático de Revisión de Código con IA: Planificar, Generar, Validar

Guía práctica para mantener la calidad del código a velocidad de IA


Introducción: El Costo Oculto del Código que “Funciona”

El siguiente código genera un endpoint de registro de usuarios. Compila correctamente, pasa las pruebas básicas y funciona en staging:

app.post('/api/register', async (req, res) => {
  const { email, password, username } = req.body;
  
  const user = await db.query(
    `INSERT INTO users (email, password, username) 
     VALUES ('${email}', '${password}', '${username}') RETURNING *`
  );
  
  res.json({ success: true, user });
});

:warning: Problemas críticos:

  • Vulnerabilidad de inyección SQL
  • Contraseña almacenada en texto plano
  • Sin validación de entrada
  • Sin manejo de errores
  • Sin protección contra duplicados

Este es el resultado típico cuando se genera código con IA sin un proceso de validación. La IA optimiza para código que funciona, no para código correcto.

La solución es implementar un workflow sistemático que separe la generación de la validación.


El Workflow de Tres Fases

Principio fundamental: Cada fase tiene un propósito específico y utiliza herramientas diferentes. La IA de generación (Cursor, Copilot, Claude) optimiza velocidad. La IA de revisión (Claude Code, CodeRabbit) optimiza seguridad y calidad.


Fase 1: Planificación Estructurada

Una planificación clara produce mejores resultados de la IA generativa. Antes de escribir código, complete el siguiente template:

Template de Planificación

Feature: [Nombre del feature]
Propósito: [Una oración describiendo el objetivo]

Entradas:
  - campo: tipo, reglas de validación
  - campo: tipo, reglas de validación

Salidas:
  Éxito:
    - código HTTP, estructura de respuesta
  Error:
    - código HTTP, estructura de error

Casos Edge:
  - ¿Qué sucede si...?
  - ¿Qué sucede si...?

Seguridad:
  - Consideración 1
  - Consideración 2

Dependencias:
  - Librería/servicio requerido

Ejemplo Aplicado: Registro de Usuario

Feature: User Registration Endpoint
Propósito: Crear nuevos usuarios con validación completa y almacenamiento seguro

Entradas:
  - email: string, formato RFC 5322, único en BD
  - password: string, mínimo 8 caracteres, al menos 1 número y 1 mayúscula
  - username: string, 3-20 caracteres alfanuméricos

Salidas:
  Éxito:
    - 201 Created, { user: { id, email, username, createdAt } }
  Error:
    - 400 Bad Request, { errors: [{ field, message }] }
    - 409 Conflict, { error: "El email ya está registrado" }
    - 500 Internal Error, { error: "Error interno del servidor" }

Casos Edge:
  - Email con formato válido pero dominio inexistente
  - Password que cumple requisitos mínimos pero es común (123456Aa)
  - Username con caracteres Unicode que parecen alfanuméricos
  - Solicitudes duplicadas simultáneas (race condition)

Seguridad:
  - Hash de password con bcrypt (cost factor 12)
  - Queries parametrizadas (prevenir SQL injection)
  - Rate limiting en endpoint
  - No revelar si email existe en mensaje de error genérico
  - Sanitización de inputs antes de logging

Dependencias:
  - bcrypt para hashing
  - zod para validación
  - express-rate-limit para throttling

Fase 2: Generación con Contexto

Con el template completo, se construye un prompt estructurado que incluye todos los requisitos.

Mejores Prácticas de Generación

Práctica Descripción
Una tarea por prompt Generar un endpoint, componente o función a la vez
Contexto limpio Nuevo chat para cada tarea, evitar contextos contaminados
Stack explícito Especificar versiones, convenciones y estructura del proyecto
Iteración deliberada 2-3 iteraciones refinando, no esperar perfección inicial

Prompt Estructurado

Crear un endpoint Express + TypeScript para registro de usuarios.

REQUISITOS TÉCNICOS:
- POST /api/v1/auth/register
- Validación con Zod
- Hash con bcrypt (12 rounds)
- Queries parametrizadas con pg (node-postgres)
- TypeScript strict mode

VALIDACIONES:
- email: formato RFC 5322
- password: mínimo 8 chars, 1 número, 1 mayúscula
- username: 3-20 chars alfanuméricos

RESPUESTAS:
- 201: { user: { id, email, username, createdAt } }
- 400: { errors: [{ field: string, message: string }] }
- 409: { error: string } para email duplicado
- 500: { error: string } genérico

SEGURIDAD:
- No incluir password en ninguna respuesta
- Mensaje genérico para email duplicado
- Logging sin datos sensibles

ESTRUCTURA DEL PROYECTO:
src/
  controllers/
  middleware/
  validators/
  types/

Fase 3: Validación Automatizada

El código generado requiere validación con una segunda perspectiva especializada en seguridad.

Arquitectura de Validación

Opción 1: Claude Code como Revisor de Seguridad

Claude Code permite ejecutar análisis de seguridad directamente desde la terminal. Configuración recomendada:

Instalación y Setup

# Instalar Claude Code
npm install -g @anthropic-ai/claude-code

# Instalar herramientas de análisis
npm install -D eslint @eslint/js eslint-plugin-security
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin

Configuración ESLint para Seguridad

// eslint.config.js
import security from 'eslint-plugin-security';
import tseslint from '@typescript-eslint/eslint-plugin';

export default [
  {
    plugins: {
      security,
      '@typescript-eslint': tseslint
    },
    rules: {
      'security/detect-object-injection': 'error',
      'security/detect-non-literal-regexp': 'error',
      'security/detect-unsafe-regex': 'error',
      'security/detect-buffer-noassert': 'error',
      'security/detect-child-process': 'warn',
      'security/detect-disable-mustache-escape': 'error',
      'security/detect-eval-with-expression': 'error',
      'security/detect-no-csrf-before-method-override': 'error',
      'security/detect-non-literal-fs-filename': 'warn',
      'security/detect-non-literal-require': 'warn',
      'security/detect-possible-timing-attacks': 'error',
      'security/detect-pseudoRandomBytes': 'error',
      'security/detect-sql-injection': 'error'
    }
  }
];

Prompt de Revisión para Claude Code

Crear un archivo CLAUDE.md en la raíz del proyecto con instrucciones de revisión:

# Instrucciones de Revisión de Seguridad

Al revisar código, analizar sistemáticamente:

## 1. Inyección
- [ ] SQL Injection: ¿Se usan queries parametrizadas?
- [ ] NoSQL Injection: ¿Se validan operadores de MongoDB?
- [ ] Command Injection: ¿Se sanitizan inputs para exec/spawn?
- [ ] XSS: ¿Se escapan outputs en templates?

## 2. Autenticación
- [ ] ¿Passwords hasheados con bcrypt/argon2 (cost >= 10)?
- [ ] ¿Tokens con expiración apropiada?
- [ ] ¿Rate limiting en endpoints de auth?

## 3. Autorización
- [ ] ¿Verificación de ownership en recursos?
- [ ] ¿RBAC/ABAC implementado correctamente?

## 4. Datos Sensibles
- [ ] ¿Secrets en variables de entorno (no hardcoded)?
- [ ] ¿Logging sin datos sensibles?
- [ ] ¿Respuestas sin información interna?

## 5. Dependencias
- [ ] Ejecutar: npm audit
- [ ] Verificar: no dependencias deprecated

## Comandos de Análisis
npx eslint --ext .ts,.js src/
npm audit --audit-level=moderate

Ejecutar Revisión con Claude Code

# Navegar al proyecto
cd mi-proyecto

# Iniciar revisión de seguridad
claude-code

# Dentro de Claude Code, ejecutar:
> Revisa el archivo src/controllers/auth.controller.ts 
> siguiendo las instrucciones de CLAUDE.md. 
> Ejecuta los comandos de análisis y reporta vulnerabilidades.

Opción 2: CodeRabbit (Integración CI/CD)

CodeRabbit se integra directamente con GitHub/GitLab para revisión automática en cada PR.

# .github/workflows/code-review.yml
name: Automated Code Review

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  security-review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          
      - name: Install Dependencies
        run: npm ci
        
      - name: Security Audit
        run: npm audit --audit-level=moderate
        
      - name: ESLint Security
        run: npx eslint --ext .ts,.js src/
        
      # CodeRabbit se activa automáticamente en PRs
      # después de instalarlo en el repositorio

Flujo de Decisión de Seguridad


Ejemplo Completo: De Vulnerable a Seguro

:cross_mark: Código Vulnerable (Generación sin validación)

// ⚠️ NO USAR EN PRODUCCIÓN - Solo para demostración
import express from 'express';
import { Pool } from 'pg';

const app = express();
const pool = new Pool();

app.post('/api/register', async (req, res) => {
  const { email, password, username } = req.body;
  
  // 🔴 SQL Injection
  const result = await pool.query(
    `INSERT INTO users (email, password, username) 
     VALUES ('${email}', '${password}', '${username}') 
     RETURNING *`
  );
  
  // 🔴 Password en respuesta
  res.json({ success: true, user: result.rows[0] });
});

Vulnerabilidades detectadas:

  1. Inyección SQL por concatenación de strings
  2. Password almacenado en texto plano
  3. Password retornado en respuesta
  4. Sin validación de entrada
  5. Sin manejo de errores
  6. Sin rate limiting

:white_check_mark: Código Seguro (Post-validación)

// src/controllers/auth.controller.ts
import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import bcrypt from 'bcrypt';
import { pool } from '../config/database';
import { AppError } from '../middleware/errorHandler';

// Esquema de validación
const registerSchema = z.object({
  email: z
    .string()
    .email('Formato de email inválido')
    .max(255, 'Email demasiado largo'),
  password: z
    .string()
    .min(8, 'Mínimo 8 caracteres')
    .regex(/[0-9]/, 'Debe contener al menos un número')
    .regex(/[A-Z]/, 'Debe contener al menos una mayúscula'),
  username: z
    .string()
    .min(3, 'Mínimo 3 caracteres')
    .max(20, 'Máximo 20 caracteres')
    .regex(/^[a-zA-Z0-9]+$/, 'Solo caracteres alfanuméricos')
});

// Tipos
interface UserResponse {
  id: string;
  email: string;
  username: string;
  createdAt: Date;
}

interface RegisterBody {
  email: string;
  password: string;
  username: string;
}

export const register = async (
  req: Request<{}, {}, RegisterBody>,
  res: Response,
  next: NextFunction
): Promise<void> => {
  try {
    // 1. Validar input
    const validation = registerSchema.safeParse(req.body);
    
    if (!validation.success) {
      const errors = validation.error.errors.map(err => ({
        field: err.path.join('.'),
        message: err.message
      }));
      res.status(400).json({ errors });
      return;
    }
    
    const { email, password, username } = validation.data;
    
    // 2. Hash password (cost factor 12)
    const hashedPassword = await bcrypt.hash(password, 12);
    
    // 3. Query parametrizada (previene SQL injection)
    const query = `
      INSERT INTO users (email, password_hash, username, created_at)
      VALUES ($1, $2, $3, NOW())
      RETURNING id, email, username, created_at as "createdAt"
    `;
    
    const result = await pool.query<UserResponse>(query, [
      email.toLowerCase(),
      hashedPassword,
      username
    ]);
    
    // 4. Respuesta sin password
    res.status(201).json({
      user: result.rows[0]
    });
    
  } catch (error: unknown) {
    // 5. Manejo de errores específicos
    if (error instanceof Error && 'code' in error) {
      const pgError = error as { code: string };
      
      // Unique constraint violation (email duplicado)
      if (pgError.code === '23505') {
        // Mensaje genérico (no revelar si email existe)
        res.status(409).json({
          error: 'No se pudo completar el registro'
        });
        return;
      }
    }
    
    // Log sin datos sensibles
    console.error('Registration error:', {
      timestamp: new Date().toISOString(),
      path: req.path,
      // NO incluir: email, password, username
    });
    
    next(new AppError('Error interno del servidor', 500));
  }
};
// src/routes/auth.routes.ts
import { Router } from 'express';
import rateLimit from 'express-rate-limit';
import { register } from '../controllers/auth.controller';

const router = Router();

// Rate limiting: 5 intentos por IP cada 15 minutos
const registerLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: { error: 'Demasiados intentos, intente más tarde' },
  standardHeaders: true,
  legacyHeaders: false
});

router.post('/register', registerLimiter, register);

export default router;

Checklist de Implementación

Utilizar esta lista de verificación antes de cada deploy:

Pre-Generación

  • Template de planificación completado
  • Casos edge identificados
  • Requisitos de seguridad documentados

Post-Generación

  • Revisión manual de estructura
  • Convenciones del proyecto respetadas
  • Tipos TypeScript correctos

Validación Automatizada

  • npm audit sin vulnerabilidades críticas
  • ESLint security sin errores
  • Claude Code / CodeRabbit sin issues de seguridad

Seguridad Específica

  • Queries parametrizadas (no concatenación)
  • Passwords hasheados (bcrypt >= 10 rounds)
  • Inputs validados con esquema (Zod, Joi)
  • Errores sin información sensible
  • Rate limiting en endpoints críticos
  • CORS configurado correctamente

Conclusión

El workflow de tres fases transforma la generación de código con IA de un riesgo potencial a una ventaja competitiva:

  1. Planificar antes de generar reduce iteraciones y mejora la calidad del output
  2. Generar con contexto completo produce código más cercano a producción
  3. Validar con herramientas especializadas captura lo que la generación omite

La clave está en reconocer que la IA de generación y la IA de validación tienen objetivos diferentes y complementarios. Integrar ambas en un flujo sistemático permite mantener la velocidad de desarrollo sin sacrificar la seguridad.


Recursos Adicionales


Publicado en yoDEV.dev - La comunidad de desarrolladores de Latinoamérica