8Modulo 8

Flujos de Trabajo Avanzados

45 min

Objetivo

Dominar el desarrollo guiado por pruebas (TDD) usando Claude Code como asistente, aprendiendo a escribir tests primero, implementar código para pasarlos, y refactorizar con confianza.


Introducción

Test-Driven Development (TDD) es una metodología donde:

  1. Escribes el test PRIMERO (antes del código)
  2. El test falla (porque no hay implementación)
  3. Escribes el código mínimo para pasar el test
  4. El test pasa
  5. Refactorizas con confianza (los tests te protegen)

Ciclo TDD: Red-Green-Refactor

┌──────────────┐
│  RED         │  Escribe test que FALLE
│  ❌ Test    │  (no hay implementación)
└──────┬───────┘


┌──────────────┐
│  GREEN       │  Implementa código MÍNIMO
│  ✅ Test    │  para que pase el test
└──────┬───────┘


┌──────────────┐
│  REFACTOR    │  Mejora el código
│  🔧 Código  │  (tests garantizan que funciona)
└──────┬───────┘

       ↓ (repetir)

¿Por qué TDD con Claude Code?

Ventajas tradicionales de TDD

Código mejor diseñado: Al pensar en los tests primero, diseñas APIs más limpias ✅ Menos bugs: Tests capturan problemas temprano ✅ Refactor seguro: Puedes mejorar código sin miedo a romperlo ✅ Documentación viva: Los tests muestran cómo usar el código

Ventajas ADICIONALES con Claude Code

Claude genera tests completos: Casos edge incluidos automáticamente ✅ Claude implementa basándose en tests: Sabe exactamente qué hacer ✅ Claude refactoriza manteniendo tests verdes: Optimiza sin romper ✅ Feedback instantáneo: Claude ejecuta tests y reporta resultados


Flujo TDD con Claude Code

Paso 1: Especificar funcionalidad

Tú: Necesito una función que valide emails.
 
Debe:
- Retornar true para emails válidos
- Retornar false para emails inválidos
- Lanzar error si el input no es string

Paso 2: Claude escribe el test primero

Tú: Escribe primero el test para esta función
// tests/validateEmail.test.ts
 
import { validateEmail } from '../src/validateEmail';
 
describe('validateEmail', () => {
  describe('casos válidos', () => {
    test('retorna true para email válido simple', () => {
      expect(validateEmail('user@example.com')).toBe(true);
    });
 
    test('retorna true para email con subdominios', () => {
      expect(validateEmail('user@mail.example.com')).toBe(true);
    });
 
    test('retorna true para email con números', () => {
      expect(validateEmail('user123@example.com')).toBe(true);
    });
 
    test('retorna true para email con guiones', () => {
      expect(validateEmail('first-last@example.com')).toBe(true);
    });
 
    test('retorna true para email con plus', () => {
      expect(validateEmail('user+tag@example.com')).toBe(true);
    });
  });
 
  describe('casos inválidos', () => {
    test('retorna false para email sin @', () => {
      expect(validateEmail('userexample.com')).toBe(false);
    });
 
    test('retorna false para email sin dominio', () => {
      expect(validateEmail('user@')).toBe(false);
    });
 
    test('retorna false para email sin usuario', () => {
      expect(validateEmail('@example.com')).toBe(false);
    });
 
    test('retorna false para email con espacios', () => {
      expect(validateEmail('user @example.com')).toBe(false);
    });
 
    test('retorna false para string vacío', () => {
      expect(validateEmail('')).toBe(false);
    });
  });
 
  describe('casos de error', () => {
    test('lanza error si input no es string', () => {
      expect(() => validateEmail(null as any)).toThrow('Input must be a string');
      expect(() => validateEmail(undefined as any)).toThrow('Input must be a string');
      expect(() => validateEmail(123 as any)).toThrow('Input must be a string');
      expect(() => validateEmail({} as any)).toThrow('Input must be a string');
    });
  });
});

Paso 3: Ejecutar test (debe fallar)

Tú: Ejecuta el test
 
Claude: Ejecutando tests...
 
❌ FAIL tests/validateEmail.test.ts
  validateEmail
    ✗ retorna true para email válido simple
      Cannot find module '../src/validateEmail'
 
Tests: 0 passed, 13 failed

Perfecto: El test falla porque no existe la función aún.

Paso 4: Implementar código mínimo

Tú: Ahora implementa validateEmail para pasar estos tests
// src/validateEmail.ts
 
export function validateEmail(email: unknown): boolean {
  // Validar que es string
  if (typeof email !== 'string') {
    throw new Error('Input must be a string');
  }
 
  // Validar formato básico
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

Paso 5: Ejecutar test (debe pasar)

Tú: Ejecuta los tests de nuevo
 
Claude: Ejecutando tests...
 
✅ PASS tests/validateEmail.test.ts
  validateEmail
    casos válidos
      ✓ retorna true para email válido simple (2 ms)
      ✓ retorna true para email con subdominios (1 ms)
      ✓ retorna true para email con números (1 ms)
      ✓ retorna true para email con guiones (1 ms)
      ✓ retorna true para email con plus (1 ms)
    casos inválidos
      ✓ retorna false para email sin @ (1 ms)
      ✓ retorna false para email sin dominio (1 ms)
      ✓ retorna false para email sin usuario (1 ms)
      ✓ retorna false para email con espacios (1 ms)
      ✓ retorna false para string vacío (1 ms)
    casos de error
      ✓ lanza error si input no es string (2 ms)
 
Tests: 13 passed, 0 failed

Todos los tests pasan

Paso 6: Refactorizar

Tú: El código funciona, pero ¿puedes refactorizarlo para mejor legibilidad?
Mantén los tests pasando.
// src/validateEmail.ts
 
/**
 * Valida si un string tiene formato de email válido
 * @param email - String a validar
 * @returns true si es email válido, false si no
 * @throws Error si el input no es string
 */
export function validateEmail(email: unknown): boolean {
  validateInput(email);
  return hasValidEmailFormat(email);
}
 
function validateInput(email: unknown): asserts email is string {
  if (typeof email !== 'string') {
    throw new Error('Input must be a string');
  }
}
 
function hasValidEmailFormat(email: string): boolean {
  const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return EMAIL_PATTERN.test(email);
}
Tú: Ejecuta tests
 
Claude:
✅ PASS tests/validateEmail.test.ts
Tests: 13 passed, 0 failed
 
Refactor exitoso: el código está más limpio y los tests siguen pasando.

Ejemplo completo: API Endpoint con TDD

Escenario

Necesitas un endpoint POST /api/users que cree usuarios.

Paso 1: Especificación

Tú: Vamos a crear un endpoint POST /api/users con TDD.
 
Requisitos:
- Recibe { email, password, name } en body
- Valida que email sea válido
- Valida que password tenga mínimo 8 caracteres
- Hashea el password con bcrypt
- Guarda usuario en base de datos
- Retorna { id, email, name } (sin password)
- Retorna 400 si validación falla
- Retorna 409 si email ya existe
 
Usa Express + TypeScript.
Empieza escribiendo los tests primero.

Paso 2: Tests primero

// tests/users.test.ts
 
import request from 'supertest';
import { app } from '../src/app';
import { db } from '../src/db';
 
describe('POST /api/users', () => {
  beforeEach(async () => {
    // Limpiar base de datos antes de cada test
    await db.users.deleteMany({});
  });
 
  afterAll(async () => {
    await db.disconnect();
  });
 
  describe('casos exitosos', () => {
    test('crea usuario con datos válidos', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'test@example.com',
          password: 'securePassword123',
          name: 'Test User'
        });
 
      expect(response.status).toBe(201);
      expect(response.body).toEqual({
        id: expect.any(String),
        email: 'test@example.com',
        name: 'Test User'
      });
      expect(response.body.password).toBeUndefined();
    });
 
    test('hashea el password antes de guardar', async () => {
      await request(app)
        .post('/api/users')
        .send({
          email: 'test@example.com',
          password: 'myPassword123',
          name: 'Test User'
        });
 
      const user = await db.users.findOne({ email: 'test@example.com' });
      expect(user.password).not.toBe('myPassword123');
      expect(user.password).toMatch(/^\$2[aby]\$.{56}$/); // bcrypt hash pattern
    });
  });
 
  describe('validación de email', () => {
    test('retorna 400 si email está vacío', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: '',
          password: 'securePassword123',
          name: 'Test User'
        });
 
      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Invalid email');
    });
 
    test('retorna 400 si email es inválido', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'not-an-email',
          password: 'securePassword123',
          name: 'Test User'
        });
 
      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Invalid email');
    });
  });
 
  describe('validación de password', () => {
    test('retorna 400 si password tiene menos de 8 caracteres', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'test@example.com',
          password: 'short',
          name: 'Test User'
        });
 
      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Password must be at least 8 characters');
    });
 
    test('retorna 400 si password está vacío', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'test@example.com',
          password: '',
          name: 'Test User'
        });
 
      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Password must be at least 8 characters');
    });
  });
 
  describe('validación de name', () => {
    test('retorna 400 si name está vacío', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'test@example.com',
          password: 'securePassword123',
          name: ''
        });
 
      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Name is required');
    });
  });
 
  describe('duplicados', () => {
    test('retorna 409 si email ya existe', async () => {
      // Crear primer usuario
      await request(app)
        .post('/api/users')
        .send({
          email: 'duplicate@example.com',
          password: 'password123',
          name: 'First User'
        });
 
      // Intentar crear otro con mismo email
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'duplicate@example.com',
          password: 'differentPassword123',
          name: 'Second User'
        });
 
      expect(response.status).toBe(409);
      expect(response.body.error).toBe('Email already exists');
    });
  });
 
  describe('campos faltantes', () => {
    test('retorna 400 si falta email', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          password: 'password123',
          name: 'Test User'
        });
 
      expect(response.status).toBe(400);
      expect(response.body.error).toContain('email');
    });
 
    test('retorna 400 si falta password', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'test@example.com',
          name: 'Test User'
        });
 
      expect(response.status).toBe(400);
      expect(response.body.error).toContain('password');
    });
 
    test('retorna 400 si falta name', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          email: 'test@example.com',
          password: 'password123'
        });
 
      expect(response.status).toBe(400);
      expect(response.body.error).toContain('name');
    });
  });
});

Paso 3: Ejecutar tests (fallan)

Tú: Ejecuta los tests
 
Claude:
❌ FAIL tests/users.test.ts
  Cannot find module '../src/app'
 
Tests: 0 passed, 14 failed
 
Perfecto, los tests fallan porque no hay implementación.

Paso 4: Implementar código

Tú: Ahora implementa el código para pasar estos tests
// src/routes/users.ts
 
import { Router, Request, Response } from 'express';
import bcrypt from 'bcrypt';
import { db } from '../db';
import { validateEmail } from '../utils/validateEmail';
 
const router = Router();
 
interface CreateUserRequest {
  email: string;
  password: string;
  name: string;
}
 
router.post('/api/users', async (req: Request, res: Response) => {
  try {
    const { email, password, name } = req.body as CreateUserRequest;
 
    // Validar campos requeridos
    if (!email || !password || !name) {
      return res.status(400).json({
        error: 'email, password, and name are required'
      });
    }
 
    // Validar email
    if (!validateEmail(email)) {
      return res.status(400).json({ error: 'Invalid email' });
    }
 
    // Validar password
    if (password.length < 8) {
      return res.status(400).json({
        error: 'Password must be at least 8 characters'
      });
    }
 
    // Validar name
    if (name.trim().length === 0) {
      return res.status(400).json({ error: 'Name is required' });
    }
 
    // Verificar si email ya existe
    const existingUser = await db.users.findOne({ email });
    if (existingUser) {
      return res.status(409).json({ error: 'Email already exists' });
    }
 
    // Hash password
    const hashedPassword = await bcrypt.hash(password, 10);
 
    // Crear usuario
    const user = await db.users.create({
      email,
      password: hashedPassword,
      name
    });
 
    // Retornar sin password
    res.status(201).json({
      id: user.id,
      email: user.email,
      name: user.name
    });
  } catch (error) {
    console.error('Error creating user:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});
 
export default router;
// src/app.ts
 
import express from 'express';
import usersRouter from './routes/users';
 
export const app = express();
 
app.use(express.json());
app.use(usersRouter);

Paso 5: Ejecutar tests (pasan)

Tú: Ejecuta los tests
 
Claude:
✅ PASS tests/users.test.ts
  POST /api/users
    casos exitosos
      ✓ crea usuario con datos válidos (45 ms)
      ✓ hashea el password antes de guardar (38 ms)
    validación de email
      ✓ retorna 400 si email está vacío (12 ms)
      ✓ retorna 400 si email es inválido (11 ms)
    validación de password
      ✓ retorna 400 si password tiene menos de 8 caracteres (10 ms)
      ✓ retorna 400 si password está vacío (9 ms)
    validación de name
      ✓ retorna 400 si name está vacío (10 ms)
    duplicados
      ✓ retorna 409 si email ya existe (52 ms)
    campos faltantes
      ✓ retorna 400 si falta email (8 ms)
      ✓ retorna 400 si falta password (9 ms)
      ✓ retorna 400 si falta name (8 ms)
 
Tests: 14 passed, 0 failed
Time: 2.154 s

Paso 6: Refactorizar

Tú: El código funciona pero está un poco largo.
Refactorízalo extrayendo la validación a funciones separadas.
Asegúrate de que los tests sigan pasando.
// src/routes/users.ts (refactorizado)
 
import { Router, Request, Response } from 'express';
import bcrypt from 'bcrypt';
import { db } from '../db';
import { validateEmail } from '../utils/validateEmail';
 
const router = Router();
 
interface CreateUserRequest {
  email: string;
  password: string;
  name: string;
}
 
interface ValidationError {
  error: string;
}
 
router.post('/api/users', async (req: Request, res: Response) => {
  try {
    const { email, password, name } = req.body as CreateUserRequest;
 
    // Validar input
    const validationError = validateUserInput({ email, password, name });
    if (validationError) {
      return res.status(400).json(validationError);
    }
 
    // Verificar duplicado
    if (await emailExists(email)) {
      return res.status(409).json({ error: 'Email already exists' });
    }
 
    // Crear usuario
    const user = await createUser({ email, password, name });
 
    // Retornar respuesta
    res.status(201).json(sanitizeUser(user));
  } catch (error) {
    console.error('Error creating user:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});
 
function validateUserInput(
  input: CreateUserRequest
): ValidationError | null {
  const { email, password, name } = input;
 
  if (!email || !password || !name) {
    return { error: 'email, password, and name are required' };
  }
 
  if (!validateEmail(email)) {
    return { error: 'Invalid email' };
  }
 
  if (password.length < 8) {
    return { error: 'Password must be at least 8 characters' };
  }
 
  if (name.trim().length === 0) {
    return { error: 'Name is required' };
  }
 
  return null;
}
 
async function emailExists(email: string): Promise<boolean> {
  const user = await db.users.findOne({ email });
  return user !== null;
}
 
async function createUser(data: CreateUserRequest) {
  const hashedPassword = await bcrypt.hash(data.password, 10);
 
  return db.users.create({
    email: data.email,
    password: hashedPassword,
    name: data.name
  });
}
 
function sanitizeUser(user: any) {
  return {
    id: user.id,
    email: user.email,
    name: user.name
  };
}
 
export default router;
Tú: Ejecuta tests
 
Claude:
✅ PASS tests/users.test.ts
Tests: 14 passed, 0 failed
 
¡Refactor exitoso! El código está más organizado y los tests garantizan
que la funcionalidad no cambió.

Ventajas de TDD con Claude Code

1. Claude genera tests exhaustivos

Tú: Escribe tests para una función calculateDiscount(price, code)
 
Claude genera automáticamente tests para:
  ✓ Happy path
  ✓ Casos edge (precio 0, negativo, decimal)
  ✓ Códigos inválidos
  ✓ Validación de tipos
  ✓ Casos límite

2. Claude implementa exactamente lo necesario

Al tener tests claros, Claude sabe exactamente qué implementar (no más, no menos).

3. Refactor sin miedo

Tú: Refactoriza esta función para mejor rendimiento
 
Claude:
- Optimiza el código
- Ejecuta tests automáticamente
- Si algo se rompe, lo detecta inmediatamente
- Ajusta hasta que todos los tests pasen

4. Documentación viva

Los tests sirven como ejemplos de uso:

test('calcula descuento del 20% con código SAVE20', () => {
  expect(calculateDiscount(100, 'SAVE20')).toBe(80);
});
 
// Este test documenta cómo usar la función

Patrones comunes de TDD con Claude

Patrón 1: Feature completa con TDD

Tú: Vamos a implementar autenticación JWT con TDD.
 
Paso 1: Escribe tests para generateToken(userId)
Paso 2: Implementa generateToken
Paso 3: Tests pasan ✅
 
Paso 4: Escribe tests para verifyToken(token)
Paso 5: Implementa verifyToken
Paso 6: Tests pasan ✅
 
Paso 7: Escribe tests para refreshToken(token)
Paso 8: Implementa refreshToken
Paso 9: Tests pasan ✅
 
Resultado: Sistema de auth completo con tests

Patrón 2: Bug fix con TDD

Tú: Hay un bug: calculateTotal falla con arrays vacíos
 
Paso 1: Escribe un test que reproduzca el bug
test('maneja array vacío', () => {
  expect(calculateTotal([])).toBe(0);
});
 
Paso 2: Ejecuta test (falla ❌)
 
Paso 3: Arregla el código
 
Paso 4: Test pasa ✅
 
Paso 5: El bug nunca volverá (test lo previene)

Patrón 3: Refactor con red de seguridad

Tú: Este código funciona pero es difícil de leer. Refactorízalo.
 
Claude:
1. Primero, genero tests para el código actual
2. Ejecuto tests → todos pasan ✅
3. Ahora refactorizo el código
4. Ejecuto tests después de cada cambio
5. Si algo se rompe, lo detecto inmediatamente
6. Resultado: código limpio + tests garantizan funcionalidad

Tips para TDD efectivo con Claude

✅ Buenas prácticas

1. Sé específico en los requisitos

❌ Malo:
"Crea una función de validación"
 
✅ Bueno:
"Crea validatePassword que:
- Retorna true si >= 8 caracteres
- Retorna true si tiene al menos 1 número
- Retorna true si tiene al menos 1 mayúscula
- Retorna false si no cumple lo anterior
- Lanza error si input no es string"

2. Pide tests primero SIEMPRE

Tú: Implementa función calculateShipping
 
Claude: [Empieza a implementar]
 
❌ Detén: "Espera, escribe los tests primero"
 
✅ Desde el inicio: "Escribe tests primero para calculateShipping"

3. Verifica que el test falle antes de implementar

1. Claude escribe test
2. Tú: "Ejecuta el test" → Debe fallar ❌
3. Si pasa sin implementación = test está mal

4. Un ciclo a la vez

No hagas:
- Escribir 10 tests
- Implementar todo de golpe
 
Haz:
- Escribir 1-3 tests relacionados
- Implementar para pasarlos
- Refactorizar
- Repetir

5. Mantén tests simples y legibles

Bueno:
test('retorna true para email válido', () => {
  expect(validateEmail('user@example.com')).toBe(true);
});
 
Malo:
test('test1', () => {
  const data = { /* 50 líneas de setup */ };
  const result = complexFunction(data);
  expect(result.nested.deeply.value[0].prop).toBe(/* algo */);
});

⚠️ Evita

1. No implementes antes del test

❌ Malo:
Tú: "Implementa esta función"
[Después] "Ahora escribe tests"
 
✅ Bueno:
Tú: "Escribe tests primero"
[Tests fallan]
"Ahora implementa"

2. No escribas tests que siempre pasan

Malo:
test('función existe', () => {
  expect(myFunction).toBeDefined();
});
 
Bueno:
test('retorna resultado correcto', () => {
  expect(myFunction(input)).toBe(expectedOutput);
});

3. No saltees la fase de refactor

❌ Malo:
Tests pasan → "Listo, siguiente feature"
 
✅ Bueno:
Tests pasan → "¿Puedo mejorar este código?"
Refactorizar → Tests siguen pasando → Listo

Ejemplo avanzado: Componente React con TDD

Escenario

Crear un componente LoginForm con TDD.

Paso 1: Tests primero

// LoginForm.test.tsx
 
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { LoginForm } from './LoginForm';
 
describe('LoginForm', () => {
  test('renderiza email y password inputs', () => {
    render(<LoginForm onSubmit={jest.fn()} />);
 
    expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
  });
 
  test('renderiza botón de submit', () => {
    render(<LoginForm onSubmit={jest.fn()} />);
 
    expect(screen.getByRole('button', { name: /log in/i })).toBeInTheDocument();
  });
 
  test('llama onSubmit con email y password', async () => {
    const handleSubmit = jest.fn();
    render(<LoginForm onSubmit={handleSubmit} />);
 
    fireEvent.change(screen.getByLabelText(/email/i), {
      target: { value: 'test@example.com' }
    });
    fireEvent.change(screen.getByLabelText(/password/i), {
      target: { value: 'password123' }
    });
 
    fireEvent.click(screen.getByRole('button', { name: /log in/i }));
 
    await waitFor(() => {
      expect(handleSubmit).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123'
      });
    });
  });
 
  test('muestra error si email está vacío', async () => {
    render(<LoginForm onSubmit={jest.fn()} />);
 
    fireEvent.click(screen.getByRole('button', { name: /log in/i }));
 
    expect(await screen.findByText(/email is required/i)).toBeInTheDocument();
  });
 
  test('muestra error si password está vacío', async () => {
    render(<LoginForm onSubmit={jest.fn()} />);
 
    fireEvent.change(screen.getByLabelText(/email/i), {
      target: { value: 'test@example.com' }
    });
 
    fireEvent.click(screen.getByRole('button', { name: /log in/i }));
 
    expect(await screen.findByText(/password is required/i)).toBeInTheDocument();
  });
 
  test('deshabilita botón mientras está submitting', async () => {
    const slowSubmit = () => new Promise(resolve => setTimeout(resolve, 100));
    render(<LoginForm onSubmit={slowSubmit} />);
 
    fireEvent.change(screen.getByLabelText(/email/i), {
      target: { value: 'test@example.com' }
    });
    fireEvent.change(screen.getByLabelText(/password/i), {
      target: { value: 'password123' }
    });
 
    const button = screen.getByRole('button', { name: /log in/i });
    fireEvent.click(button);
 
    expect(button).toBeDisabled();
    expect(button).toHaveTextContent(/logging in/i);
  });
 
  test('muestra error del servidor', async () => {
    const handleSubmit = jest.fn().mockRejectedValue(
      new Error('Invalid credentials')
    );
    render(<LoginForm onSubmit={handleSubmit} />);
 
    fireEvent.change(screen.getByLabelText(/email/i), {
      target: { value: 'test@example.com' }
    });
    fireEvent.change(screen.getByLabelText(/password/i), {
      target: { value: 'wrong' }
    });
 
    fireEvent.click(screen.getByRole('button', { name: /log in/i }));
 
    expect(await screen.findByText(/invalid credentials/i)).toBeInTheDocument();
  });
});

Paso 2: Implementar componente

// LoginForm.tsx
 
import { useState, FormEvent } from 'react';
 
interface LoginFormProps {
  onSubmit: (credentials: { email: string; password: string }) => Promise<void>;
}
 
export function LoginForm({ onSubmit }: LoginFormProps) {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState<{ email?: string; password?: string; server?: string }>({});
  const [isSubmitting, setIsSubmitting] = useState(false);
 
  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
 
    // Limpiar errores previos
    setErrors({});
 
    // Validar
    const newErrors: typeof errors = {};
    if (!email) newErrors.email = 'Email is required';
    if (!password) newErrors.password = 'Password is required';
 
    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors);
      return;
    }
 
    // Submit
    setIsSubmitting(true);
    try {
      await onSubmit({ email, password });
    } catch (error) {
      setErrors({ server: error.message });
    } finally {
      setIsSubmitting(false);
    }
  };
 
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        {errors.email && <span role="alert">{errors.email}</span>}
      </div>
 
      <div>
        <label htmlFor="password">Password</label>
        <input
          id="password"
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        {errors.password && <span role="alert">{errors.password}</span>}
      </div>
 
      {errors.server && <div role="alert">{errors.server}</div>}
 
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Logging in...' : 'Log in'}
      </button>
    </form>
  );
}

Paso 3: Tests pasan ✅

✅ PASS LoginForm.test.tsx
  LoginForm
    ✓ renderiza email y password inputs
    ✓ renderiza botón de submit
    ✓ llama onSubmit con email y password
    ✓ muestra error si email está vacío
    ✓ muestra error si password está vacío
    ✓ deshabilita botón mientras está submitting
    ✓ muestra error del servidor
 
Tests: 7 passed, 0 failed

Resumen

TDD con Claude Code combina lo mejor de ambos mundos:

Tests primero → Diseño mejor pensado ✅ Claude genera tests exhaustivos → Cobertura completa ✅ Implementación guiada → Claude sabe exactamente qué hacer ✅ Refactor seguro → Tests protegen contra regresiones ✅ Feedback instantáneo → Claude ejecuta tests automáticamente

Ciclo TDD:

  1. 🔴 RED: Test que falla
  2. 🟢 GREEN: Código mínimo que pasa
  3. 🔧 REFACTOR: Mejorar con seguridad

Próximo punto (8.2): Aprenderás técnicas de debugging acelerado con Claude Code.


Verificación de comprensión

Antes de continuar:

  • Entiendes el ciclo Red-Green-Refactor
  • Sabes pedir a Claude que escriba tests primero
  • Comprendes por qué verificar que el test falle inicialmente
  • Puedes aplicar TDD a funciones, APIs y componentes
  • Reconoces las ventajas de TDD con Claude Code

8.2 Debugging acelerado

Objetivo

Dominar técnicas de debugging eficiente con Claude Code, aprendiendo a diagnosticar y resolver errores rápidamente mediante análisis de stack traces, reproducción de bugs y corrección sistemática.


Introducción

El debugging tradicional puede ser lento y frustrante:

❌ Proceso tradicional:
1. Ves el error → 😰
2. Lees el stack trace → 🤔
3. Googlas el error → 🔍
4. Lees Stack Overflow → 📚
5. Pruebas solución 1 → No funciona
6. Pruebas solución 2 → No funciona
7. Pruebas solución 3 → Funciona!
   (2 horas después) → 😓
 
✅ Con Claude Code:
1. Copias el error completo
2. Se lo pasas a Claude
3. Claude analiza, identifica causa, propone solución
4. Aplicas la solución
5. Verificas
   (5 minutos después) → ✅

El proceso de debugging acelerado

Paso 1: Capturar el error completo

Información crucial a incluir:

  1. Stack trace completo
  2. Mensaje de error
  3. Código que causó el error
  4. Contexto (qué estabas haciendo)
  5. Cambios recientes (si aplica)

Ejemplo de captura completa

Usuario: Tengo este error:
 
Error: Cannot read property 'map' of undefined
    at UserList.render (UserList.tsx:23:18)
    at finishClassComponent (react-dom.development.js:17485:31)
    at updateClassComponent (react-dom.development.js:17435:24)
    at beginWork (react-dom.development.js:19073:16)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3945:14)
 
Código donde ocurre (UserList.tsx):
 
21: function UserList({ users }) {
22:   return (
23:     <ul>
24:       {users.map(user => (
25:         <li key={user.id}>{user.name}</li>
26:       ))}
27:     </ul>
28:   );
29: }
 
Contexto:
- Ocurre al cargar la página de usuarios
- Funcionaba ayer, dejó de funcionar hoy
- Actualicé la API que devuelve los usuarios

Caso 1: Error de runtime

Error capturado

Usuario: Este error aparece en producción:
 
TypeError: Cannot read properties of null (reading 'token')
    at verifyAuth (middleware/auth.js:15:29)
    at Layer.handle [as handle_request] (express/lib/router/layer.js:95:5)
    at next (express/lib/router/route.js:144:13)
    at Route.dispatch (express/lib/router/route.js:114:3)
 
Código (middleware/auth.js):
 
12: function verifyAuth(req, res, next) {
13:   const authHeader = req.headers.authorization;
14:
15:   const token = authHeader.split(' ')[1];
16:
17:   jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
18:     if (err) return res.status(401).json({ error: 'Invalid token' });
19:     req.userId = decoded.userId;
20:     next();
21:   });
22: }
 
Contexto:
- Algunos usuarios reportan este error al hacer requests autenticados
- No todos los usuarios lo experimentan

Análisis de Claude

Claude: Identifiqué el problema:
 
🔍 CAUSA RAÍZ:
El error ocurre en la línea 15 cuando `authHeader` es null o undefined.
 
Esto sucede cuando:
1. El request no incluye header `Authorization`
2. Algunos clientes no están enviando el header
 
🐛 PROBLEMA ESPECÍFICO:
authHeader.split(' ')[1]

         Si authHeader es null, esto lanza TypeError
 
👥 POR QUÉ ALGUNOS USUARIOS SÍ Y OTROS NO:
- Usuarios con sesión expirada → No envían header → Error
- Usuarios con sesión válida → Envían header → Funciona
 
✅ SOLUCIÓN:
 
function verifyAuth(req, res, next) {
  const authHeader = req.headers.authorization;
 
  // VALIDAR: Header existe
  if (!authHeader) {
    return res.status(401).json({
      error: 'Authorization header missing'
    });
  }
 
  // VALIDAR: Header tiene formato correcto
  const parts = authHeader.split(' ');
  if (parts.length !== 2 || parts[0] !== 'Bearer') {
    return res.status(401).json({
      error: 'Invalid authorization format. Use: Bearer <token>'
    });
  }
 
  const token = parts[1];
 
  jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
    if (err) return res.status(401).json({ error: 'Invalid token' });
    req.userId = decoded.userId;
    next();
  });
}
 
🎯 CAMBIOS:
1. Validar que authHeader existe antes de usar .split()
2. Validar formato "Bearer <token>"
3. Mensajes de error más descriptivos
 
🧪 TESTING:
Prueba con estos casos:
1. Sin header Authorization → 401 "Authorization header missing"
2. Header malformado "token123" → 401 "Invalid authorization format"
3. Header correcto "Bearer token123" → Funciona

Aplicar y verificar

Usuario: Aplica la solución
 
Claude: [Actualiza el archivo]
 
Ahora ejecuta estos tests para verificar:
 
curl http://localhost:3000/api/users
→ Debería retornar 401 "Authorization header missing"
 
curl -H "Authorization: malformed" http://localhost:3000/api/users
→ Debería retornar 401 "Invalid authorization format"
 
curl -H "Authorization: Bearer valid_token" http://localhost:3000/api/users
→ Debería funcionar correctamente

Caso 2: Error de build/compilación

Error capturado

Usuario: Mi build de TypeScript está fallando:
 
error TS2339: Property 'username' does not exist on type 'User'.
 
34   const greeting = `Hello, ${user.username}`;
                                    ~~~~~~~~
 
  src/types/User.ts:3:5
    3     name: string;
          ~~~~
    'name' is declared here.
 
Build failed.
 
Código (src/components/Greeting.tsx):
 
32: function Greeting({ user }: { user: User }) {
33:   return (
34:     <div>Hello, {user.username}</div>
35:   );
36: }
 
Interfaz (src/types/User.ts):
 
1: export interface User {
2:   id: string;
3:   name: string;
4:   email: string;
5: }
 
Contexto:
- Funcionaba antes
- Alguien del equipo cambió la interfaz User ayer

Análisis de Claude

Claude: Problema identificado:
 
🔍 CAUSA:
La interfaz User tiene la propiedad `name`, pero el código usa `username`.
 
📝 HISTORIA:
Alguien renombró `username` → `name` en la interfaz,
pero no actualizó todos los usos en el código.
 
🔎 BUSCANDO TODOS LOS USOS:
[Claude busca automáticamente...]
 
Encontré 5 archivos que usan user.username:
1. src/components/Greeting.tsx:34
2. src/components/Profile.tsx:18
3. src/components/UserCard.tsx:42
4. src/utils/formatUser.ts:12
5. src/services/userService.ts:67
 
✅ SOLUCIONES POSIBLES:
 
OPCIÓN 1: Cambiar código para usar `name`
Pros: Se alinea con la nueva interfaz
Contras: Muchos archivos que actualizar
 
OPCIÓN 2: Añadir `username` como alias en la interfaz
Pros: No rompe código existente
Contras: Duplicación, eventual deuda técnica
 
OPCIÓN 3: Cambiar interfaz de vuelta a `username`
Pros: Nada que cambiar en el código
Contras: Inconsistente si otros tipos usan `name`
 
💡 RECOMENDACIÓN: Opción 1 (actualizar código)
 
Razón: Es mejor alinearse con la convención del equipo.
Si otros tipos de usuario usan `name`, mantén consistencia.
 
¿Quieres que actualice los 5 archivos automáticamente?

Aplicar solución

Usuario: Sí, actualiza los 5 archivos
 
Claude: Actualizando archivos...
 
✓ src/components/Greeting.tsx
  - user.username → user.name
 
✓ src/components/Profile.tsx
  - user.username → user.name
 
✓ src/components/UserCard.tsx
  - user.username → user.name
 
✓ src/utils/formatUser.ts
  - user.username → user.name
 
✓ src/services/userService.ts
  - user.username → user.name
 
Ejecutando build...
✅ Build exitoso
 
Tests...
✅ 47 tests passed

Caso 3: Error lógico (bug sutil)

Problema reportado

Usuario: Tengo un bug extraño en mi función de cálculo:
 
La función calculateTotal a veces retorna valores incorrectos.
 
Ejemplos:
- calculateTotal([10, 20, 30]) → 60 ✅ Correcto
- calculateTotal([5, 5, 5]) → 15 ✅ Correcto
- calculateTotal([1.1, 2.2, 3.3]) → 6.6000000000000005 ❌ Incorrecto
- calculateTotal([9.99, 9.99]) → 19.98 ✅ Correcto
- calculateTotal([0.1, 0.2]) → 0.30000000000000004 ❌ Incorrecto
 
Código:
 
function calculateTotal(prices: number[]): number {
  return prices.reduce((sum, price) => sum + price, 0);
}

Análisis de Claude

Claude: Identifiqué el problema:
 
🔍 CAUSA:
Problema clásico de precisión de punto flotante en JavaScript.
 
📚 EXPLICACIÓN:
JavaScript usa IEEE 754 para números flotantes.
Algunos decimales no pueden representarse exactamente en binario:
 
0.1 en binario = 0.0001100110011001100... (infinito)
0.2 en binario = 0.001100110011001100... (infinito)
 
Al sumar, los errores de redondeo se acumulan:
0.1 + 0.2 = 0.30000000000000004
 
🐛 POR QUÉ ALGUNOS FUNCIONAN:
- Enteros (10, 20, 30) → Sin decimales, sin problema
- 9.99 → Casualidad, se representa exactamente
- 0.1, 0.2 → No se pueden representar exactamente
 
✅ SOLUCIONES:
 
OPCIÓN 1: Redondear resultado (simple)
function calculateTotal(prices: number[]): number {
  const total = prices.reduce((sum, price) => sum + price, 0);
  return Math.round(total * 100) / 100; // 2 decimales
}
 
OPCIÓN 2: Trabajar con centavos (recomendado para dinero)
function calculateTotal(prices: number[]): number {
  // Convertir a centavos (enteros)
  const totalCents = prices.reduce(
    (sum, price) => sum + Math.round(price * 100),
    0
  );
  // Convertir de vuelta a dólares
  return totalCents / 100;
}
 
OPCIÓN 3: Usar biblioteca de precisión decimal
import Decimal from 'decimal.js';
 
function calculateTotal(prices: number[]): number {
  return prices
    .reduce(
      (sum, price) => sum.plus(price),
      new Decimal(0)
    )
    .toNumber();
}
 
💡 RECOMENDACIÓN para tu caso:
Opción 2 (centavos) si es dinero real
Opción 1 (redondeo) si solo necesitas 2 decimales
 
🧪 TESTS SUGERIDOS:
test('maneja decimales correctamente', () => {
  expect(calculateTotal([0.1, 0.2])).toBe(0.3);
  expect(calculateTotal([1.1, 2.2, 3.3])).toBe(6.6);
  expect(calculateTotal([9.99, 0.01])).toBe(10.00);
});

Caso 4: Error de dependencia/versión

Error capturado

Usuario: Después de actualizar dependencias, mi app no arranca:
 
Error: Cannot find module 'react-dom/client'
Require stack:
- /app/src/index.tsx
- /app/node_modules/react-scripts/scripts/start.js
 
npm ls react
myapp@1.0.0
├── react@17.0.2
└─┬ react-scripts@5.0.1
  └── react@18.2.0
 
Contexto:
- Actualicé React de 17 a 18
- Olvidé actualizar react-dom

Análisis de Claude

Claude: Problema de versiones incompatibles detectado:
 
🔍 CAUSA:
React 18 movió el punto de entrada a 'react-dom/client',
pero tienes react-dom 17 que no tiene ese módulo.
 
📦 VERSIONES ACTUALES:
- react: 18.2.0 ✅
- react-dom: 17.0.2 ❌ (desactualizada)
- react-scripts: 5.0.1 (requiere React 18)
 
⚠️ CONFLICTO:
React Scripts 5 espera React 18, pero react-dom está en 17.
 
✅ SOLUCIÓN:
 
Paso 1: Actualiza react-dom
npm install react-dom@18.2.0
 
Paso 2: Actualiza el código de entrada (src/index.tsx)
 
ANTES (React 17):
import ReactDOM from 'react-dom';
import App from './App';
 
ReactDOM.render(
  <App />,
  document.getElementById('root')
);
 
DESPUÉS (React 18):
import { createRoot } from 'react-dom/client';
import App from './App';
 
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<App />);
 
Paso 3: Verifica package.json
Asegura que ambas versiones coincidan:
{
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}
 
🔄 COMANDOS A EJECUTAR:
npm install react-dom@18.2.0
# Luego actualizo src/index.tsx
npm start
 
📚 BREAKING CHANGES en React 18:
1. createRoot en lugar de ReactDOM.render
2. Automatic batching activado
3. Concurrent features disponibles
 
¿Quieres que aplique estos cambios?

Caso 5: Error de performance

Problema reportado

Usuario: Mi página se vuelve muy lenta después de unos minutos:
 
Síntomas:
- Empieza rápido
- Después de 5 minutos, lag notable
- Después de 10 minutos, casi inutilizable
- Refrescar la página lo arregla temporalmente
 
Código sospechoso (Dashboard.tsx):
 
function Dashboard() {
  const [data, setData] = useState([]);
 
  useEffect(() => {
    const interval = setInterval(() => {
      fetchLatestData().then(newData => {
        setData(prev => [...prev, newData]);
      });
    }, 5000); // cada 5 segundos
 
    return () => clearInterval(interval);
  }, []);
 
  return (
    <div>
      {data.map(item => <DataCard key={item.id} data={item} />)}
    </div>
  );
}

Análisis de Claude

Claude: Memory leak detectado:
 
🔍 CAUSA:
El array `data` crece indefinidamente.
Cada 5 segundos añades un elemento, nunca eliminas.
 
📊 CÁLCULO:
- 1 fetch cada 5 segundos
- 12 fetches por minuto
- 120 fetches en 10 minutos
- Si cada item ocupa 1KB → 120KB acumulados
- Más el DOM rendering de 120 elementos
 
📈 PROGRESIÓN:
Minuto 0: 0 items → Rápido
Minuto 5: 60 items → Empieza lag
Minuto 10: 120 items → Lento
Minuto 20: 240 items → Inutilizable
 
🐛 PROBLEMAS:
1. Array crece sin límite
2. React re-renderiza toda la lista cada vez
3. Acumulación de memoria
 
✅ SOLUCIONES:
 
OPCIÓN 1: Mantener solo últimos N elementos
function Dashboard() {
  const [data, setData] = useState([]);
  const MAX_ITEMS = 20; // Solo últimos 20
 
  useEffect(() => {
    const interval = setInterval(() => {
      fetchLatestData().then(newData => {
        setData(prev => {
          const updated = [...prev, newData];
          // Mantener solo últimos MAX_ITEMS
          return updated.slice(-MAX_ITEMS);
        });
      });
    }, 5000);
 
    return () => clearInterval(interval);
  }, []);
 
  return (
    <div>
      {data.map(item => <DataCard key={item.id} data={item} />)}
    </div>
  );
}
 
OPCIÓN 2: Reemplazar en lugar de acumular
function Dashboard() {
  const [data, setData] = useState(null);
 
  useEffect(() => {
    const interval = setInterval(() => {
      fetchLatestData().then(newData => {
        setData(newData); // Reemplazar, no acumular
      });
    }, 5000);
 
    return () => clearInterval(interval);
  }, []);
 
  return data ? <DataCard data={data} /> : null;
}
 
OPCIÓN 3: Paginación virtual (para muchos elementos)
import { FixedSizeList } from 'react-window';
 
function Dashboard() {
  const [data, setData] = useState([]);
 
  // ... (lógica de fetch con límite)
 
  return (
    <FixedSizeList
      height={600}
      itemCount={data.length}
      itemSize={80}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          <DataCard data={data[index]} />
        </div>
      )}
    </FixedSizeList>
  );
}
 
💡 RECOMENDACIÓN:
- Si solo necesitas el dato más reciente → Opción 2
- Si necesitas historial limitado → Opción 1
- Si necesitas mostrar cientos de items → Opción 3
 
🧪 CÓMO VERIFICAR:
1. Abre Chrome DevTools → Performance
2. Graba durante 1 minuto
3. Verifica que memoria se estabilice
4. Verifica que no haya layout thrashing
 
¿Qué opción prefieres?

Estrategias de debugging efectivo

1. Proporciona contexto completo

❌ Malo:
"Tengo un error con map"
 
✅ Bueno:
"Error: Cannot read property 'map' of undefined
En: UserList.tsx:23
Al: Cargar la página
Después de: Actualizar la API
Stack trace: [completo]
Código: [relevante]"

2. Menciona qué cambió recientemente

✅ Muy útil:
"Funcionaba ayer. Hoy actualicé:
- Express 4.17 → 4.18
- Node 16 → 18
- Añadí middleware de CORS"

3. Incluye intentos previos

✅ Ahorra tiempo:
"Ya intenté:
- Reinstalar node_modules → No funcionó
- Cambiar versión de React → No funcionó
- Revisar permisos → Todo OK"

4. Comparte el environment

✅ Información valiosa:
"Sistema: macOS 13.2
Node: 18.14.0
npm: 9.5.0
Entorno: Desarrollo local
Base de datos: PostgreSQL 14"

Comandos útiles para debugging

Capturar logs completos

# Guardar output completo en archivo
npm start 2>&1 | tee debug.log
 
# Luego compartir con Claude
# Pega el contenido de debug.log

Inspeccionar dependencias

# Ver árbol de dependencias
npm ls <paquete>
 
# Ver versiones instaladas
npm list --depth=0
 
# Verificar incompatibilidades
npm ls | grep UNMET

Debugging de tests

# Ejecutar test específico con output detallado
npm test -- --verbose UserList.test.ts
 
# Con coverage
npm test -- --coverage --verbose
 
# Modo watch para debugging iterativo
npm test -- --watch

Debugging de errores comunes

Error: EADDRINUSE (puerto en uso)

Claude detecta:
 
Error: listen EADDRINUSE: address already in use :::3000
 
✅ SOLUCIONES:
 
# Opción 1: Matar proceso en puerto 3000
lsof -ti:3000 | xargs kill -9
 
# Opción 2: Usar otro puerto
PORT=3001 npm start
 
# Opción 3: Encontrar qué está usando el puerto
lsof -i:3000

Error: MODULE_NOT_FOUND

Claude detecta:
 
Error: Cannot find module 'express'
 
✅ CAUSAS COMUNES:
1. No instalaste el módulo → npm install express
2. Ruta incorrecta → Verifica el import
3. node_modules corrupto → rm -rf node_modules && npm install
4. Package en devDependencies pero necesitas en dependencies

Error: CORS

Claude detecta:
 
Access to fetch at 'http://api.example.com' from origin 'http://localhost:3000'
has been blocked by CORS policy
 
✅ SOLUCIÓN (Backend):
 
const cors = require('cors');
 
app.use(cors({
  origin: 'http://localhost:3000', // O '*' para desarrollo
  credentials: true
}));

Tips para debugging productivo

✅ Buenas prácticas

  1. Reproduce el error de forma consistente

    Antes de pedir ayuda, verifica:
    - ¿El error ocurre siempre?
    - ¿En qué condiciones específicas?
    - ¿Pasos exactos para reproducirlo?
  2. Aísla el problema

    - Comenta código hasta que funcione
    - Identifica la línea exacta que falla
    - Crea un ejemplo mínimo reproducible
  3. Lee el error completo

    No solo la primera línea:
    - Stack trace completo
    - Archivos involucrados
    - Números de línea
  4. Usa logs estratégicos

    console.log('Antes de fetch:', data);
    const result = await fetch(url);
    console.log('Después de fetch:', result);
  5. Verifica lo obvio primero

    - ¿Servidor corriendo?
    - ¿Base de datos conectada?
    - ¿Variables de entorno configuradas?
    - ¿Dependencias instaladas?

⚠️ Evita

  1. No hagas múltiples cambios a la vez

    ❌ Malo: Cambiar 5 cosas y ver qué pasa
    ✅ Bueno: Cambiar 1 cosa, verificar, continuar
  2. No ignores warnings

    Los warnings a menudo son precursores de errores
  3. No copies/pegues soluciones sin entender

    Claude explica por qué funciona la solución
    Asegúrate de entender antes de aplicar

Flujo de debugging recomendado

┌─────────────────────────────────────┐
│ 1. ERROR APARECE                    │
└────────────┬────────────────────────┘


┌─────────────────────────────────────┐
│ 2. CAPTURAR INFORMACIÓN             │
│  - Stack trace completo             │
│  - Código relevante                 │
│  - Contexto y cambios recientes     │
└────────────┬────────────────────────┘


┌─────────────────────────────────────┐
│ 3. COMPARTIR CON CLAUDE             │
│  "Tengo este error: [info completa]"│
└────────────┬────────────────────────┘


┌─────────────────────────────────────┐
│ 4. CLAUDE ANALIZA                   │
│  - Identifica causa raíz            │
│  - Explica por qué ocurre           │
│  - Propone soluciones               │
└────────────┬────────────────────────┘


┌─────────────────────────────────────┐
│ 5. APLICAR SOLUCIÓN                 │
│  Claude implementa o tú lo haces    │
└────────────┬────────────────────────┘


┌─────────────────────────────────────┐
│ 6. VERIFICAR                        │
│  - Error desapareció? ✅            │
│  - Tests pasan? ✅                  │
│  - No hay regresiones? ✅           │
└────────────┬────────────────────────┘


┌─────────────────────────────────────┐
│ 7. DOCUMENTAR (opcional)            │
│  Añadir comentario explicativo      │
│  o test que prevenga regresión      │
└─────────────────────────────────────┘

Resumen

Debugging acelerado con Claude Code:

Captura completa: Stack trace + código + contexto ✅ Análisis instantáneo: Claude identifica causa raíz ✅ Soluciones explicadas: No solo "qué" sino "por qué" ✅ Aplicación rápida: De error a solución en minutos ✅ Prevención futura: Tests y documentación

Información clave para Claude:

  • Stack trace completo
  • Código donde ocurre el error
  • Qué estabas haciendo cuando ocurrió
  • Qué cambió recientemente
  • Intentos previos de solución

Próximo punto (8.3): Aprenderás técnicas de onboarding acelerado a proyectos nuevos con Claude Code.


Verificación de comprensión

Antes de continuar:

  • Sabes qué información incluir al reportar un error
  • Entiendes la importancia del contexto y cambios recientes
  • Puedes capturar stack traces completos
  • Reconoces patrones comunes de errores
  • Sabes verificar que la solución funcionó completamente

8.3 Codebase Onboarding

Objetivo

Dominar técnicas para hacer onboarding acelerado a proyectos nuevos usando Claude Code, aprendiendo a entender rápidamente la arquitectura, patrones, flujos de datos y convenciones de cualquier codebase.


Introducción

Unirte a un proyecto nuevo puede ser abrumador:

❌ Onboarding tradicional:
- Día 1-2: Leer README desactualizado
- Día 3-5: Explorar carpetas manualmente
- Semana 2: Entender ~30% del código
- Semana 3: Hacer primera contribución pequeña
- Mes 1: Sentirse medianamente productivo
 
✅ Onboarding con Claude Code:
- Hora 1: Entender estructura completa
- Hora 2: Mapear flujos principales
- Hora 3: Identificar patrones y convenciones
- Día 1: Primera contribución
- Semana 1: Productivo en el proyecto

Reducción: De 1 mes a 1 semana


Proceso de onboarding acelerado

Fase 1: Reconocimiento general (15-30 min)

Paso 1.1: Estructura del proyecto

Tú: Analiza la estructura de este proyecto y explícala.

Respuesta de Claude:

📁 ESTRUCTURA DEL PROYECTO
 
proyecto/
├── frontend/                → Aplicación React
│   ├── src/
│   │   ├── components/      → Componentes reutilizables
│   │   ├── pages/           → Páginas/rutas
│   │   ├── hooks/           → Custom React hooks
│   │   ├── services/        → Llamadas a API
│   │   ├── utils/           → Funciones utilitarias
│   │   ├── contexts/        → React Context (estado global)
│   │   └── types/           → Definiciones TypeScript
│   ├── public/              → Assets estáticos
│   └── package.json         → Dependencias frontend

├── backend/                 → API Node.js
│   ├── src/
│   │   ├── routes/          → Endpoints REST
│   │   ├── controllers/     → Lógica de negocio
│   │   ├── models/          → Esquemas de base de datos
│   │   ├── middleware/      → Middleware Express
│   │   ├── services/        → Servicios externos
│   │   └── utils/           → Utilidades
│   ├── tests/               → Tests del backend
│   └── package.json         → Dependencias backend

├── shared/                  → Código compartido
│   └── types/               → Tipos TypeScript compartidos

├── docs/                    → Documentación
│   ├── api/                 → Documentación de API
│   └── architecture/        → Diagramas y decisiones

└── scripts/                 → Scripts de automatización
    ├── deploy.sh
    └── seed-db.js
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
🏗️ ARQUITECTURA
 
Tipo: Monorepo
Frontend: React 18 + TypeScript
Backend: Node.js + Express + TypeScript
Base de datos: PostgreSQL
ORM: Prisma
Patrón: MVC (Model-View-Controller)
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
📊 ESTADÍSTICAS
 
Frontend:
  - 42 componentes
  - 12 páginas
  - 18 custom hooks
  - 8 servicios API
 
Backend:
  - 15 endpoints
  - 8 modelos
  - 6 middlewares
  - 12 controladores
 
Total líneas de código: ~15,000
Cobertura de tests: 73%

Paso 1.2: Dependencias principales

Tú: ¿Cuáles son las dependencias principales y para qué sirven?

Respuesta de Claude:

📦 DEPENDENCIAS CLAVE
 
FRONTEND
--------
 
react (18.2.0)
  → Biblioteca UI principal
  → Componentes funcionales + hooks
 
react-router-dom (6.14.0)
  → Enrutamiento SPA
  → Usado en: src/App.tsx para definir rutas
 
@tanstack/react-query (4.32.0)
  → Manejo de estado servidor
  → Cacheo y sincronización de datos
  → Usado en: todos los servicios de API
 
zustand (4.4.0)
  → Estado global ligero
  → Alternativa a Redux
  → Usado en: src/stores/ para auth y UI state
 
axios (1.4.0)
  → Cliente HTTP
  → Usado en: src/services/ para llamadas a API
 
zod (3.22.0)
  → Validación de esquemas
  → Runtime type checking
  → Usado en: formularios y validación de API responses
 
BACKEND
-------
 
express (4.18.2)
  → Framework web
  → Manejo de rutas y middleware
 
prisma (5.1.0)
  → ORM (Object-Relational Mapping)
  → Esquema en: prisma/schema.prisma
  → Migraciones en: prisma/migrations/
 
jsonwebtoken (9.0.1)
  → Autenticación JWT
  → Usado en: src/middleware/auth.ts
 
bcrypt (5.1.0)
  → Hash de passwords
  → Usado en: src/controllers/authController.ts
 
joi (17.9.2)
  → Validación de request body
  → Usado en: src/middleware/validation.ts
 
winston (3.10.0)
  → Logging estructurado
  → Configurado en: src/utils/logger.ts
 
DESARROLLO
----------
 
typescript (5.1.6)
  → Type safety en todo el proyecto
 
vitest (0.34.0)
  → Framework de testing
  → Más rápido que Jest
 
eslint + prettier
  → Linting y formateo
  → Configurado en: .eslintrc.js y .prettierrc

Fase 2: Flujos principales (30-45 min)

Paso 2.1: Autenticación

Tú: ¿Cómo funciona la autenticación en este proyecto?

Respuesta de Claude:

🔐 FLUJO DE AUTENTICACIÓN
 
TECNOLOGÍA: JWT (JSON Web Tokens)
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
1. REGISTRO (Sign Up)
 
   Frontend                     Backend                    Base de datos
   ────────                     ───────                    ─────────────
 
   [Formulario]
   - email
   - password
   - name

        ↓ POST /api/auth/register

        │                    [authController.ts:23]
        │                    1. Validar input (Joi)
        │                    2. Verificar email único
        │                    3. Hash password (bcrypt)
        │                         │
        │                         ↓ prisma.user.create()
        │                         │                       [users table]
        │                         │                       - Guarda usuario
        │                         ↓
        │                    4. Generar JWT
        │                    5. Retornar { token, user }

   [Guarda token]
   localStorage.setItem('token', ...)
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
2. LOGIN
 
   Frontend                     Backend                    Base de datos
   ────────                     ───────                    ─────────────
 
   [Formulario]
   - email
   - password

        ↓ POST /api/auth/login

        │                    [authController.ts:67]
        │                    1. Buscar usuario por email
        │                         │
        │                         ↓ prisma.user.findUnique()
        │                         │                       [users table]
        │                         ↓                       - Lee usuario
        │                    2. Comparar passwords
        │                       bcrypt.compare(input, hash)
        │                    3. Si válido: generar JWT
        │                    4. Retornar { token, user }

   [Guarda token]
   localStorage.setItem('token', ...)
   [Actualiza estado]
   setUser(userData)
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
3. REQUESTS AUTENTICADOS
 
   Frontend                     Backend
   ────────                     ───────
 
   [Request a API]
   headers: {
     Authorization: `Bearer ${token}`
   }

        ↓ GET/POST/PUT/DELETE /api/*

        │                    [middleware/auth.ts:12]
        │                    1. Extraer token del header
        │                    2. Verificar JWT
        │                       jwt.verify(token, SECRET)
        │                    3. Si válido: añadir userId a req
        │                       req.userId = decoded.userId
        │                    4. next() → continuar a controller

        │                    [controller respectivo]
        │                    - Tiene acceso a req.userId
        │                    - Puede hacer queries filtradas
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
4. LOGOUT
 
   Frontend
   ────────
 
   [Logout button]


   localStorage.removeItem('token')
   setUser(null)
   navigate('/login')
 
   Nota: JWT es stateless, el backend no mantiene sesiones.
         El token expira automáticamente (configurado: 7 días)
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
ARCHIVOS CLAVE:
 
Backend:
  - src/routes/auth.ts               → Rutas de auth
  - src/controllers/authController.ts → Lógica de login/register
  - src/middleware/auth.ts           → Middleware de verificación
  - src/utils/jwt.ts                 → Helpers para JWT
 
Frontend:
  - src/services/authService.ts      → API calls de auth
  - src/contexts/AuthContext.tsx     → Estado global de auth
  - src/hooks/useAuth.ts             → Hook para acceder a auth
  - src/components/ProtectedRoute.tsx → HOC para rutas privadas
 
CONFIGURACIÓN:
  - JWT_SECRET: Variable de entorno (backend/.env)
  - Expiración: 7 días (configurable en utils/jwt.ts:8)
  - Refresh tokens: NO implementado (considerar si necesario)

Paso 2.2: Flujo de datos completo

Tú: Explica el flujo de datos completo desde que el usuario hace clic
en "Crear producto" hasta que se guarda en la base de datos.

Respuesta de Claude:

📊 FLUJO COMPLETO: CREAR PRODUCTO
 
┌─────────────────────────────────────────────────────────────┐
│ 1. FRONTEND: Usuario interactúa                            │
└─────────────────────────────────────────────────────────────┘
 
[ProductForm.tsx:45]
Usuario llena formulario:
  - name: "Laptop Dell"
  - price: 999.99
  - category: "electronics"
  - description: "..."
 
Click en "Crear" → handleSubmit()
 
┌─────────────────────────────────────────────────────────────┐
│ 2. VALIDACIÓN FRONTEND                                      │
└─────────────────────────────────────────────────────────────┘
 
[ProductForm.tsx:67]
const schema = z.object({
  name: z.string().min(3),
  price: z.number().positive(),
  category: z.string(),
  description: z.string().optional()
});
 
const result = schema.safeParse(formData);
if (!result.success) {
  // Mostrar errores
  return;
}
 
┌─────────────────────────────────────────────────────────────┐
│ 3. LLAMADA A API                                            │
└─────────────────────────────────────────────────────────────┘
 
[services/productService.ts:23]
export async function createProduct(data: CreateProductDto) {
  const response = await axios.post('/api/products', data, {
    headers: {
      'Authorization': `Bearer ${getToken()}`
    }
  });
  return response.data;
}
 
POST /api/products
Headers:
  - Authorization: Bearer eyJhbGc...
  - Content-Type: application/json
Body:
  {
    "name": "Laptop Dell",
    "price": 999.99,
    "category": "electronics",
    "description": "..."
  }
 
┌─────────────────────────────────────────────────────────────┐
│ 4. BACKEND: Middleware de autenticación                    │
└─────────────────────────────────────────────────────────────┘
 
[middleware/auth.ts:12]
1. Extraer token del header
2. Verificar con jwt.verify()
3. Si válido: req.userId = decoded.userId
4. next() → continuar
 
Si falla → 401 Unauthorized
 
┌─────────────────────────────────────────────────────────────┐
│ 5. BACKEND: Validación de request                          │
└─────────────────────────────────────────────────────────────┘
 
[middleware/validation.ts:34]
const productSchema = Joi.object({
  name: Joi.string().min(3).required(),
  price: Joi.number().positive().required(),
  category: Joi.string().valid('electronics', 'clothing', ...).required(),
  description: Joi.string().optional()
});
 
validate(productSchema)(req, res, next);
 
Si falla → 400 Bad Request
 
┌─────────────────────────────────────────────────────────────┐
│ 6. BACKEND: Controller                                      │
└─────────────────────────────────────────────────────────────┘
 
[controllers/productController.ts:45]
export async function createProduct(req, res) {
  try {
    const { name, price, category, description } = req.body;
    const userId = req.userId; // Del middleware auth
 
    // Llamar al servicio
    const product = await productService.create({
      name,
      price,
      category,
      description,
      userId // Asociar producto con usuario creador
    });
 
    res.status(201).json(product);
  } catch (error) {
    logger.error('Error creating product:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
}
 
┌─────────────────────────────────────────────────────────────┐
│ 7. BACKEND: Service Layer                                  │
└─────────────────────────────────────────────────────────────┘
 
[services/productService.ts:23]
export async function create(data: CreateProductData) {
  // Lógica de negocio adicional
 
  // Ej: Generar slug automático
  const slug = generateSlug(data.name);
 
  // Ej: Validar que categoría existe
  await validateCategory(data.category);
 
  // Guardar en DB
  const product = await prisma.product.create({
    data: {
      ...data,
      slug,
      createdAt: new Date()
    }
  });
 
  // Operaciones post-creación
  await logActivity('product_created', product.id);
  await cacheInvalidate('products:list');
 
  return product;
}
 
┌─────────────────────────────────────────────────────────────┐
│ 8. BASE DE DATOS: Prisma ORM                               │
└─────────────────────────────────────────────────────────────┘
 
Prisma genera SQL:
INSERT INTO products (
  id, name, price, category, description, slug, user_id, created_at
) VALUES (
  'uuid...', 'Laptop Dell', 999.99, 'electronics', '...',
  'laptop-dell', 'user_id...', '2024-01-15 10:30:00'
)
RETURNING *;
 
PostgreSQL ejecuta query y retorna el registro creado.
 
┌─────────────────────────────────────────────────────────────┐
│ 9. RESPUESTA BACKEND → FRONTEND                            │
└─────────────────────────────────────────────────────────────┘
 
HTTP 201 Created
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Laptop Dell",
  "price": 999.99,
  "category": "electronics",
  "description": "...",
  "slug": "laptop-dell",
  "userId": "user_id...",
  "createdAt": "2024-01-15T10:30:00.000Z"
}
 
┌─────────────────────────────────────────────────────────────┐
│ 10. FRONTEND: Actualización de estado                      │
└─────────────────────────────────────────────────────────────┘
 
[ProductForm.tsx:89]
// React Query actualiza cache automáticamente
queryClient.invalidateQueries(['products']);
 
// Mostrar notificación de éxito
toast.success('Producto creado exitosamente');
 
// Navegar a lista de productos
navigate('/products');
 
// React Query refetch en ProductList
// → Lista actualizada incluye nuevo producto
 
┌─────────────────────────────────────────────────────────────┐
│ 11. FRONTEND: Re-render                                    │
└─────────────────────────────────────────────────────────────┘
 
[ProductList.tsx:34]
- useQuery refetch automático
- Lista se actualiza con el nuevo producto
- Usuario ve "Laptop Dell" en la lista
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
TIEMPO TOTAL: ~200-500ms
 
Desglose:
  - Validación frontend: ~5ms
  - Request HTTP: ~50ms
  - Middleware auth: ~10ms
  - Validación backend: ~5ms
  - Controller: ~5ms
  - Service logic: ~20ms
  - Query a DB: ~30ms
  - Response HTTP: ~50ms
  - Update UI: ~20ms
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
MANEJO DE ERRORES en cada capa:
 
Frontend:
  - Validación Zod falla → Mostrar errores en formulario
  - API retorna error → Mostrar toast de error
 
Backend:
  - Auth falla → 401 Unauthorized
  - Validación falla → 400 Bad Request con detalles
  - DB error → 500 Internal Server Error (error logged)
 
Base de datos:
  - Constraint violation → Prisma lanza error
  - Connection error → Service retry o error

Fase 3: Patrones y convenciones (30-45 min)

Paso 3.1: Patrones de diseño

Tú: ¿Qué patrones de diseño se usan en este proyecto?

Respuesta de Claude:

🎨 PATRONES DE DISEÑO IDENTIFICADOS
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
1. MVC (Model-View-Controller)
 
   Backend estructurado en capas:
 
   MODEL (models/)

   CONTROLLER (controllers/)

   VIEW (JSON responses)
 
   Ejemplo:
   - Model: prisma/schema.prisma
   - Controller: src/controllers/userController.ts
   - View: JSON response
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
2. Repository Pattern
 
   [services/productService.ts]
 
   Abstracción de acceso a datos:
 
   class ProductRepository {
     async findAll() { ... }
     async findById(id) { ... }
     async create(data) { ... }
     async update(id, data) { ... }
     async delete(id) { ... }
   }
 
   Beneficio: Cambiar DB sin afectar controllers
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
3. Middleware Pattern
 
   [Express middleware]
 
   Request → Auth → Validation → Rate Limit → Controller → Response
 
   Cada middleware tiene una responsabilidad única.
 
   Ejemplo:
   app.post('/api/products',
     authMiddleware,      // Verificar auth
     validateMiddleware,  // Validar input
     rateLimitMiddleware, // Prevenir spam
     productController.create
   );
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
4. Factory Pattern
 
   [utils/logger.ts:23]
 
   function createLogger(service: string) {
     return winston.createLogger({
       defaultMeta: { service },
       transports: [...]
     });
   }
 
   Uso:
   const logger = createLogger('auth-service');
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
5. Singleton Pattern
 
   [utils/database.ts:12]
 
   let prismaInstance: PrismaClient | null = null;
 
   export function getPrisma() {
     if (!prismaInstance) {
       prismaInstance = new PrismaClient();
     }
     return prismaInstance;
   }
 
   Beneficio: Una sola conexión a DB
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
6. Dependency Injection
 
   [controllers/userController.ts:8]
 
   class UserController {
     constructor(
       private userService: UserService,
       private emailService: EmailService
     ) {}
 
     async create(req, res) {
       const user = await this.userService.create(...);
       await this.emailService.sendWelcome(user);
     }
   }
 
   Beneficio: Fácil de testear (mock services)
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
7. Custom Hooks (React)
 
   [hooks/useAuth.ts]
   [hooks/useProducts.ts]
   [hooks/useDebounce.ts]
 
   Reutilización de lógica entre componentes.
 
   Ejemplo:
   function useAuth() {
     const { data, isLoading } = useQuery('user', fetchUser);
     const logout = () => { ... };
     return { user: data, isLoading, logout };
   }
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
8. Compound Components (React)
 
   [components/Modal/]
 
   <Modal>
     <Modal.Header>Title</Modal.Header>
     <Modal.Body>Content</Modal.Body>
     <Modal.Footer>Actions</Modal.Footer>
   </Modal>
 
   Componentes que trabajan juntos.
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
9. Error Handling Pattern
 
   [middleware/errorHandler.ts]
 
   Manejo centralizado de errores:
 
   app.use((err, req, res, next) => {
     logger.error(err);
 
     if (err instanceof ValidationError) {
       return res.status(400).json(...);
     }
 
     if (err instanceof UnauthorizedError) {
       return res.status(401).json(...);
     }
 
     // Default
     res.status(500).json(...);
   });
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
10. Observer Pattern (React Query)
 
    [hooks/useProducts.ts]
 
    Componentes "observan" datos:
 
    const { data } = useQuery('products', fetchProducts);
 
    Cuando datos cambian:
    → React Query notifica
    → Componentes re-renderizan automáticamente

Paso 3.2: Convenciones de código

Tú: ¿Qué convenciones de código debo seguir en este proyecto?

Respuesta de Claude:

📝 CONVENCIONES DE CÓDIGO
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
NOMENCLATURA
------------
 
Variables y funciones:
  ✅ camelCase
  const userId = 123;
  function getUserById() { }
 
Clases y componentes:
  ✅ PascalCase
  class UserService { }
  function UserProfile() { }
 
Constantes:
  ✅ UPPER_SNAKE_CASE
  const API_BASE_URL = 'https://...';
  const MAX_RETRIES = 3;
 
Archivos:
  ✅ kebab-case para utils
  user-service.ts
  validation-helpers.ts
 
  ✅ PascalCase para componentes React
  UserProfile.tsx
  ProductCard.tsx
 
Tipos e Interfaces:
  ✅ Interfaces: I + PascalCase
  interface IUser { }
  interface IProduct { }
 
  ✅ Types: T + PascalCase
  type TUserRole = 'admin' | 'user';
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
ESTRUCTURA DE ARCHIVOS
-----------------------
 
Componentes React:
  ✅ Un componente por archivo
  ✅ Archivo mismo nombre que componente
 
  components/
    UserProfile/
      UserProfile.tsx        → Componente
      UserProfile.test.tsx   → Tests
      UserProfile.styles.ts  → Estilos (si styled-components)
      index.ts               → Re-export
 
Servicios:
  ✅ Sufijo "Service"
  userService.ts
  emailService.ts
 
Controllers:
  ✅ Sufijo "Controller"
  userController.ts
  productController.ts
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
IMPORTS
-------
 
Orden de imports:
  1. Externos
  2. Internos
  3. Tipos
  4. Estilos
 
  // Externos
  import React from 'react';
  import { useQuery } from '@tanstack/react-query';
 
  // Internos
  import { Button } from '@/components/Button';
  import { useAuth } from '@/hooks/useAuth';
 
  // Tipos
  import type { User } from '@/types/user';
 
  // Estilos
  import styles from './UserProfile.module.css';
 
Path aliases configurados:
  @ → src/
  @components → src/components/
  @hooks → src/hooks/
  @utils → src/utils/
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
TYPESCRIPT
----------
 
✅ HACER:
  - Tipos explícitos en parámetros de función
  - Interfaces para objetos complejos
  - Type guards cuando sea necesario
 
  function getUser(id: string): Promise<IUser> { }
 
  interface ICreateUserDto {
    email: string;
    password: string;
    name: string;
  }
 
❌ EVITAR:
  - any (prohibido por eslint)
  - type assertions innecesarios
  - tipos implícitos en exports
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
FUNCIONES
---------
 
✅ Arrow functions para la mayoría:
  const handleClick = () => { };
  const calculateTotal = (items: Item[]) => { };
 
✅ Function declaration para exportadas:
  export function getUserById(id: string) { }
 
✅ Async/await preferido sobre Promises:
  ❌ return fetch().then().catch()
  ✅ const data = await fetch()
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
COMENTARIOS
-----------
 
✅ JSDoc para funciones exportadas:
  /**
   * Obtiene un usuario por su ID
   * @param id - ID del usuario
   * @returns Usuario encontrado
   * @throws NotFoundError si el usuario no existe
   */
  export async function getUserById(id: string): Promise<IUser> { }
 
✅ Comentarios inline solo cuando sea necesario:
  // HACK: Temporal fix para bug #123
  // TODO: Refactorizar usando nuevo patrón
  // FIXME: Memory leak aquí
 
❌ NO comentar lo obvio:
  // Incrementa counter en 1
  counter++; // ❌ Innecesario
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
ERROR HANDLING
--------------
 
Backend:
  ✅ try/catch en controllers
  ✅ Errores específicos personalizados
 
  if (!user) {
    throw new NotFoundError('User not found');
  }
 
Frontend:
  ✅ Error boundaries para errores de React
  ✅ React Query maneja errores de API
 
  const { data, error, isError } = useQuery(...);
 
  if (isError) {
    return <ErrorMessage error={error} />;
  }
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
TESTING
-------
 
✅ Archivos de test: *.test.ts / *.test.tsx
✅ describe/test (no it)
✅ Nombres descriptivos
 
  describe('UserService', () => {
    describe('getUserById', () => {
      test('retorna usuario cuando existe', async () => { });
      test('lanza NotFoundError cuando no existe', async () => { });
    });
  });
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
CONFIGURACIÓN
-------------
 
Prettier:
  - Semi: true
  - Single quotes: true
  - Trailing comma: es5
  - Print width: 100
 
ESLint:
  - no-console: warn (permitido en desarrollo)
  - no-any: error
  - prefer-const: error
  - no-unused-vars: error
 
Git:
  - Commits: Conventional Commits
    feat: nueva funcionalidad
    fix: corrección de bug
    docs: documentación
    refactor: refactorización
    test: tests
    chore: tareas

Fase 4: Tests y comandos (15-30 min)

Paso 4.1: Sistema de tests

Tú: ¿Dónde están los tests y cómo se ejecutan?

Respuesta de Claude:

🧪 SISTEMA DE TESTS
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
UBICACIÓN
---------
 
Backend:
  backend/tests/
    ├── unit/           → Tests unitarios
    │   ├── services/
    │   ├── utils/
    │   └── models/
    ├── integration/    → Tests de integración
    │   ├── api/
    │   └── database/
    └── e2e/            → Tests end-to-end
 
Frontend:
  frontend/src/
    ├── components/
    │   └── UserProfile/
    │       └── UserProfile.test.tsx
    ├── hooks/
    │   └── useAuth.test.ts
    └── utils/
        └── validation.test.ts
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
FRAMEWORK
---------
 
Frontend y Backend: Vitest
Razón: Más rápido que Jest, mejor DX
 
Testing Library (Frontend):
  - @testing-library/react
  - @testing-library/user-event
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
COMANDOS
--------
 
# Backend
cd backend
 
# Todos los tests
npm test
 
# Tests en watch mode
npm test -- --watch
 
# Tests con coverage
npm test -- --coverage
 
# Tests específicos
npm test userService
 
# Solo unitarios
npm test -- unit/
 
# Solo integración
npm test -- integration/
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
# Frontend
cd frontend
 
# Todos los tests
npm test
 
# Watch mode
npm test -- --watch
 
# Coverage
npm test -- --coverage
 
# Tests de un componente específico
npm test UserProfile
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
COBERTURA ACTUAL
----------------
 
Backend:
  - Statements: 78%
  - Branches: 72%
  - Functions: 81%
  - Lines: 78%
 
Frontend:
  - Statements: 68%
  - Branches: 64%
  - Functions: 71%
  - Lines: 68%
 
Mínimo requerido: 70% (configurado en vitest.config.ts)
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
EJEMPLO: Test unitario
 
[backend/tests/unit/services/userService.test.ts]
 
import { describe, test, expect, vi } from 'vitest';
import { UserService } from '@/services/userService';
 
describe('UserService', () => {
  test('getUserById retorna usuario cuando existe', async () => {
    const mockUser = { id: '1', name: 'Test' };
    vi.spyOn(prisma.user, 'findUnique').mockResolvedValue(mockUser);
 
    const result = await UserService.getUserById('1');
 
    expect(result).toEqual(mockUser);
  });
});
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
EJEMPLO: Test de componente
 
[frontend/src/components/UserProfile/UserProfile.test.tsx]
 
import { render, screen } from '@testing-library/react';
import { UserProfile } from './UserProfile';
 
test('renderiza nombre del usuario', () => {
  const user = { id: '1', name: 'John Doe', email: 'john@example.com' };
 
  render(<UserProfile user={user} />);
 
  expect(screen.getByText('John Doe')).toBeInTheDocument();
});
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
CI/CD
-----
 
Los tests corren automáticamente en:
  - Pre-commit hook (lint-staged)
  - Pull requests (GitHub Actions)
  - Antes de deploy (pipeline)
 
Archivo: .github/workflows/test.yml

Paso 4.2: Comandos comunes

Tú: ¿Cuáles son los comandos más importantes que debo conocer?

Respuesta de Claude:

⚡ COMANDOS ESENCIALES
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
DESARROLLO
----------
 
# Instalar dependencias
npm install
 
# Desarrollo (frontend + backend simultáneamente)
npm run dev
 
# Solo frontend
cd frontend && npm run dev
 
# Solo backend
cd backend && npm run dev
 
# Build de producción
npm run build
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
BASE DE DATOS
-------------
 
# Generar cliente Prisma
cd backend && npm run prisma:generate
 
# Crear migración
npm run prisma:migrate:dev
 
# Aplicar migraciones
npm run prisma:migrate:deploy
 
# Abrir Prisma Studio (GUI para DB)
npm run prisma:studio
 
# Seed de datos
npm run db:seed
 
# Reset completo de DB
npm run db:reset
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
TESTING
-------
 
# Todos los tests
npm test
 
# Tests en watch mode
npm test -- --watch
 
# Coverage
npm test -- --coverage
 
# Tests de un archivo específico
npm test userService
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
LINTING Y FORMATEO
------------------
 
# Lint
npm run lint
 
# Lint con fix automático
npm run lint:fix
 
# Format con Prettier
npm run format
 
# Check de format
npm run format:check
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
TYPESCRIPT
----------
 
# Type check
npm run typecheck
 
# Type check en watch mode
npm run typecheck:watch
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
GIT HOOKS (Husky)
-----------------
 
Automático en:
 
Pre-commit:
  → npm run lint
  → npm run typecheck
  → npm test (solo archivos staged)
 
Pre-push:
  → npm test (todos)
  → npm run build
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
DOCKER
------
 
# Build images
docker-compose build
 
# Levantar servicios
docker-compose up
 
# Levantar en background
docker-compose up -d
 
# Ver logs
docker-compose logs -f
 
# Detener servicios
docker-compose down
 
# Reset completo (incluye volúmenes)
docker-compose down -v
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
SCRIPTS ÚTILES
--------------
 
# Generar tipos TypeScript desde Prisma schema
npm run generate:types
 
# Analizar bundle size
npm run analyze
 
# Limpiar cache y reinstalar
npm run clean && npm install
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
WORKFLOW TÍPICO
---------------
 
# Día 1: Setup inicial
git clone <repo>
npm install
cp .env.example .env
# (Configurar variables de entorno)
npm run prisma:migrate:dev
npm run db:seed
npm run dev
 
# Desarrollo diario
git checkout -b feature/nueva-feature
npm run dev
# (Hacer cambios)
npm test
npm run lint:fix
git add .
git commit -m "feat: nueva feature"
# (Pre-commit hook corre automáticamente)
git push
# (Pre-push hook corre automáticamente)
# (Crear PR en GitHub)

Preguntas clave para onboarding

Arquitectura

✅ ¿Qué tipo de arquitectura usa? (MVC, Clean, Hexagonal)
✅ ¿Cómo se comunican frontend y backend? (REST, GraphQL, tRPC)
✅ ¿Qué base de datos usa y por qué?
✅ ¿Hay servicios externos integrados? (Stripe, AWS, etc.)
✅ ¿Cómo se maneja el estado global? (Redux, Zustand, Context)

Autenticación y seguridad

✅ ¿Cómo funciona la autenticación? (JWT, sessions, OAuth)
✅ ¿Cómo se protegen las rutas? (middleware, guards)
✅ ¿Dónde se almacenan los tokens?
✅ ¿Hay refresh tokens implementados?
✅ ¿Qué medidas de seguridad hay? (CORS, rate limiting, etc.)

Flujos de datos

✅ Explica el flujo completo de [acción principal]
✅ ¿Cómo se manejan los errores en cada capa?
✅ ¿Hay cacheo? ¿Dónde y cómo?
✅ ¿Cómo se sincronizan datos entre cliente y servidor?

Código y convenciones

✅ ¿Qué convenciones de nomenclatura se usan?
✅ ¿Cómo se estructuran los archivos?
✅ ¿Hay guía de estilo documentada?
✅ ¿Qué patrones de diseño se usan?

Testing y calidad

✅ ¿Dónde están los tests?
✅ ¿Qué framework de testing se usa?
✅ ¿Cuál es la cobertura actual?
✅ ¿Cómo ejecuto los tests?
✅ ¿Hay tests e2e?

Desarrollo y deployment

✅ ¿Cómo levanto el proyecto localmente?
✅ ¿Qué variables de entorno necesito?
✅ ¿Cómo se hace el deployment?
✅ ¿Hay pipeline de CI/CD?
✅ ¿Cómo se manejan las migraciones de DB?

Checklist de onboarding

□ Clonar repositorio
□ Instalar dependencias
□ Configurar variables de entorno
□ Levantar base de datos local
□ Ejecutar migraciones
□ Seed de datos de prueba
□ Levantar servidor de desarrollo
□ Verificar que todo funciona
□ Ejecutar tests
□ Leer documentación en /docs
□ Entender estructura del proyecto
□ Mapear flujos principales
□ Identificar patrones de código
□ Conocer comandos esenciales
□ Configurar IDE (extensiones, snippets)
□ Revisar issues abiertos
□ Hacer primera contribución pequeña

Resumen

Onboarding acelerado con Claude Code:

Fase 1: Reconocimiento general (estructura, dependencias) ✅ Fase 2: Flujos principales (auth, CRUD, datos) ✅ Fase 3: Patrones y convenciones (diseño, código) ✅ Fase 4: Tests y comandos (testing, desarrollo)

Tiempo total: 2-3 horas (vs 1-2 semanas tradicional)

Preguntas clave: Arquitectura, auth, flujos, convenciones, testing, deployment

Próximo punto (8.4): Aprenderás técnicas de code review con Claude Code.


Verificación de comprensión

Antes de continuar:

  • Sabes cómo explorar la estructura de un proyecto nuevo
  • Puedes identificar dependencias principales y su propósito
  • Entiendes cómo mapear flujos de datos completos
  • Reconoces patrones de diseño en código existente
  • Sabes dónde encontrar y ejecutar tests
  • Conoces las preguntas clave para hacer onboarding efectivo

8.4 Code review con Claude Code

Objetivo

Dominar el proceso de code review automatizado con Claude Code, aprendiendo a utilizar tanto el plugin nativo /code-review como a crear skills personalizadas de revisión, integrándolas en tu workflow de desarrollo.


Introducción

El code review es una de las prácticas más valiosas en desarrollo de software, pero también una de las más costosas en tiempo. Claude Code transforma este proceso mediante:

Ventajas del Code Review con Claude

Revisión instantánea: Análisis completo en segundos vs horas de revisión manual ✅ Consistencia: Mismos estándares aplicados en cada revisión ✅ Cobertura exhaustiva: Revisa bugs, seguridad, rendimiento, legibilidad simultáneamente ✅ Sin sesgo humano: No se cansa, no tiene apuros, no pasa por alto detalles ✅ Escalable: Puede revisar PRs pequeños o refactors masivos con igual efectividad ✅ Educativo: Explica el por qué detrás de cada sugerencia


Plugin Nativo: /code-review

Claude Code incluye un plugin oficial de code review que utiliza una arquitectura multi-agente avanzada.

Arquitectura del Plugin

┌─────────────────────────────────────────────────┐
│  /code-review  (orquestador principal)          │
└─────────────────┬───────────────────────────────┘

      ┌───────────┴───────────┐
      │  Análisis Paralelo    │
      └───────────┬───────────┘

      ┌───────────┴────────────────────────────┐
      │                                        │
      ▼                                        ▼
┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│  Agente #1   │  │  Agente #2   │  │  Agente #3   │  │  Agente #4   │
│              │  │              │  │              │  │              │
│ Cumplimiento │  │ Cumplimiento │  │    Bugs      │  │  Histórico   │
│  CLAUDE.md   │  │  CLAUDE.md   │  │  Obvios      │  │  git blame   │
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       │                 │                 │                 │
       └─────────────────┴─────────────────┴─────────────────┘


                 ┌────────────────┐
                 │  Scoring 0-100 │
                 │  por problema  │
                 └────────┬───────┘


                 ┌────────────────┐
                 │ Filtro (>80)   │
                 │ Threshold      │
                 └────────┬───────┘


                 ┌────────────────┐
                 │ Reporte Final  │
                 └────────────────┘

Características clave:

  • 4 agentes especializados ejecutándose en paralelo
  • Sistema de scoring: Cada problema recibe un score de confianza (0-100)
  • Filtrado automático: Solo muestra problemas con score > 80 (configurable)
  • Contexto del proyecto: Lee automáticamente archivos CLAUDE.md

Uso Básico del Plugin

Análisis Local

# En una rama con cambios sin mergear
/code-review

Salida en terminal:

🔍 Analizando cambios en el PR...
 
📋 Contexto detectado:
  - CLAUDE.md encontrado: .claude/CLAUDE.md
  - Archivos cambiados: 8
  - Líneas añadidas: +247
  - Líneas eliminadas: -103
 
🤖 Ejecutando análisis con 4 agentes en paralelo...
 
✅ Análisis completado
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 CRÍTICO (P1) - Score: 95
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
📁 src/auth/validateToken.ts:42
 
⚠️ Vulnerabilidad de seguridad: JWT sin verificación de expiración
 
Código actual:
```typescript
function validateToken(token: string) {
  const decoded = jwt.decode(token); // ❌ No verifica firma ni expiración
  return decoded;
}

🔧 Recomendación:

function validateToken(token: string) {
  const decoded = jwt.verify(token, SECRET_KEY); // ✅ Verifica firma y expiración
  return decoded;
}

📖 Explicación: jwt.decode() solo decodifica el token sin verificar su firma criptográfica ni la expiración (exp claim). Un atacante puede crear tokens arbitrarios.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🟡 MEDIO (P3) - Score: 87 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📁 src/utils/formatDate.ts:15

📦 Problema de rendimiento: Creación innecesaria de objetos Date

Código actual:

dates.map(d => new Date(d).toISOString());

🔧 Recomendación:

// Si d ya es Date, evitar recreación
dates.map(d => (d instanceof Date ? d : new Date(d)).toISOString());

📖 Explicación: Si dates ya contiene objetos Date, crear nuevas instancias es innecesario y costoso en loops grandes.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🔵 BAJO (P4) - Score: 82 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📁 src/components/UserList.tsx:8

📝 Mejora de legibilidad: Nombre de variable poco descriptivo

Código actual:

const data = users.filter(u => u.active);

🔧 Recomendación:

const activeUsers = users.filter(user => user.active);

📖 Explicación: Según CLAUDE.md del proyecto, las variables deben tener nombres descriptivos. "data" es genérico; "activeUsers" comunica intención.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📊 Resumen:

  • Problemas encontrados: 3
  • Críticos (P1): 1
  • Altos (P2): 0
  • Medios (P3): 1
  • Bajos (P4): 1
 
### Publicar como Comentario en PR
 
```bash
/code-review --comment

Esto publica el análisis como comentario en el PR de GitHub automáticamente.


Clasificación de Severidad

Claude Code clasifica cada problema en 4 niveles de prioridad:

PrioridadTipoEjemplos
P1 (Crítico)Bugs que causan crashes, vulnerabilidades de seguridadSQL injection, null pointer dereference, credenciales expuestas
P2 (Alto)Bugs funcionales, problemas serios de rendimientoLógica incorrecta, memory leaks, N+1 queries
P3 (Medio)Code smells, mejoras recomendadasDuplicación, funciones largas, acoplamiento alto
P4 (Bajo)Estilo, sugerencias menoresNombres poco claros, comentarios, formateo

Sistema de Scoring

Cada problema recibe un score de confianza (0-100):

  • 90-100: Claude está muy seguro del problema
  • 80-89: Alta confianza, muy probable que sea válido
  • 70-79: Confianza moderada, revisar caso por caso
  • 0-69: Baja confianza, puede ser falso positivo

Por defecto, solo se muestran problemas con score ≥ 80.


Qué Analiza Claude Code

1. Bugs Potenciales

// ❌ Bug detectado
function divide(a: number, b: number) {
  return a / b; // No valida división por cero
}
 
// ✅ Sugerencia de Claude
function divide(a: number, b: number) {
  if (b === 0) {
    throw new Error('Cannot divide by zero');
  }
  return a / b;
}

2. Vulnerabilidades de Seguridad

// ❌ Vulnerabilidad
app.get('/user', (req, res) => {
  const userId = req.query.id;
  db.query(`SELECT * FROM users WHERE id = ${userId}`); // SQL injection
});
 
// ✅ Sugerencia de Claude
app.get('/user', (req, res) => {
  const userId = req.query.id;
  db.query('SELECT * FROM users WHERE id = ?', [userId]); // Prepared statement
});

3. Problemas de Rendimiento

// ❌ Problema de rendimiento
function findUser(users: User[], id: string) {
  return users.find(u => u.id === id); // O(n) en cada llamada
}
 
// Si se llama muchas veces:
for (const id of userIds) {
  findUser(allUsers, id); // O(n²) total
}
 
// ✅ Sugerencia de Claude
const userMap = new Map(users.map(u => [u.id, u])); // O(n) una vez
 
function findUser(userMap: Map<string, User>, id: string) {
  return userMap.get(id); // O(1) por llamada
}

4. Mejoras de Legibilidad

// ❌ Difícil de leer
function p(u: User) {
  if (u.a && u.a.length > 0 && u.s === 'active') {
    return u.a.filter(x => x.t === 'pro').map(x => x.p);
  }
  return [];
}
 
// ✅ Sugerencia de Claude
function getActiveProPrices(user: User): number[] {
  const hasSubscriptions = user.subscriptions?.length > 0;
  const isActive = user.status === 'active';
 
  if (!hasSubscriptions || !isActive) {
    return [];
  }
 
  return user.subscriptions
    .filter(subscription => subscription.type === 'pro')
    .map(subscription => subscription.price);
}

5. Violaciones de Convenciones

Claude lee tu CLAUDE.md para verificar:

# CLAUDE.md
 
## Convenciones de Código
 
- Usar async/await, no callbacks
- Nombres de funciones en camelCase
- Componentes React deben ser functional components
- Siempre usar TypeScript estricto (no any)
// ❌ Viola convenciones del proyecto
fetchData(function(err, data) { // Callback en lugar de async/await
  if (err) return handleError(err);
  ProcessData(data); // PascalCase en función (debería ser camelCase)
});
 
// ✅ Cumple convenciones
async function fetchAndProcessData(): Promise<void> {
  try {
    const data = await fetchData();
    processData(data);
  } catch (error) {
    handleError(error);
  }
}

Configuración Avanzada

Ajustar Threshold de Confianza

Por defecto, solo se muestran problemas con score ≥ 80. Para ajustar:

Ubicación del archivo:

~/.claude/plugins/code-review/commands/code-review.md

Modificar la línea:

Filter out any issues with a score less than 80

Ejemplos de configuración:

# Muy estricto: solo problemas de alta confianza
Filter out any issues with a score less than 90
 
# Balanceado (default)
Filter out any issues with a score less than 80
 
# Permisivo: incluye más sugerencias
Filter out any issues with a score less than 70
 
# Mostrar todo (incluye posibles falsos positivos)
Filter out any issues with a score less than 0

Integración con CLAUDE.md

Claude Code lee automáticamente todos los archivos CLAUDE.md en tu proyecto:

proyecto/
├── .claude/CLAUDE.md          → Contexto general del proyecto
├── src/
│   └── CLAUDE.md              → Convenciones de /src
└── src/api/
    └── CLAUDE.md              → Reglas específicas de API

Ejemplo de CLAUDE.md para code review:

# Proyecto: E-commerce Platform
 
## Arquitectura
- Frontend: React 18 + TypeScript
- Backend: Node.js + Express
- Base de datos: PostgreSQL con Prisma
 
## Convenciones Críticas
 
### Seguridad
- NUNCA exponer credenciales en código
- Siempre validar input del usuario
- Usar prepared statements para SQL
- Sanitizar output HTML (prevenir XSS)
 
### Rendimiento
- Cachear respuestas de DB cuando sea posible
- Usar índices en queries frecuentes
- Lazy loading para imágenes y componentes pesados
- Debounce en inputs de búsqueda
 
### Código
- TypeScript estricto (no any)
- Funciones máximo 50 líneas
- Nombres descriptivos (no abreviaturas)
- Un componente por archivo
 
### Testing
- Mínimo 80% de cobertura
- Tests unitarios para lógica de negocio
- Tests de integración para APIs
- E2E para flujos críticos (checkout, login)
 
## Áreas Sensibles
 
- `/src/payments/`: Código crítico, revisar con cuidado especial
- `/src/auth/`: Revisión de seguridad obligatoria
- `/src/admin/`: Solo admin debe acceder
 
## Archivos Generados (No Modificar)
 
- `/src/generated/**`: Código auto-generado por Prisma
- `/public/build/**`: Compilado de frontend

Con este CLAUDE.md, Claude Code revisará automáticamente:

  • ✅ Que no haya credenciales expuestas
  • ✅ Que SQL use prepared statements
  • ✅ Que no se use any en TypeScript
  • ✅ Que funciones no excedan 50 líneas
  • ✅ Que código en /payments/ y /auth/ tenga cuidado extra

Integración en CI/CD

GitHub Actions

Revisa automáticamente cada PR:

Paso 1: Instalar GitHub App

claude /install-github-app

Paso 2: Crear workflow

# .github/workflows/code-review.yml
 
name: Automated Code Review
 
on:
  pull_request:
    types: [opened, synchronize]
 
jobs:
  review:
    runs-on: ubuntu-latest
 
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Necesario para git diff
 
      - name: Install Claude Code
        run: npm install -g @anthropic-ai/claude-code
 
      - name: Run Code Review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          claude /code-review --comment
 
      - name: Check for Critical Issues
        if: failure()
        run: |
          echo "❌ Code review encontró problemas críticos"
          exit 1

Paso 3: Configurar secrets

En tu repositorio de GitHub:

  1. Ir a Settings → Secrets and variables → Actions
  2. Añadir ANTHROPIC_API_KEY con tu API key

Resultado:

Cada PR recibirá automáticamente un comentario con el análisis:

🤖 Claude Code Review
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 CRÍTICO (P1) - 2 problemas
🟠 ALTO (P2) - 3 problemas
🟡 MEDIO (P3) - 5 problemas
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
[Detalles del análisis...]

Patrones de Workflow Avanzados

Patrón 1: Writer/Reviewer Independientes

┌─────────────────┐
│  Session A      │
│  (Writer)       │
│                 │
│  Escribe código │
└────────┬────────┘


┌─────────────────┐
│  Session B      │
│  (Reviewer)     │
│                 │
│  /code-review   │
└─────────────────┘

¿Por qué funciona?

  • Session B tiene contexto limpio (sin sesgo del autor)
  • Detecta problemas que Session A podría pasar por alto
  • Simula revisión por otro desarrollador

Implementación:

# Session A (escritura)
claude
> Implementa endpoint POST /api/users con validación
[Claude escribe código]
> Haz commit
 
# Session B (revisión) - NUEVA SESIÓN
claude
> /code-review
[Claude analiza con perspectiva fresca]

Patrón 2: TDD Inverso (Test-Driven Review)

┌─────────────────┐
│  Claude A       │
│  Escribe tests  │
└────────┬────────┘


┌─────────────────┐
│  Claude B       │
│  Implementa     │
└────────┬────────┘


┌─────────────────┐
│  Claude C       │
│  Code Review    │
└─────────────────┘

Flujo completo:

# Agente 1: Tests
claude -p "Escribe tests para función calculateShipping que:
- Calcula envío gratis si pedido > $50
- Cobra $5 si pedido < $50
- Maneja casos edge (pedido $0, negativo)" > tests/calculateShipping.test.ts
 
# Agente 2: Implementación
claude -p "Implementa calculateShipping para pasar estos tests" > src/calculateShipping.ts
 
# Agente 3: Review
claude
> /code-review

Patrón 3: Review Incremental en PRs Grandes

Para PRs con muchos archivos, revisar por grupos:

# Revisar solo archivos de autenticación
git diff main...HEAD -- src/auth/** | claude -p "
Revisa estos cambios de autenticación:
- Vulnerabilidades de seguridad
- Validación de tokens
- Manejo de sesiones
"
 
# Revisar solo archivos de frontend
git diff main...HEAD -- src/components/** | claude -p "
Revisa componentes React:
- Problemas de rendimiento
- Accessibility
- Hooks usados correctamente
"
 
# Revisar solo cambios de DB
git diff main...HEAD -- prisma/** src/db/** | claude -p "
Revisa cambios de base de datos:
- Migraciones seguras
- Índices apropiados
- Queries optimizados
"

Crear tu Propia Skill /review

Si quieres un comando más simple que el plugin nativo, puedes crear tu propio skill:

Ubicación:

.claude/skills/review/SKILL.md

Contenido:

---
name: review
description: Revisa código con enfoque específico
argument-hint: "[archivo o directorio]"
allowed-tools: Read, Grep, Bash(git *)
---
 
Eres un code reviewer experto con las siguientes especialidades:
 
## Áreas de Análisis
 
### 1. Seguridad
- SQL injection
- XSS (Cross-Site Scripting)
- CSRF tokens
- Credenciales expuestas
- Validación de input
 
### 2. Bugs
- Null/undefined handling
- División por cero
- Race conditions
- Off-by-one errors
- Edge cases no manejados
 
### 3. Rendimiento
- Complejidad algorítmica
- Queries N+1
- Memory leaks
- Re-renders innecesarios (React)
- Cacheo ineficiente
 
### 4. Legibilidad
- Nombres descriptivos
- Funciones cortas y enfocadas
- Comentarios útiles (no obvios)
- Estructura lógica
- Principio DRY
 
### 5. Convenciones del Proyecto
- Lee .claude/CLAUDE.md
- Verifica cumplimiento de estándares
- Valida arquitectura establecida
 
## Formato de Output
 
Para cada problema encontrado:
 
**[SEVERIDAD] Título del Problema**
 
📁 **Archivo:** `ruta/al/archivo.ts:línea`
 
⚠️ **Problema:**
[Descripción breve]
 
```typescript
// Código problemático

🔧 Solución:

// Código corregido

📖 Explicación: [Por qué es un problema y cómo la solución lo resuelve]

Score de confianza: X/100


Proceso

  1. Si recibiste $ARGUMENTS, analiza ese archivo/directorio
  2. Si no hay argumentos, ejecuta git diff --staged para revisar cambios staged
  3. Lee CLAUDE.md si existe
  4. Analiza código línea por línea
  5. Genera reporte con problemas encontrados
  6. Ordena por severidad (P1 → P4)
  7. Incluye score de confianza (0-100) para cada problema
 
**Uso:**
 
```bash
# Revisar archivo específico
/review src/auth/login.ts
 
# Revisar directorio
/review src/components
 
# Revisar cambios staged
git add .
/review

Comparación: /code-review vs /review Custom

Característica/code-review (nativo)/review (custom)
InstalaciónIncluido en Claude CodeCrear manualmente
Arquitectura4 agentes paralelos1 agente
VelocidadRápido (paralelismo)Más lento
Scoring automático✅ Sí (0-100)⚠️ Manual
Integración GitHub--comment flag❌ Requiere scripting
Personalización⚠️ Limitada✅ Total control
Contexto CLAUDE.md✅ Automático✅ Puedes implementar
Uso recomendadoPRs completosCasos específicos

Cuándo usar cada uno:

  • Usa /code-review para:

    • Revisión completa de PRs
    • Integración en CI/CD
    • Máxima velocidad
    • Scoring automático de confianza
  • Usa /review custom para:

    • Revisiones enfocadas (solo seguridad, solo performance)
    • Flujo de trabajo muy específico
    • Necesitas personalizar formato de output
    • Integración con herramientas propias

Ejemplo Completo: Review de PR Real

Escenario

PR que añade autenticación JWT al proyecto.

Archivos cambiados:

  • src/auth/generateToken.ts (nuevo)
  • src/auth/verifyToken.ts (nuevo)
  • src/middleware/auth.ts (nuevo)
  • src/routes/users.ts (modificado)

Paso 1: Ejecutar Review

/code-review

Paso 2: Resultado

🔍 Analizando 4 archivos cambiados...
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 CRÍTICO (P1) - Score: 98
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
📁 src/auth/generateToken.ts:7
 
⚠️ Secret hardcodeado en código fuente
 
Código actual:
```typescript
const SECRET = 'my-secret-key-123'; // ❌ Hardcoded secret
 
function generateToken(userId: string) {
  return jwt.sign({ userId }, SECRET, { expiresIn: '1h' });
}

🔧 Solución:

const SECRET = process.env.JWT_SECRET; // ✅ Desde variable de entorno
 
if (!SECRET) {
  throw new Error('JWT_SECRET environment variable is required');
}
 
function generateToken(userId: string) {
  return jwt.sign({ userId }, SECRET, { expiresIn: '1h' });
}

📖 Explicación: Exponer el secret en código fuente es una vulnerabilidad crítica. Si el código se sube a GitHub, el secret queda comprometido.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🟠 ALTO (P2) - Score: 92 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📁 src/auth/verifyToken.ts:15

⚠️ Manejo de errores insuficiente

Código actual:

function verifyToken(token: string) {
  try {
    return jwt.verify(token, SECRET);
  } catch (error) {
    return null; // ❌ Pierde información del error
  }
}

🔧 Solución:

function verifyToken(token: string): DecodedToken {
  try {
    return jwt.verify(token, SECRET) as DecodedToken;
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      throw new AuthError('Token expired', 'TOKEN_EXPIRED');
    }
    if (error instanceof jwt.JsonWebTokenError) {
      throw new AuthError('Invalid token', 'INVALID_TOKEN');
    }
    throw new AuthError('Token verification failed', 'VERIFICATION_FAILED');
  }
}

📖 Explicación: Retornar null pierde contexto de POR QUÉ falló la verificación. El cliente no puede distinguir entre token expirado vs token malformado.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🟡 MEDIO (P3) - Score: 85 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📁 src/middleware/auth.ts:10

📝 Falta validación de formato de header

Código actual:

const token = req.headers.authorization.split(' ')[1];
// ❌ Puede crashear si no tiene formato "Bearer <token>"

🔧 Solución:

const authHeader = req.headers.authorization;
 
if (!authHeader || !authHeader.startsWith('Bearer ')) {
  throw new AuthError('Missing or invalid Authorization header', 'INVALID_AUTH_HEADER');
}
 
const token = authHeader.split(' ')[1];
 
if (!token) {
  throw new AuthError('Token not provided', 'NO_TOKEN');
}

📖 Explicación: Si el header no tiene formato "Bearer ", el código crashea. Validar formato previene errores runtime.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🔵 BAJO (P4) - Score: 81 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📁 src/routes/users.ts:25

📝 Nombre de variable poco descriptivo

Código actual:

const data = await getUserData(userId);

🔧 Solución:

const userData = await getUserData(userId);
// o más específico:
const userProfile = await getUserData(userId);

📖 Explicación: Según CLAUDE.md del proyecto, evitar nombres genéricos como "data".

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📊 Resumen:

  • Problemas encontrados: 4
  • Críticos (P1): 1 → 🚨 DEBE arreglarse antes de mergear
  • Altos (P2): 1 → ⚠️ Recomendado arreglar
  • Medios (P3): 1 → 💡 Considerar
  • Bajos (P4): 1 → 📝 Opcional

✅ Recomendación: NO APROBAR hasta arreglar P1 y P2

 
### Paso 3: Arreglar Problemas
 
```bash
# Arreglar P1 y P2
> Aplica las correcciones para los problemas P1 y P2
 
# Verificar
/code-review
 
# Resultado
✅ Todos los problemas críticos y altos resueltos
📊 Solo quedan 2 sugerencias menores (P3, P4)

Tips para Code Review Efectivo

✅ Buenas Prácticas

1. Revisar antes del push

# Workflow recomendado
git add .
/code-review  # Revisar antes de commit
[Arreglar problemas]
git commit -m "..."
git push

2. Usar CLAUDE.md para guiar reviews

Cuanto más detallado sea tu CLAUDE.md, mejor será el review:

## Code Review Checklist
 
Antes de aprobar un PR, verificar:
- [ ] Tests pasan
- [ ] Cobertura >= 80%
- [ ] No hay console.log (usar logger)
- [ ] Documentación actualizada
- [ ] Sin credenciales hardcodeadas
- [ ] Manejo de errores presente

3. Combinar review automático + humano

Claude Code Review → Arreglar problemas obvios → Human Review → Mergear

Claude atrapa:

  • Bugs técnicos
  • Vulnerabilidades
  • Violaciones de estándares

Humanos revisan:

  • Lógica de negocio
  • Decisiones arquitectónicas
  • UX/product fit

4. Revisar por capas en PRs grandes

# Capa 1: Solo seguridad
/review src/** --focus="security vulnerabilities only"
 
# Capa 2: Performance
/review src/** --focus="performance issues only"
 
# Capa 3: Legibilidad
/review src/** --focus="code readability only"

⚠️ Evita

1. No ignorar problemas P1/P2

❌ Malo:
"Es solo una sugerencia de Claude, lo ignoramos"
 
✅ Bueno:
Si score > 90, investigar seriamente

2. No revisar sin contexto

❌ Malo:
/code-review sin leer el PR description
 
✅ Bueno:
Leer qué intenta lograr el PR → /code-review → Evaluar sugerencias

3. No depender 100% de automatización

Claude Code ≠ reemplaza code review humano
Claude Code = complementa code review humano

Integración con Otras Herramientas

ESLint / Prettier

// .claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write(*.ts)",
        "hooks": [
          {
            "type": "command",
            "command": "npx eslint --fix $file"
          },
          {
            "type": "command",
            "command": "npx prettier --write $file"
          }
        ]
      }
    ]
  }
}

Flujo completo:

Claude escribe código → Hooks ejecutan ESLint/Prettier → /code-review analiza → Listo

SonarQube

# Script de integración
#!/bin/bash
 
# 1. Review de Claude
claude /code-review > claude-review.txt
 
# 2. Análisis SonarQube
sonar-scanner
 
# 3. Combinar reportes
node scripts/merge-reports.js claude-review.txt sonar-report.json

GitHub Code Scanning (CodeQL)

# .github/workflows/security-review.yml
 
name: Security Review
 
on: [pull_request]
 
jobs:
  codeql:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: github/codeql-action/init@v2
      - uses: github/codeql-action/analyze@v2
 
  claude:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Claude Security Review
        run: |
          claude /code-review --comment

Resultado: Dos capas de análisis de seguridad en cada PR.


Resumen

Code review con Claude Code transforma la revisión de código en un proceso:

Instantáneo: Análisis completo en segundos ✅ Exhaustivo: 4 agentes especializados en paralelo ✅ Inteligente: Sistema de scoring de confianza (0-100) ✅ Contextual: Lee CLAUDE.md para estándares del proyecto ✅ Integrado: Funciona en terminal y CI/CD ✅ Educativo: Explica el por qué de cada problema

Comandos clave:

# Review local
/code-review
 
# Publicar en PR
/code-review --comment
 
# Review custom
/review src/components

Próximo punto (8.5): Aprenderás a integrar Claude Code con IDEs (VS Code, Cursor, JetBrains) para un flujo de trabajo aún más fluido.


Verificación de Comprensión

Antes de continuar:

  • Entiendes la diferencia entre /code-review (nativo) y /review (custom)
  • Conoces el sistema de severidad (P1-P4) y scoring (0-100)
  • Sabes configurar el threshold de confianza
  • Puedes crear un CLAUDE.md para guiar reviews
  • Has probado /code-review en un PR real
  • Entiendes el patrón Writer/Reviewer
  • Sabes integrar code review en CI/CD

8.5 Integración con IDEs

Objetivo

Dominar la integración de Claude Code con diferentes entornos de desarrollo (VS Code, JetBrains, Cursor, Windsurf, Vim/Neovim), comprendiendo cuándo usar la terminal vs la extensión del IDE, y configurando el flujo de trabajo óptimo según el tipo de tarea.


Introducción

Claude Code puede usarse de dos formas principales:

  1. Terminal CLI → Análisis profundo, multi-archivo, operaciones complejas
  2. Extensión de IDE → Feedback visual, edición inline, flujo interactivo

Filosofía: Terminal vs IDE

┌─────────────────────────────────────────────────────────┐
│  TERMINAL CLI (claude)                                  │
│                                                         │
│  ✅ Análisis completo del codebase                      │
│  ✅ Multi-file operations (refactors, migraciones)     │
│  ✅ 5.5x más eficiente en tokens                       │
│  ✅ Código "production-ready" (~30% menos rework)      │
│  ✅ Comandos bash + git integration                    │
│  ✅ Plan mode, checkpoints, /compact                   │
│                                                         │
│  ❌ Sin feedback visual en tiempo real                 │
│  ❌ Requiere precisión en instrucciones                │
└─────────────────────────────────────────────────────────┘
 
┌─────────────────────────────────────────────────────────┐
│  IDE EXTENSION (VS Code, JetBrains, etc.)              │
│                                                         │
│  ✅ Feedback visual inmediato                          │
│  ✅ Diffs visuales side-by-side                        │
│  ✅ Edición inline con Accept/Reject                   │
│  ✅ Mención de archivos con @                          │
│  ✅ Integración nativa con IDE features                │
│                                                         │
│  ❌ Enfoque file-by-file (no multi-file desde cero)   │
│  ❌ Menos eficiente en tokens                          │
│  ❌ Requiere revisión individual de cambios            │
└─────────────────────────────────────────────────────────┘

La clave: No es "terminal O IDE", es "terminal Y IDE" según la tarea.


VS Code Extension

La extensión oficial de Claude Code para VS Code es la integración más completa y recomendada para usuarios que prefieren entornos visuales.

Instalación

Método 1: Desde VS Code

1. Abrir VS Code
2. Cmd+Shift+X (Mac) o Ctrl+Shift+X (Windows/Linux)
3. Buscar "Claude Code"
4. Instalar la extensión oficial de Anthropic

Método 2: Desde terminal

code --install-extension anthropic.claude-code

Requisitos:

  • VS Code 1.98.0 o superior
  • Claude Code CLI instalado (npm install -g @anthropic-ai/claude-code)

Interfaz Principal

┌─────────────────────────────────────────────────────────┐
│  VS Code                                   [X] [ ] [-]  │
├─────────────────────────────────────────────────────────┤
│  File  Edit  Selection  View  Go  ...        🔍  ⚙️    │
├──────────┬──────────────────────────────────────────────┤
│          │  📁 src/                                     │
│ 📂       │    📄 index.ts                               │
│ 🔍       │    📁 components/                            │
│ 🌿       │      📄 Header.tsx                           │
│ 🐛       │      📄 Footer.tsx                           │
│ 🧩       │    📁 utils/                                 │
│          │                                              │
│ 🤖       │  ┌────────────────────────────────────────┐  │
│ Claude   │  │  Header.tsx                            │  │
│          │  │                                        │  │
│          │  │  export function Header() {            │  │
│          │  │    return <header>...</header>         │  │
│          │  │  }                                     │  │
│          │  │                                        │  │
│          │  └────────────────────────────────────────┘  │
├──────────┴──────────────────────────────────────────────┤
│  🤖 Claude Code                                         │
│  ┌────────────────────────────────────────────────────┐ │
│  │ 💬 What do you want to build?                      │ │
│  │                                                    │ │
│  │ [Type your message... Shift+Enter for new line]   │ │
│  └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

Características Principales

1. Panel Lateral Dedicado

🤖 Claude Code (panel lateral)
 
┌─────────────────────────────────────────┐
│  New Conversation  [+]        [⚙️]      │
├─────────────────────────────────────────┤
│  📝 Conversation History                │
│                                         │
│  🟢 Current: Refactor auth system       │
│  ⚪ Add dark mode toggle               │
│  ⚪ Fix login bug                      │
│  ⚪ Create user dashboard              │
├─────────────────────────────────────────┤
│  💬 Chat                                │
│                                         │
│  You: Refactor the auth to use JWT     │
│                                         │
│  Claude: I'll refactor authentication.  │
│  Let me start by reading...            │
│                                         │
│  📁 Read: src/auth/login.ts            │
│  📁 Read: src/auth/session.ts          │
│  ✏️  Edit: src/auth/jwt.ts [Accept]    │
│                                         │
├─────────────────────────────────────────┤
│  💬 Type your message...                │
│  [Shift+Enter for new line]            │
└─────────────────────────────────────────┘

2. Diffs Visuales

Cuando Claude modifica un archivo, VS Code muestra un diff side-by-side:

┌──────────────────────┬──────────────────────┐
│  BEFORE              │  AFTER               │
├──────────────────────┼──────────────────────┤
│  function login() {  │  async function      │
│    // old code       │  login() {           │
│    validateUser();   │    const isValid =   │
│    createSession();  │    await validate(); │
│  }                   │    if (!isValid) {   │
│                      │      throw Error();  │
│                      │    }                 │
│                      │    const token =     │
│                      │    await createJWT();│
│                      │    return token;     │
│                      │  }                   │
├──────────────────────┴──────────────────────┤
│  [Accept]  [Reject]  [Tell Claude what to  │
│                       do instead]           │
└─────────────────────────────────────────────┘

Opciones:

  • Accept: Aplicar los cambios
  • Reject: Descartar los cambios
  • Tell Claude what to do instead: Dar instrucciones alternativas

3. Mención de Archivos con @

💬 Tu mensaje:
 
Refactoriza @src/auth/login.ts para usar
el patrón del @src/payments/stripe.ts
 
┌─────────────────────────────────────────┐
│  @ [aparece menú de archivos]          │
│                                         │
│  🔍 Search files...                     │
│                                         │
│  📁 src/auth/login.ts                  │
│  📁 src/auth/register.ts               │
│  📁 src/payments/stripe.ts             │
│  📁 src/utils/validation.ts            │
└─────────────────────────────────────────┘

Sintaxis avanzada:

@file.ts          → Archivo completo
@file.ts#5-10     → Líneas 5 a 10
@src/**/*.test.ts → Patrón glob

4. Selección Automática de Contexto

Si tienes código seleccionado en el editor, Claude lo ve automáticamente:

// Tienes este código seleccionado en el editor:
function calculateTotal(items: Item[]) {
  return items.reduce((sum, item) => sum + item.price, 0);
}
💬 Tu mensaje (sin pegar código):
 
Esta función es lenta con arrays grandes, optimízala
 
Claude automáticamente ve el código seleccionado ✅

5. Múltiples Conversaciones

Abre varias conversaciones en pestañas separadas:

┌─────────────────────────────────────────┐
│ [Frontend] [Backend] [Tests] [+]       │
├─────────────────────────────────────────┤
│  Cada pestaña = conversación separada  │
│  con su propio contexto                │
└─────────────────────────────────────────┘

Atajos de Teclado

AcciónMacWindows/Linux
Abrir Claude CodeCmd+EscCtrl+Esc
Nueva conversaciónCmd+NCtrl+N
Nueva pestañaCmd+Shift+EscCtrl+Shift+Esc
Insertar @-menciónOption+KAlt+K
Enviar mensajeEnterEnter
Nueva línea en mensajeShift+EnterShift+Enter
Paleta de comandosCmd+Shift+PCtrl+Shift+P

Personalizar atajos:

// keybindings.json en VS Code
{
  "key": "cmd+shift+c",
  "command": "claude-code.newConversation"
}

Configuración

Ubicación:

VS Code Settings → Extensions → Claude Code

O en settings.json:

{
  // Modelo por defecto
  "claudeCode.selectedModel": "claude-sonnet-4-20250514",
 
  // Modo de permisos inicial
  "claudeCode.initialPermissionMode": "default",
  // Opciones: "default", "plan", "acceptEdits", "bypassPermissions"
 
  // Ubicación del panel
  "claudeCode.preferredLocation": "sidebar",
  // Opciones: "sidebar", "panel"
 
  // Auto-guardar archivos antes de que Claude los lea/escriba
  "claudeCode.autosave": true,
 
  // Usar Ctrl/Cmd+Enter para enviar (en lugar de Enter)
  "claudeCode.useCtrlEnterToSend": false,
 
  // Respetar .gitignore al buscar archivos
  "claudeCode.respectGitIgnore": true,
 
  // Terminal integrada vs separada
  "claudeCode.useTerminal": false
}

Modos de Permiso

Claude Code en VS Code tiene 3 modos de permiso:

1. Normal (Default)

Claude: Quiero modificar src/auth/login.ts
 
┌─────────────────────────────────────────┐
│  ⚠️  Claude Code wants to:              │
│                                         │
│  ✏️  Edit src/auth/login.ts             │
│                                         │
│  [Allow] [Reject] [Always Allow]       │
└─────────────────────────────────────────┘

2. Plan Mode

Claude: Antes de hacer cambios, aquí está mi plan:
 
1. Leer src/auth/login.ts
2. Crear src/auth/jwt.ts con helpers
3. Modificar login.ts para usar JWT
4. Actualizar tests en tests/auth.test.ts
 
¿Procedo?
 
[Yes] [No] [Modify Plan]

3. Auto-accept

Claude modifica archivos automáticamente sin pedir permiso ✅
(Solo en proyectos de confianza)

Cambiar modo:

Cmd+Shift+P → "Claude Code: Change Permission Mode"

JetBrains IDEs

La extensión oficial de Claude Code para IntelliJ IDEA, PyCharm, WebStorm, PhpStorm, GoLand, Android Studio.

Instalación

1. File → Settings → Plugins
2. Marketplace → Buscar "Claude Code [Beta]"
3. Install
4. Restart IDE

Activación

1. Tools → Claude Code [Beta] → Settings
2. Configurar comando: claude (default)
3. Habilitar multi-línea, auto-updates

Atajos de Teclado

AcciónMacWindows/Linux
Abrir Claude CodeCmd+EscCtrl+Esc
Insertar referenciaCmd+Option+KCtrl+Alt+K

Características Específicas

1. Diff Viewer Nativo

Los cambios de Claude se muestran en el diff viewer nativo de JetBrains:

┌─────────────────────────────────────────────────────┐
│  IntelliJ IDEA - Diff View                         │
├─────────────────────────────────────────────────────┤
│  Local Version           Claude's Version           │
│                                                     │
│  [código original]   →   [código modificado]       │
│                                                     │
│  [Accept Left] [Accept Right] [Merge]              │
└─────────────────────────────────────────────────────┘

2. Diagnostic Sharing

Claude Code recibe automáticamente errores del IDE:

┌─────────────────────────────────────────┐
│  IntelliJ detecta error:                │
│                                         │
│  ❌ Cannot find symbol 'userId'         │
│     at line 42 in UserService.java     │
└─────────────────────────────────────────┘
 
Claude Code automáticamente sabe del error
y puede sugerir fixes contextualmente ✅

3. Selection Context

El código seleccionado en el editor se comparte automáticamente:

// Tienes esto seleccionado:
public void processPayment(Payment payment) {
  // ...
}
💬 Mensaje a Claude:
 
Optimiza esta función
 
Claude ya tiene el contexto de la selección ✅

Configuración WSL

Si usas Windows Subsystem for Linux:

// Settings → Tools → Claude Code [Beta]
{
  "claudeCommand": "wsl -d Ubuntu claude"
}

Claude Agent (Nuevo - Septiembre 2025)

JetBrains lanzó Claude Agent integrado nativamente:

Diferencias con el plugin:

Claude Code PluginClaude Agent Nativo
Plugin betaNativo en IDE
Requiere Claude Code CLIIncluido en JetBrains AI subscription
Ventana separadaIntegrado en IDE UI
Menos features nativasMás features IDE-native

Cuándo usar cada uno:

  • Plugin Claude Code: Para flujo idéntico al CLI
  • Claude Agent: Para integración más profunda con JetBrains

Cursor IDE

Cursor es un fork de VS Code con IA integrada desde cero.

Diferencias Clave con VS Code

┌─────────────────────────────────────────┐
│  VS Code + Claude Code Extension       │
│                                         │
│  • Extensión añadida a VS Code         │
│  • Usa Claude Code CLI                 │
│  • Experiencia similar a terminal      │
└─────────────────────────────────────────┘
 
┌─────────────────────────────────────────┐
│  Cursor                                 │
│                                         │
│  • IDE completo diseñado para IA       │
│  • IA integrada en cada funcionalidad  │
│  • Puede usar modelos Claude           │
│  • GUI-first approach                  │
└─────────────────────────────────────────┘

Integración con Claude Code

Cursor puede ejecutar Claude Code CLI en su terminal integrada:

# Dentro de Cursor
Terminal claude
 
# O abrir Claude Code en panel lateral
# (requiere extensión Claude Code instalada)

Características de Cursor

┌─────────────────────────────────────────┐
│  🎯 Cmd+K: Inline editing               │
│     Selecciona código → Cmd+K → Edit    │
│                                         │
│  💬 Cmd+L: Chat panel                   │
│     Conversación con IA sobre codebase │
│                                         │
│  🤖 Tab: Autocomplete                   │
│     Sugerencias contextuales avanzadas │
│                                         │
│  📁 @: Mention files/docs               │
│     @file.ts, @docs, @web, @folder     │
└─────────────────────────────────────────┘

Cuándo Usar Cursor vs Claude Code

Usa Cursor para:

  • ✅ Exploración rápida
  • ✅ Ediciones inline iterativas
  • ✅ Flow state coding (feedback inmediato)
  • ✅ Prototipos rápidos
  • ✅ Aprendiendo un nuevo codebase

Usa Claude Code CLI para:

  • ✅ Refactors multi-archivo
  • ✅ Migraciones complejas
  • ✅ Documentación extensa
  • ✅ Test suites completas
  • ✅ Operaciones DevOps
  • ✅ Profundidad > velocidad

Windsurf

Windsurf es un IDE con Cascade, su asistente IA nativo (powered by Claude).

Integración con Claude Code

# Opción 1: Usar Claude Code CLI en terminal de Windsurf
Terminal claude
 
# Opción 2: Configurar Claude Code como MCP server
# (permite a Cascade usar Claude Code como herramienta)

Características de Windsurf

  • Cascade: Asistente multimodal (soporta imágenes)
  • Flows: Automatizaciones predefinidas
  • Deep IDE integration: Acceso a debugger, git, terminal

Nota Importante (2026)

Anthropic ha limitado acceso a modelos Claude en Windsurf, incentivando el uso de Claude Code CLI oficial.

Recomendación:

  • Usa Claude Code CLI como herramienta principal
  • Usa Windsurf si ya tienes workflow establecido con Cascade

Vim / Neovim

Para desarrolladores que prefieren editores modal.

Plugins Disponibles

Neovim

1. coder/claudecode.nvim (Recomendado)

-- Con packer.nvim
use {
  'coder/claudecode.nvim',
  config = function()
    require('claudecode').setup({
      -- Configuración
    })
  end
}

Características:

  • ✅ Protocolo MCP bidireccional
  • ✅ Diagnósticos integrados
  • ✅ Soporte nvim-tree, oil.nvim, neotree
  • ✅ Vista previa de diffs

2. greggh/claude-code.nvim

use {
  'greggh/claude-code.nvim',
  config = function()
    require('claude-code').setup({
      claude_cmd = 'claude',
      enable_auto_show = true
    })
  end
}

Características:

  • ✅ Toggle terminal integrado
  • ✅ Argumentos CLI dinámicos
  • ✅ Auto-detección de cambios

3. dreemanuel/claude-code.nvim

use 'dreemanuel/claude-code.nvim'

Características:

  • ✅ Preview de diffs
  • ✅ Integración Tmux
  • ✅ Soporte multi-buffer

Configuración Ejemplo (Neovim)

-- init.lua o ~/.config/nvim/lua/plugins/claude.lua
 
local claude = require('claudecode')
 
claude.setup({
  -- Comando Claude Code CLI
  claude_cmd = 'claude',
 
  -- Auto-mostrar ventana al detectar cambios
  auto_show = true,
 
  -- Keymaps
  keymaps = {
    toggle = '<leader>ct',  -- Toggle Claude panel
    accept = '<leader>ca',  -- Accept changes
    reject = '<leader>cr',  -- Reject changes
  },
 
  -- Window settings
  window = {
    position = 'right',
    width = 80,
  }
})
 
-- Atajos personalizados
vim.keymap.set('n', '<leader>cc', ':ClaudeCodeToggle<CR>', { desc = 'Toggle Claude Code' })
vim.keymap.set('v', '<leader>ca', ':ClaudeCodeAsk<CR>', { desc = 'Ask Claude about selection' })

Vim

claude.vim

" .vimrc
Plug 'anthropic/claude.vim'
 
" Configuración
let g:claude_cmd = 'claude'
let g:claude_auto_accept = 0
 
" Atajos
nnoremap <leader>ct :ClaudeToggle<CR>
nnoremap <leader>ca :ClaudeAccept<CR>

Emacs

Para usuarios de Emacs.

Plugin: claude-code-ide.el

Instalación:

;; init.el
 
(use-package claude-code-ide
  :ensure t
  :config
  (setq claude-code-cmd "claude")
  (setq claude-code-auto-show t)
 
  ;; Keybindings
  (global-set-key (kbd "C-c c t") 'claude-code-toggle)
  (global-set-key (kbd "C-c c a") 'claude-code-accept)
  (global-set-key (kbd "C-c c r") 'claude-code-reject))

Características:

  • ✅ Integración bidireccional con CLI
  • ✅ Vista ediff para cambios
  • ✅ Protocolo MCP
  • ✅ Acceso a diagnósticos

Comparación de IDEs

IDEInstalaciónMadurezVisual FeaturesRecomendado Para
VS CodeExtension marketplace✅ Oficial⭐⭐⭐⭐⭐Mayoría de usuarios
CursorIDE standalone✅ Maduro⭐⭐⭐⭐⭐Flow state, exploración
WindsurfIDE standalone✅ Maduro⭐⭐⭐⭐Usuarios de Cascade
JetBrainsPlugin marketplace⚠️ Beta⭐⭐⭐⭐Usuarios Java/Kotlin/Python
NeovimPlugin manager⚠️ Community⭐⭐⭐Power users, terminal-first
VimPlugin manager⚠️ Community⭐⭐Puristas vim
EmacsPackage manager⚠️ Community⭐⭐⭐Usuarios Emacs

Estrategia de Uso Híbrido (Recomendado)

La mejor estrategia es combinar terminal + IDE según la tarea:

Escenario 1: Feature Nueva

┌──────────────────────────────────────────┐
│  Paso 1: Planificación                  │
│  📍 Dónde: Terminal CLI                  │
│  📝 Qué: claude (con plan mode)         │
│                                          │
│  > Diseña arquitectura para feature X   │
│  > Identifica archivos a modificar      │
│  > Crea plan detallado                  │
└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐
│  Paso 2: Implementación                 │
│  📍 Dónde: Terminal CLI                  │
│  📝 Qué: claude                          │
│                                          │
│  > Implementa según el plan             │
│  > Genera tests                         │
│  > Refactoriza                          │
└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐
│  Paso 3: Ajustes Finos                  │
│  📍 Dónde: VS Code Extension             │
│  📝 Qué: Ediciones inline                │
│                                          │
│  > Seleccionar código → Optimizar       │
│  > Ajustar nombres de variables         │
│  > Formatear comentarios                │
└──────────────────────────────────────────┘

Escenario 2: Bug Fix

┌──────────────────────────────────────────┐
│  Paso 1: Exploración                    │
│  📍 Dónde: Cursor o VS Code Extension    │
│  📝 Qué: Chat sobre el codebase          │
│                                          │
│  > ¿Dónde está la lógica de pagos?      │
│  > Explica el flujo de checkout         │
│  > ¿Qué hace esta función?              │
└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐
│  Paso 2: Fix                             │
│  📍 Dónde: VS Code Extension             │
│  📝 Qué: Edición inline                  │
│                                          │
│  > Seleccionar función buggy            │
│  > "Arregla este bug: [descripción]"    │
│  > Accept/Reject cambios visualmente    │
└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐
│  Paso 3: Tests                           │
│  📍 Dónde: Terminal CLI                  │
│  📝 Qué: claude                          │
│                                          │
│  > Escribe tests para este fix          │
│  > Ejecuta tests                        │
│  > Verifica que el bug no regrese       │
└──────────────────────────────────────────┘

Escenario 3: Refactor Masivo

┌──────────────────────────────────────────┐
│  📍 SOLO Terminal CLI                    │
│                                          │
│  > Migra todos los componentes de       │
│    clase a funcionales                  │
│                                          │
│  > Renombra 50 archivos siguiendo       │
│    nueva convención                     │
│                                          │
│  > Actualiza imports en todo el         │
│    proyecto                             │
└──────────────────────────────────────────┘
 
⚠️ IDE extensions no están optimizadas
   para operaciones multi-archivo masivas

Escenario 4: Aprendizaje de Codebase

┌──────────────────────────────────────────┐
│  📍 Cursor o VS Code Extension           │
│                                          │
│  > ¿Cómo funciona la autenticación?     │
│  > Explica @src/auth/login.ts           │
│  > ¿Qué hace @utils/validation.ts?      │
│  > Muéstrame el flujo de datos          │
└──────────────────────────────────────────┘
 
✅ Chat visual es mejor para onboarding

Configuración Avanzada

Compartir Configuración entre Terminal y VS Code

La extensión VS Code usa la misma configuración que el CLI:

~/.claude/settings.json     → Global (CLI + VS Code)
.claude/settings.json       → Proyecto (CLI + VS Code)

Ejemplo:

// ~/.claude/settings.json
{
  "model": "claude-sonnet-4-20250514",
  "permissions": {
    "allowedTools": ["Read", "Write", "Bash(git *)"]
  },
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write(*.ts)",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write $file"
          }
        ]
      }
    ]
  }
}

Esta configuración aplica tanto en terminal como en VS Code

MCP Servers

Se configuran en ~/.claude.json o .claude.json:

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "${GITHUB_TOKEN}"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem"]
    }
  }
}

⚠️ Advertencia: No habilites todos los MCPs simultáneamente:

Ventana de contexto: 200K tokens
Con demasiados MCPs: ~70K tokens disponibles ❌
 
Recomendación: Habilita solo MCPs que uses activamente

Settings Específicos de VS Code

Estos settings solo aplican a la extensión VS Code:

// .vscode/settings.json (específico del proyecto)
{
  // Ubicación del panel
  "claudeCode.preferredLocation": "panel",
 
  // Auto-guardar antes de operaciones
  "claudeCode.autosave": true,
 
  // Ctrl+Enter para enviar
  "claudeCode.useCtrlEnterToSend": true,
 
  // Respetar .gitignore
  "claudeCode.respectGitIgnore": true
}

Flujo de Trabajo Ejemplo: Feature Completa

Setup Inicial

IDE: VS Code con extensión Claude Code
Terminal: También disponible
Proyecto: E-commerce app
Feature: Añadir wishlist

Paso 1: Planificación (Terminal)

$ claude
 
> Necesito añadir una funcionalidad de wishlist.
> Antes de implementar, dame un plan detallado.
 
Claude:
Aquí está el plan:
 
1. Backend:
   - Crear modelo Wishlist en DB (Prisma)
   - API endpoints: GET/POST/DELETE /api/wishlist
   - Middleware de autenticación
 
2. Frontend:
   - Componente WishlistButton
   - Hook useWishlist
   - Página /wishlist
 
3. Tests:
   - Tests API
   - Tests componentes
 
¿Procedo con la implementación?

Paso 2: Implementación Backend (Terminal)

> Sí, implementa el backend completo con tests
 
[Claude genera:]
- prisma/schema.prisma (modificado)
- src/api/wishlist.ts (nuevo)
- src/middleware/auth.ts (modificado)
- tests/api/wishlist.test.ts (nuevo)
 
> Ejecuta los tests
 
 All tests pass

Paso 3: Frontend (VS Code Extension)

Abre VS Code → Panel Claude Code

💬 Tu mensaje:
 
Ahora implementa el frontend según el plan.
Usa @src/components/CartButton.tsx como referencia
para el estilo del WishlistButton

Claude genera:

  • src/components/WishlistButton.tsx
  • src/hooks/useWishlist.ts
  • src/pages/wishlist.tsx

Ves los cambios visualmente → Accept

Paso 4: Ajustes Finos (VS Code Extension)

Seleccionas el componente WishlistButton:

// Código seleccionado
export function WishlistButton({ productId }: Props) {
  const { addToWishlist } = useWishlist();
  // ...
}
💬 Mensaje (con código seleccionado):
 
Añade loading state y toast notification al agregar

Claude modifica inline → Accept

Paso 5: Review Final (Terminal)

$ claude
 
> /code-review
 
[Claude analiza todo el feature]
 
 No se encontraron problemas críticos
💡 2 sugerencias menores (P4)

Paso 6: Commit (Terminal)

$ claude
 
> Crea un commit con un mensaje descriptivo
 
[Claude:]
git add .
git commit -m "feat: add wishlist functionality
 
- Backend: Prisma model + API endpoints
- Frontend: WishlistButton + hook + page
- Tests: Full coverage for API and components
 
Closes #123"

Troubleshooting Común

Problema 1: Extensión VS Code no conecta con CLI

Síntomas:

⚠️ Claude Code CLI not found

Solución:

# Verificar instalación
which claude
 
# Si no está instalado:
npm install -g @anthropic-ai/claude-code
 
# Reiniciar VS Code

Problema 2: Cambios no se reflejan en IDE

Síntomas:

Claude dice que modificó archivo.ts
pero no veo cambios en el editor

Solución:

// settings.json
{
  "claudeCode.autosave": true  // ← Activar
}

Problema 3: Contexto muy grande

Síntomas:

⚠️ Context window 95% full

Solución:

# En VS Code Extension
Cmd+Shift+P → "Claude Code: Compact Conversation"
 
# O en terminal
/compact

Problema 4: MCP servers ralentizan IDE

Síntomas:

VS Code Extension muy lento

Solución:

// ~/.claude.json
{
  "mcpServers": {
    // Deshabilitar MCPs que no uses
    // Solo dejar los esenciales
    "github": { ... }  // ← Solo este
  }
}

Resumen

Integración de Claude Code con IDEs ofrece flexibilidad para adaptar el flujo de trabajo según la tarea:

Cuándo usar cada herramienta

TareaTerminal CLIVS Code ExtCursorJetBrains
Refactor multi-archivo
Exploración de código⚠️
Ediciones rápidas
Tests completos⚠️⚠️⚠️
Documentación⚠️⚠️⚠️
Code review
Git operations⚠️⚠️⚠️
Plan mode

Recomendación general:

✅ Terminal CLI:      Profundidad, multi-archivo, autonomía
✅ VS Code Extension: Balance ideal (visual + features completos)
✅ Cursor:            Exploración rápida, prototipado
✅ JetBrains:         Si ya usas IntelliJ/PyCharm/WebStorm

Próximo módulo (Módulo 9): Gestión de Contexto y Tokens - aprenderás a optimizar el uso de la ventana de contexto para sesiones más largas y eficientes.


Verificación de Comprensión

Antes de continuar:

  • Entiendes la diferencia entre terminal CLI y extensión IDE
  • Sabes instalar la extensión en VS Code
  • Conoces los atajos de teclado principales
  • Puedes mencionar archivos con @
  • Entiendes los modos de permiso
  • Sabes cuándo usar terminal vs IDE según la tarea
  • Has configurado settings.json compartido
  • Comprendes la estrategia de uso híbrido

8.6 Git Workflow con Claude Code

Claude Code se integra perfectamente con Git para manejar todo el flujo de desarrollo: desde crear ramas hasta preparar pull requests completas. En esta sección aprenderás a usar Claude como tu asistente Git personal.


Índice

  1. Comandos Git básicos con Claude
  2. Crear ramas y features
  3. Commits inteligentes
  4. Pull Requests automatizadas
  5. Gestión de conflictos
  6. Workflow completo de feature
  7. Buenas prácticas
  8. Casos de uso avanzados

Comandos Git básicos con Claude

Claude puede ejecutar cualquier comando Git por ti, pero lo hace de forma más inteligente.

Verificar estado del repositorio

> ¿Cuál es el estado actual del repositorio?

Claude ejecutará git status y te dará un resumen interpretado:

El repositorio tiene:
- 3 archivos modificados en el área de staging
- 2 archivos sin seguimiento
- Estás en la rama feature/user-auth
- Tu rama está 2 commits adelante de origin/main

Ver cambios pendientes

> Muéstrame qué cambios he hecho

Claude ejecuta git diff y te explica los cambios en lenguaje natural:

Has modificado src/auth/login.ts:
- Añadiste validación de email
- Cambiaste el timeout de sesión de 1h a 24h
- Agregaste logs para debugging

Ver historial de commits

> Muéstrame los últimos commits

Claude ejecuta git log con formato legible y te muestra el historial relevante.


Crear ramas y features

Flujo básico: crear rama para nueva feature

Opción 1: Dejar que Claude decida el nombre

> Voy a implementar autenticación con JWT.
  Crea una rama apropiada y cámbiate a ella.

Claude ejecutará:

git checkout -b feature/jwt-authentication

Opción 2: Especificar el nombre

> Crea una rama llamada feature/user-auth

Claude ejecuta:

git checkout -b feature/user-auth

Verificar en qué rama estás

> ¿En qué rama estoy?

Claude verifica y te responde:

Estás en la rama feature/user-auth

Cambiar entre ramas

> Cámbiate a main
git checkout main
> Vuelve a la rama anterior
git checkout -

Commits inteligentes

Claude Code tiene integración profunda con Git para crear commits de calidad profesional.

Commit básico

Forma simple:

> Haz commit de estos cambios

Claude:

  1. Ejecuta git status para ver qué hay modificado
  2. Ejecuta git diff para analizar los cambios
  3. Genera un mensaje descriptivo
  4. Añade los archivos relevantes con git add
  5. Crea el commit

Ejemplo de mensaje generado:

feat: add JWT authentication to login endpoint
 
- Implement token generation with 24h expiration
- Add email validation middleware
- Include refresh token mechanism
 
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Commit con mensaje específico

> Haz commit con el mensaje "fix: corregir validación de email"

Claude ejecuta:

git add [archivos relevantes]
git commit -m "fix: corregir validación de email
 
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

Commit parcial (staging selectivo)

> Haz commit solo de los cambios en src/auth/login.ts

Claude:

git add src/auth/login.ts
git commit -m "feat: update login validation logic
 
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

Conventional Commits

Claude sigue automáticamente el formato Conventional Commits:

PrefijoUso
feat:Nueva funcionalidad
fix:Corrección de bugs
docs:Cambios en documentación
style:Formato, espacios, etc. (no afecta código)
refactor:Refactorización (no cambia funcionalidad)
test:Añadir o modificar tests
chore:Cambios de build, dependencias, etc.
perf:Mejoras de rendimiento

Ejemplo:

> He añadido tests para la función validateEmail.
  Haz commit.

Claude genera:

test: add unit tests for validateEmail function
 
- Test valid email formats
- Test invalid formats return false
- Test error handling for non-string input
 
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Ver el log de commits recientes

> Muéstrame el historial de commits de esta rama

Claude ejecuta:

git log --oneline --graph --decorate -10

Pull Requests automatizadas

Una de las funcionalidades más potentes de Claude Code es la creación automática de PRs completas.

Crear PR básica

> Crea un pull request para esta rama

Claude:

  1. Verifica que estés en una rama de feature
  2. Ejecuta git status y git diff main...HEAD para ver todos los cambios
  3. Analiza todos los commits desde que divergiste de main (no solo el último)
  4. Pushea la rama al remoto si es necesario
  5. Genera título y descripción completa
  6. Crea la PR usando gh pr create

Ejemplo de PR generada:

## Summary
 
- Implementa autenticación JWT completa
- Añade middleware de validación de tokens
- Incluye mecanismo de refresh tokens
- Agrega tests exhaustivos
 
## Changes
 
- `src/auth/jwt.ts`: Generación y validación de tokens
- `src/middleware/auth.ts`: Middleware de autenticación
- `tests/auth/jwt.test.ts`: Tests unitarios
- `README.md`: Documentación actualizada
 
## Test plan
 
- [x] Tests unitarios pasan (npm test)
- [x] Tests de integración ejecutados
- [ ] Probado manualmente en entorno local
- [ ] Revisado por al menos un compañero
 
🤖 Generated with Claude Code

PR con contexto específico

> Crea un PR para mergear a develop (no main)

Claude ajusta el target branch:

gh pr create --base develop --title "..." --body "..."

PR con revisores asignados

> Crea un PR y asigna como reviewers a @john y @sarah

Claude:

gh pr create --title "..." --body "..." \
  --reviewer john,sarah

Ver PRs abiertas

> Muéstrame las PRs abiertas de este repo

Claude ejecuta:

gh pr list

Y te muestra un resumen legible:

PRs abiertas:
1. #42: Add JWT authentication (feature/jwt-auth)
2. #41: Fix email validation (fix/email-validator)
3. #40: Update docs (docs/api-reference)

Ver detalles de una PR

> Muéstrame los detalles de la PR #42

Claude ejecuta:

gh pr view 42

Gestión de conflictos

Claude puede ayudarte a resolver conflictos de merge de forma inteligente.

Detectar conflictos

> Intenta mergear main en mi rama

Claude ejecuta:

git merge main

Si hay conflictos, te avisa:

Hay conflictos de merge en:
- src/auth/login.ts
- package.json
 
¿Quieres que te ayude a resolverlos?

Resolver conflictos

> Sí, ayúdame a resolverlos

Claude:

  1. Lee los archivos con conflictos
  2. Analiza las diferencias entre <<<<<<< HEAD y >>>>>>> main
  3. Propone una resolución inteligente
  4. Aplica los cambios
  5. Marca como resueltos con git add

Ejemplo:

En src/auth/login.ts hay un conflicto en la función validateEmail.
 
Tu versión (HEAD):
- Valida formato con regex complejo
- Retorna boolean
 
Versión de main:
- Valida con librería validator.js
- Lanza excepciones
 
Propongo mantener la validación con validator.js (más robusta)
pero retornar boolean en lugar de lanzar excepciones (tu lógica).
 
¿Aplico esta resolución?

Ver estado de merge

> ¿Quedan conflictos por resolver?

Claude verifica:

git status

Y te responde:

✅ Todos los conflictos resueltos.
Archivos listos para commit:
- src/auth/login.ts
- package.json
 
¿Quieres hacer commit del merge?

Workflow completo de feature

Aquí está el flujo completo para implementar una feature desde cero hasta mergear.

Paso 1: Crear rama

> Voy a implementar búsqueda de usuarios.
  Prepara el entorno.

Claude:

# Asegurarse de estar en main actualizado
git checkout main
git pull origin main
 
# Crear rama de feature
git checkout -b feature/user-search

Paso 2: Implementar con TDD

> Implementa búsqueda de usuarios con TDD.
 
Endpoint: GET /api/users/search?q=john
 
Requisitos:
- Búsqueda case-insensitive
- Buscar en nombre y email
- Paginación (limit, offset)
- Tests completos

Claude implementa toda la funcionalidad siguiendo TDD.

Paso 3: Commits incrementales

Durante el desarrollo, Claude hace commits automáticos al completar cada parte:

✓ test: add tests for user search endpoint
✓ feat: implement basic user search functionality
✓ feat: add case-insensitive search
✓ feat: add pagination to search results
✓ refactor: extract search logic to service

Paso 4: Code review local

> Haz un code review de todos los cambios antes de crear la PR

Claude analiza el código y sugiere mejoras.

Paso 5: Aplicar mejoras

> Aplica las mejoras P1 y P2

Claude refactoriza y hace commit:

✓ refactor: improve search query performance

Paso 6: Verificar tests

> Ejecuta todos los tests
npm test
✅ All tests passing (42 tests, 0 failures)

Paso 7: Crear PR

> Crea un pull request

Claude:

  1. Push de la rama:
git push -u origin feature/user-search
  1. Crea PR:
gh pr create --title "feat: add user search endpoint" \
  --body "$(cat <<'EOF'
## Summary
 
- Implements user search endpoint with pagination
- Case-insensitive search across name and email fields
- Comprehensive test coverage
 
## Changes
 
- `src/routes/users.ts`: New search endpoint
- `src/services/userService.ts`: Search logic
- `tests/routes/users.test.ts`: Search tests
 
## Test plan
 
- [x] Unit tests passing
- [x] Integration tests passing
- [x] Manual testing completed
- [x] Performance verified (sub-100ms queries)
 
🤖 Generated with Claude Code
EOF
)"
  1. Te da el URL:
✅ Pull Request created: https://github.com/user/repo/pull/43

Paso 8: Hacer cambios después del code review

Si un reviewer pide cambios:

> El reviewer pidió mejorar la validación del parámetro 'q'.
  Haz los cambios necesarios.

Claude:

  1. Implementa los cambios
  2. Ejecuta tests
  3. Hace commit
  4. Pushea automáticamente
✓ feat: improve query parameter validation
✓ Pushed to feature/user-search

La PR se actualiza automáticamente.

Paso 9: Mergear

Cuando la PR está aprobada:

> La PR fue aprobada. Mergéala a main.

Claude ejecuta:

gh pr merge 43 --squash --delete-branch

O si prefieres merge commit:

gh pr merge 43 --merge

Buenas prácticas

1. Commits atómicos

❌ Evitar:

> Haz commit de todo

Resultado: Un mega-commit con 20 archivos diferentes.

✅ Mejor:

> Haz commit de los cambios en el servicio de autenticación

Luego:

> Ahora haz commit de los tests

2. Mensajes descriptivos

❌ Evitar:

> Haz commit con mensaje "fix stuff"

✅ Mejor:

> Haz commit con un mensaje descriptivo sobre los cambios que hicimos

Claude analiza el diff y genera un mensaje apropiado.

3. Revisar antes de commitear

✅ Buena práctica:

> Muéstrame qué se va a commitear antes de hacerlo

Claude muestra el git diff --staged y espera confirmación.

4. No commitear archivos sensibles

Claude tiene protecciones integradas:

> Haz commit de todos los cambios

Claude responde:

⚠️ Detecté archivos sensibles en el staging:
- .env (contiene variables de entorno)
- config/secrets.json (posibles credenciales)
 
Estos archivos NO serán incluidos en el commit.
¿Quieres agregar .env al .gitignore?

5. Mantener PRs enfocadas

❌ Evitar: Una PR con 50 archivos cambiados, mezclando features, fixes y refactors.

✅ Mejor: Una PR por feature/fix. Si durante el desarrollo haces cambios no relacionados, crea commits separados o incluso ramas separadas.

> Detecté que también arreglé un bug no relacionado.
  ¿Debería crear una rama separada para ese fix?

Claude puede sugerir separar trabajo no relacionado.


Casos de uso avanzados

Rebase interactivo

> Haz un rebase interactivo de los últimos 3 commits para limpiar el historial

Claude ejecuta:

git rebase -i HEAD~3

Y te ayuda a decidir qué hacer con cada commit (squash, reword, etc.).

Cherry-pick de commits

> Aplica el commit abc123 de la rama feature/other a esta rama

Claude:

git cherry-pick abc123

Stash de cambios

> Guarda temporalmente mis cambios sin hacer commit

Claude:

git stash save "WIP: user search implementation"

Para recuperarlos:

> Restaura los cambios que guardé
git stash pop

Revertir commits

> El commit abc123 introdujo un bug. Reviértelo.

Claude:

git revert abc123

Comparar ramas

> ¿Qué diferencias hay entre mi rama y main?

Claude ejecuta:

git diff main...HEAD

Y te da un resumen:

Tu rama tiene 5 commits adelante de main:
- feat: add user search
- test: add search tests
- refactor: extract search logic
- docs: update API docs
- fix: handle empty query
 
Cambios principales:
- 3 archivos nuevos
- 8 archivos modificados
- 142 inserciones, 23 eliminaciones

Actualizar rama con main

> Actualiza mi rama con los últimos cambios de main

Claude puede hacer merge o rebase según tu preferencia:

Opción merge:

git fetch origin
git merge origin/main

Opción rebase:

git fetch origin
git rebase origin/main

Trabajar con forks

> Configura este repo como fork de upstream

Claude:

git remote add upstream https://github.com/original/repo.git
git fetch upstream

Para sincronizar:

> Sincroniza mi fork con upstream
git fetch upstream
git checkout main
git merge upstream/main
git push origin main

Gestión de tags

> Crea un tag v1.0.0 para marcar el release

Claude:

git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0

Ver quién modificó una línea (blame)

> ¿Quién modificó la línea 42 de src/auth/login.ts?

Claude ejecuta:

git blame -L 42,42 src/auth/login.ts

Y te responde:

Línea 42 fue modificada por John Doe en el commit abc123:
"feat: add email validation"
 
Hace 3 días

Workflow con GitHub Actions

Claude puede ayudarte a debuggear y trabajar con CI/CD.

Ver status de checks

> ¿Pasaron los checks de CI en la última PR?

Claude ejecuta:

gh pr checks

Y te muestra:

✅ Tests (passed)
✅ Linter (passed)
❌ Build (failed)

Ver logs de un check fallido

> Muéstrame por qué falló el build

Claude obtiene los logs y te explica el error:

El build falló porque:
 
TypeError: Cannot read property 'name' of undefined
  at src/services/userService.ts:42
 
Parece que estás intentando acceder a 'user.name' pero 'user'
puede ser undefined. Necesitas agregar una validación.

Volver a ejecutar checks

> Vuelve a ejecutar los checks de la PR

Claude:

gh pr checks --watch

Ejemplo completo: Feature de inicio a fin

Aquí está un ejemplo real de conversación completa para implementar una feature:

Usuario: Quiero implementar paginación en el endpoint de usuarios
 
Claude: Perfecto. Voy a:
1. Crear una rama feature/user-pagination
2. Implementar la funcionalidad con TDD
3. Hacer commits incrementales
4. Crear una PR
 
¿Es correcto?
 
Usuario: Sí
 
Claude: [Ejecuta git checkout -b feature/user-pagination]
       ✅ Rama creada y checkout realizado
 
       Comenzando con TDD. Primero escribiré los tests...
       [Crea tests/routes/users.test.ts]
       ✓ test: add pagination tests for users endpoint
 
       Ahora implementando...
       [Implementa la funcionalidad]
       ✓ feat: add pagination to users endpoint
 
       Ejecutando tests...
       ✅ All tests passing
 
       ¿Quieres que haga code review antes de crear la PR?
 
Usuario: Sí
 
Claude: [Ejecuta análisis]
 
       Code review completado. Encontré:
       - P2: Falta validación de límite máximo de page size
       - P3: Podrías extraer la lógica de paginación a un helper
 
       ¿Quieres que aplique estos fixes?
 
Usuario: Solo el P2
 
Claude: [Implementa validación]
       ✓ feat: add max page size validation
 
       ✅ Tests passing
 
       ¿Creo la PR?
 
Usuario: Sí
 
Claude: [Pushea rama y crea PR]
 
       ✅ PR creada: https://github.com/user/repo/pull/44
 
       Título: "feat: add pagination to users endpoint"
 
       La PR incluye:
       - Tests completos
       - Implementación con validación
       - 3 commits atómicos

Troubleshooting común

"Changes not staged for commit"

Si Claude reporta este mensaje:

> Muéstrame qué archivos están modified pero no staged

Claude ejecuta git status y te da la lista.

Para stagear:

> Añade todos los archivos TypeScript modificados al staging

"Branch diverged"

Si tu rama local divergió del remoto:

> Mi rama local y la remota divergieron. ¿Qué hago?

Claude analiza y sugiere:

Tu rama local tiene 2 commits que no están en remoto.
El remoto tiene 1 commit que no tienes local.
 
Opciones:
1. Hacer pull con rebase: git pull --rebase
2. Hacer pull con merge: git pull
3. Forzar push (¡peligroso!): git push --force
 
Recomiendo opción 1 (rebase) para mantener historial limpio.
¿Procedo?

"Detached HEAD state"

> Estoy en "detached HEAD state". ¿Qué significa y cómo salgo?

Claude explica y ofrece soluciones.

Permisos de Git

Si configuraste permisos restrictivos para Bash, Claude podría necesitar que autorices comandos git:

{
  "permissions": {
    "allowedTools": [
      "Bash(git *)",
      "Bash(gh *)"
    ]
  }
}

Resumen

Con Claude Code puedes:

Crear ramas automáticamente con nombres apropiados ✅ Commits inteligentes con mensajes descriptivos en formato Conventional Commits ✅ Pull Requests completas con descripción, test plan y contexto ✅ Resolver conflictos de merge con ayuda inteligente ✅ Code review antes de commitear o crear PRs ✅ Gestión completa de workflow Git sin salir de la conversación ✅ Integración con GitHub via gh CLI ✅ Debuggear CI/CD cuando los checks fallan

El workflow Git con Claude Code es significativamente más rápido y menos propenso a errores que hacerlo manualmente, especialmente para:

  • Desarrolladores junior que aprenden Git
  • Equipos que quieren consistencia en commits y PRs
  • Proyectos con mucho volumen de cambios
  • Automatización de tareas repetitivas

Práctica recomendada

  1. Implementa una feature completa siguiendo el workflow de este documento
  2. Crea al menos 3 commits atómicos durante el desarrollo
  3. Pide code review antes de crear la PR
  4. Genera una PR automática y revisa que la descripción sea útil
  5. Simula cambios del reviewer y actualiza la PR

En el siguiente módulo (Módulo 9) aprenderás a optimizar el uso de contexto para sesiones más largas y eficientes.


Práctica del módulo 8: Flujos de trabajo avanzados

En esta práctica aplicarás todos los conceptos del módulo en un proyecto real: implementarás una feature completa usando TDD, harás debugging, code review, y trabajarás tanto con la terminal como con extensión de IDE.

Objetivos

  • Implementar una feature usando TDD con Claude Code
  • Debuggear un error real pegando el stack trace
  • Hacer onboarding a un proyecto open source
  • Usar code review en código propio
  • Integrar Claude Code con tu IDE preferido

Preparación: Setup del proyecto de práctica

Crearás una aplicación de gestión de tareas (TODO app) desde cero, aplicando las mejores prácticas del módulo.

Paso 1: Crear proyecto

mkdir ~/claude-practice-todo-app
cd ~/claude-practice-todo-app
npm init -y

Paso 2: Instalar dependencias

# Framework y herramientas
npm install express
npm install -D typescript @types/node @types/express ts-node nodemon
 
# Testing
npm install -D vitest @vitest/ui
 
# Linting
npm install -D eslint prettier

Paso 3: Configurar TypeScript

npx tsc --init

Modifica tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Paso 4: Configurar scripts

Edita package.json:

{
  "scripts": {
    "dev": "nodemon --exec ts-node src/index.ts",
    "test": "vitest",
    "test:ui": "vitest --ui",
    "build": "tsc",
    "lint": "eslint src --ext .ts",
    "format": "prettier --write \"src/**/*.ts\""
  }
}

Paso 5: Crear CLAUDE.md del proyecto

cat > CLAUDE.md << 'EOF'
# Proyecto: TODO App (Práctica Módulo 8)
 
Aplicación de gestión de tareas para practicar flujos de trabajo avanzados con Claude Code.
 
## Stack
- Backend: Node.js + Express + TypeScript
- Testing: Vitest
- Storage: Array en memoria (sin DB por simplicidad)
 
## Arquitectura
/src
  /routes       → Endpoints de API
  /services     → Lógica de negocio
  /models       → Tipos e interfaces
  /utils        → Funciones utilitarias
  /middleware   → Express middleware
/tests          → Tests (misma estructura que /src)
 
## Convenciones de Código
 
### TypeScript
- Modo estricto activado
- No usar `any`
- Interfaces con prefijo `I`: `ITask`, `IUser`
- Tipos con prefijo `T`: `TTaskStatus`, `TTaskPriority`
 
### Funciones
- Usar async/await (no callbacks ni .then())
- Nombres descriptivos en inglés
- Máximo 30 líneas por función
- Una responsabilidad por función
 
### API REST
- Códigos HTTP correctos:
  - 200: GET exitoso
  - 201: POST exitoso (creación)
  - 204: DELETE exitoso
  - 400: Error de validación
  - 404: Recurso no encontrado
  - 500: Error del servidor
- Formato de respuesta:
  ```json
  { "success": true, "data": ... }
  { "success": false, "error": "mensaje" }

Testing

  • Test Driven Development (TDD)
  • Escribir tests ANTES de implementar
  • Patrón AAA (Arrange, Act, Assert)
  • Cobertura mínima: 80%
  • Nombres descriptivos: should return 404 when task not found

Validación

  • Validar todos los inputs
  • Mensajes de error descriptivos
  • Sanitizar datos antes de procesar

Modelo de datos

Task

interface ITask {
  id: string;
  title: string;
  description: string;
  status: 'pending' | 'in_progress' | 'completed';
  priority: 'low' | 'medium' | 'high';
  createdAt: Date;
  updatedAt: Date;
}

Reglas de negocio

  • id: UUID generado automáticamente
  • title: 3-100 caracteres, obligatorio
  • description: 0-500 caracteres, opcional
  • status: Inicia en "pending"
  • priority: Default "medium"
  • No permitir títulos duplicados

Comandos importantes

  • npm run dev → Servidor en modo desarrollo (puerto 3000)
  • npm test → Ejecutar tests
  • npm test -- --watch → Tests en modo watch
  • npm run test:ui → UI de tests en navegador

Reglas importantes

  • SIEMPRE escribir tests primero (TDD)
  • NO commitear código sin tests
  • NO usar console.log (crear logger utility)
  • Ejecutar linter antes de commit EOF
 
### Paso 6: Estructura inicial
 
```bash
mkdir -p src/{routes,services,models,utils,middleware}
mkdir -p tests/{routes,services,utils}

Verificación: Ejecuta ls -R src y deberías ver todas las carpetas creadas.


Ejercicio 1: Test-Driven Development (TDD)

Implementarás la funcionalidad de crear tareas usando TDD estricto.

Paso 1.1: Escribir tests PRIMERO

Inicia Claude Code:

claude

Pide a Claude que escriba los tests primero:

Vamos a implementar la funcionalidad de crear tareas usando TDD.
 
IMPORTANTE: Escribe SOLO los tests primero, NO implementes nada aún.
 
Crea tests/services/taskService.test.ts que pruebe:
1. Crear tarea con datos válidos
2. Validar que title es obligatorio
3. Validar que title tiene mínimo 3 caracteres
4. Validar que title tiene máximo 100 caracteres
5. No permitir títulos duplicados
6. Generar ID automáticamente (UUID)
7. Establecer timestamps (createdAt, updatedAt)
8. Status inicial "pending"
9. Priority por defecto "medium"

Verificación: Claude debe crear el archivo de tests SIN implementar el servicio todavía.

Paso 1.2: Ejecutar tests (deben fallar ❌)

npm test

Verificación: Los tests deben fallar porque no existe taskService.ts todavía.

Paso 1.3: Implementar código mínimo

Vuelve a Claude:

Ahora implementa src/services/taskService.ts para que TODOS los tests pasen.
 
Implementa solo lo necesario para pasar los tests, nada más.

Verificación: Claude implementa taskService.ts.

Paso 1.4: Ejecutar tests (deben pasar ✅)

npm test

Verificación: Todos los tests pasan.

Paso 1.5: Refactorizar

El código funciona pero podría ser más legible.
Refactoriza taskService.ts para mejorar la estructura.
 
IMPORTANTE: Los tests deben seguir pasando después del refactor.

Ejecuta tests de nuevo:

npm test

Verificación: Tests siguen pasando después del refactor.


Ejercicio 2: Implementar API REST con TDD

Ahora implementarás los endpoints REST siguiendo TDD.

Paso 2.1: Tests para endpoint POST /api/tasks

Escribe tests para el endpoint POST /api/tasks
 
Usa supertest para hacer requests HTTP.
Instala la dependencia primero: npm install -D supertest @types/supertest
 
Tests a incluir:
1. POST válido retorna 201 y la tarea creada
2. POST sin title retorna 400
3. POST con title muy corto retorna 400
4. POST con title muy largo retorna 400
5. POST con título duplicado retorna 409
6. El response no debe incluir campos internos

Paso 2.2: Implementar endpoint

Ahora implementa:
- src/routes/tasks.ts con el endpoint POST /api/tasks
- src/index.ts con el servidor Express básico
 
Usa el taskService que ya implementamos.

Paso 2.3: Tests para endpoint GET /api/tasks

Escribe tests para GET /api/tasks que:
1. Retorna 200 y array vacío si no hay tareas
2. Retorna 200 y todas las tareas creadas
3. Las tareas se ordenan por createdAt (más recientes primero)

Paso 2.4: Implementar GET

Implementa GET /api/tasks en src/routes/tasks.ts

Paso 2.5: Tests para GET /api/tasks/:id

Escribe tests para GET /api/tasks/:id que:
1. Retorna 200 y la tarea si existe
2. Retorna 404 si la tarea no existe
3. Retorna 400 si el ID no es un UUID válido

Paso 2.6: Implementar GET by ID

Implementa GET /api/tasks/:id

Verificación completa:

npm test

Todos los tests deben pasar ✅


Ejercicio 3: Debugging acelerado

Vamos a introducir un bug intencionalmente y practicar debugging.

Paso 3.1: Introducir bug

Modifica manualmente src/services/taskService.ts:

// En la función createTask, cambia:
const task: ITask = {
  id: generateId(),
  title,
  description: description || '',
  status: 'pending',
  priority: priority || 'medium',
  createdAt: new Date(),
  updatedAt: new Date()
};
 
// Por esto (bug intencional):
const task: ITask = {
  id: generateId(),
  title,
  description: description || '',
  status: 'pending',
  priority: priority || 'medium',
  createdAt: new Date(),
  updatedAt: undefined as any  // ❌ Bug: undefined en lugar de Date
};

Paso 3.2: Ejecutar tests

npm test

Copia el error completo que aparece.

Paso 3.3: Debugging con Claude

En Claude Code:

Tengo este error al ejecutar tests:
 
[Pegar aquí el error completo con stack trace]
 
¿Cuál es el problema y cómo lo arreglo?

Verificación: Claude debe identificar el problema y sugerir la solución correcta.

Paso 3.4: Aplicar fix

Aplica la solución
npm test

Verificación: Tests vuelven a pasar ✅


Ejercicio 4: Code review

Harás code review de todo el código implementado.

Paso 4.1: Review con el plugin nativo (si está disponible)

Si tienes el plugin /code-review:

claude
/code-review

Paso 4.2: Review manual

Si no tienes el plugin, pide a Claude:

Haz un code review completo del proyecto.
 
Analiza:
1. Bugs potenciales
2. Problemas de seguridad
3. Mejoras de rendimiento
4. Violaciones de las convenciones en CLAUDE.md
5. Mejoras de legibilidad
 
Clasifica cada problema como P1 (crítico), P2 (alto), P3 (medio), P4 (bajo)

Paso 4.3: Arreglar problemas críticos

Si Claude encuentra problemas P1 o P2:

Arregla todos los problemas P1 y P2 que encontraste

Paso 4.4: Verificar que nada se rompió

npm test

Verificación: Tests siguen pasando después de los fixes.


Ejercicio 5: Integración con IDE

Configurarás Claude Code en tu IDE preferido.

Paso 5.1: VS Code (recomendado)

Instalar extensión:

  1. Abre VS Code
  2. Cmd+Shift+X (Mac) o Ctrl+Shift+X (Windows/Linux)
  3. Busca "Claude Code"
  4. Instala la extensión oficial de Anthropic

Configurar:

  1. Cmd+, para abrir Settings
  2. Buscar "Claude Code"
  3. Configurar:
    • Model: claude-sonnet-4-20250514
    • Permission Mode: default
    • Location: sidebar

Probar:

  1. Cmd+Esc para abrir Claude Code
  2. En el chat, escribe:
Explica el flujo completo de cómo funciona POST /api/tasks
  1. Selecciona código en src/routes/tasks.ts
  2. En el chat:
Optimiza esta función

Verificación: Deberías ver diffs visuales y poder aceptar/rechazar cambios.

Paso 5.2: Cursor (alternativo)

Si prefieres Cursor:

  1. Abre el proyecto en Cursor
  2. Cmd+L para abrir chat
  3. Prueba mencionar archivos con @:
Compara @src/routes/tasks.ts con @src/services/taskService.ts
y explica las responsabilidades de cada uno

Paso 5.3: JetBrains (si usas WebStorm/IntelliJ)

  1. File → Settings → Plugins
  2. Marketplace → "Claude Code [Beta]"
  3. Install y reiniciar
  4. Cmd+Esc para abrir

Ejercicio 6: Codebase onboarding (proyecto real)

Practicarás hacer onboarding a un proyecto open source real.

Paso 6.1: Clonar proyecto open source

Elige un proyecto (sugerencias):

# Opción A: Framework web minimalista
git clone https://github.com/fastify/fastify.git /tmp/fastify
cd /tmp/fastify
 
# Opción B: Utilidad popular
git clone https://github.com/sindresorhus/got.git /tmp/got
cd /tmp/got
 
# Opción C: CLI tool
git clone https://github.com/ohmyzsh/ohmyzsh.git /tmp/ohmyzsh
cd /tmp/ohmyzsh

Paso 6.2: Análisis inicial

claude

Haz estas preguntas:

1. Explica la estructura general de este proyecto
2. ¿Cómo funciona [feature principal]?
   Por ejemplo, para Fastify: "¿Cómo funciona el routing?"
3. ¿Cuál es el flujo de datos desde [punto A] hasta [punto B]?
4. ¿Qué patrones de diseño se usan?
5. ¿Dónde están los tests y cómo se ejecutan?
6. ¿Cuáles son las dependencias principales y para qué sirven?

Verificación: Al final deberías entender:

  • Qué hace el proyecto
  • Cómo está organizado
  • Cómo funciona internamente
  • Cómo contribuir

Paso 6.3: Crear tu propio CLAUDE.md

Basándote en lo aprendido, crea un CLAUDE.md para ese proyecto:

Basándote en lo que analizaste, genera un CLAUDE.md completo
para este proyecto que ayude a futuros contributors.
 
Incluye:
- Arquitectura
- Convenciones
- Comandos importantes
- Áreas sensibles del código

Ejercicio 7: Feature completa (integración de todo)

Implementarás una feature completa aplicando TODO lo aprendido.

Paso 7.1: Planificación

Feature a implementar: Filtrado de tareas por status y priority

Endpoint: GET /api/tasks?status=pending&priority=high

En Claude (terminal):

Voy a implementar filtrado de tareas.
 
Endpoint: GET /api/tasks?status=pending&priority=high
 
Antes de implementar, dame un plan detallado paso a paso.

Verificación: Claude debe dar un plan claro.

Paso 7.2: TDD - Escribir tests

Perfecto. Siguiendo TDD, escribe SOLO los tests primero.
 
Tests a incluir:
1. Filtrar por status
2. Filtrar por priority
3. Filtrar por ambos
4. Sin filtros retorna todas las tareas
5. Filtros inválidos retornan 400
6. Combinación que no coincide retorna array vacío

Paso 7.3: Ejecutar tests (fallan)

npm test

Verificación: Tests fallan ❌

Paso 7.4: Implementar

Ahora implementa la funcionalidad para pasar los tests

Paso 7.5: Tests pasan

npm test

Verificación: Tests pasan ✅

Paso 7.6: Code review

/code-review

O manual:

Haz code review de los cambios que acabamos de hacer

Paso 7.7: Refactor (si es necesario)

Si Claude sugiere mejoras:

Aplica las mejoras sugeridas manteniendo los tests pasando

Paso 7.8: Commit

Crea un commit con un mensaje descriptivo siguiendo
conventional commits (feat:, fix:, etc.)

Verificación: Commit creado correctamente.


Ejercicio 8: Workflow híbrido (Terminal + IDE)

Implementarás la feature de actualizar tareas combinando terminal y extensión IDE.

Paso 8.1: Planificación (Terminal)

claude
Feature: Actualizar tareas existentes
Endpoint: PATCH /api/tasks/:id
 
Campos actualizables: title, description, status, priority
 
Dame un plan

Paso 8.2: Tests (Terminal)

Escribe los tests primero

Paso 8.3: Implementación básica (Terminal)

Implementa la funcionalidad básica

Paso 8.4: Ajustes finos (IDE)

Abre VS Code con la extensión:

  1. Abre src/routes/tasks.ts
  2. Selecciona la función del endpoint PATCH
  3. En Claude Code panel (Cmd+Esc):
Esta función podría validar mejor los inputs.
Añade validación robusta manteniendo el código limpio.
  1. Revisa el diff visual
  2. Accept si está bien

Paso 8.5: Tests finales (Terminal)

npm test

Verificación: Todo pasa ✅


Ejercicio 9: Debugging de error complejo

Simularás un error real de producción.

Paso 9.1: Crear endpoint DELETE

claude
Implementa DELETE /api/tasks/:id con TDD
 
Tests:
1. DELETE existente retorna 204
2. DELETE inexistente retorna 404
3. DELETE con ID inválido retorna 400

Paso 9.2: Introducir race condition

Modifica manualmente el código para crear un bug sutil:

// En src/services/taskService.ts, en deleteTask:
 
export async function deleteTask(id: string): Promise<void> {
  const index = tasks.findIndex(t => t.id === id);
 
  if (index === -1) {
    throw new Error('Task not found');
  }
 
  // Bug: delay simulando operación async
  await new Promise(resolve => setTimeout(resolve, 100));
 
  // Si otra operación modificó el array durante el delay,
  // el index puede estar desactualizado
  tasks.splice(index, 1);
}

Paso 9.3: Crear test que falle

Crea un test que simule eliminar dos tareas simultáneamente
y demuestre que hay un race condition

Paso 9.4: Debugging

El test falla con este error:
[pegar error]
 
Explica qué está pasando y cómo arreglarlo

Paso 9.5: Aplicar fix

Implementa la solución

Verificación: Tests pasan ✅


Checklist de validación

Marca cada punto cuando lo hayas completado:

TDD

  • Escribí tests ANTES de implementar código
  • Verifiqué que los tests fallaban inicialmente
  • Implementé solo lo necesario para pasar tests
  • Refactoricé manteniendo tests verdes
  • Cobertura de tests >= 80%

Debugging

  • Practiqué pegar stack traces completos a Claude
  • Claude identificó correctamente el problema
  • Apliqué el fix y verifiqué que funcionaba

Code Review

  • Ejecuté code review del proyecto
  • Arreglé problemas P1 y P2
  • Entiendo la clasificación de severidad

Codebase Onboarding

  • Hice onboarding a un proyecto real
  • Entendí arquitectura y flujo de datos
  • Creé CLAUDE.md basado en el análisis

Integración IDE

  • Instalé extensión en mi IDE preferido
  • Probé chat integrado
  • Usé diffs visuales para revisar cambios
  • Probé mención de archivos con @

Workflow Completo

  • Implementé al menos una feature completa de inicio a fin
  • Combiné terminal + IDE según la tarea
  • Creé commits con mensajes descriptivos

Troubleshooting común

Tests no ejecutan

# Verificar que vitest está instalado
npm list vitest
 
# Reinstalar si es necesario
npm install -D vitest

Claude no respeta CLAUDE.md

  • Verifica que el archivo existe: cat CLAUDE.md
  • Ejecuta /memory para confirmar que se cargó
  • Las reglas deben ser específicas e imperativas

Extensión IDE no conecta

# Verificar instalación CLI
which claude
 
# Reinstalar si es necesario
npm install -g @anthropic-ai/claude-code
 
# Reiniciar IDE

Tests pasan localmente pero Claude dice que fallan

  • Claude puede estar usando caché viejo
  • Ejecuta manualmente: npm test
  • Si pasan, dile a Claude que use el resultado que ves

Retos adicionales (opcionales)

Reto A: Implementar autenticación

Añade autenticación JWT a la API:
- POST /api/auth/register
- POST /api/auth/login
- Middleware para proteger rutas
- Tests completos

Reto B: Persistencia con DB

Reemplaza el array en memoria por una base de datos real:
- Usa Prisma + SQLite
- Migra todos los tests
- Mantén la misma API

Reto C: API completa con paginación

Implementa paginación en GET /api/tasks:
- Query params: page, limit
- Response con metadata: total, pages, currentPage
- Tests exhaustivos

Reto D: CI/CD con code review automático

Crea GitHub Actions workflow que:
1. Ejecuta tests
2. Ejecuta linter
3. Ejecuta /code-review automático
4. Comenta en PR con resultados

Reto E: Documentación automática

Genera documentación OpenAPI/Swagger automáticamente:
- Tipos desde TypeScript
- Ejemplos desde tests
- Deploy a GitHub Pages

Proyecto final del módulo

Si completaste todos los ejercicios, ahora tienes:

API REST completa con CRUD de tareas ✅ Tests exhaustivos (cobertura >= 80%) ✅ Code review aplicado ✅ CLAUDE.md bien configurado ✅ Integración IDE funcionando ✅ Workflow híbrido dominado ✅ Debugging skills mejorados

Bonus: Puedes usar este proyecto como portfolio o base para proyectos reales.


Siguiente módulo: Gestión de contexto y tokens