Flujos de Trabajo Avanzados
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:
- Escribes el test PRIMERO (antes del código)
- El test falla (porque no hay implementación)
- Escribes el código mínimo para pasar el test
- El test pasa ✅
- 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 stringPaso 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 sPaso 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ímite2. 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 pasen4. 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ónPatrones 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 testsPatró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 funcionalidadTips 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á mal4. Un ciclo a la vez
No hagas:
- Escribir 10 tests
- Implementar todo de golpe
Haz:
- Escribir 1-3 tests relacionados
- Implementar para pasarlos
- Refactorizar
- Repetir5. 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 → ListoEjemplo 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 failedResumen
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:
- 🔴 RED: Test que falla
- 🟢 GREEN: Código mínimo que pasa
- 🔧 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:
- Stack trace completo
- Mensaje de error
- Código que causó el error
- Contexto (qué estabas haciendo)
- 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 usuariosCaso 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 experimentanAná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" → FuncionaAplicar 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 correctamenteCaso 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 ayerAná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 passedCaso 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-domAná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.logInspeccionar dependencias
# Ver árbol de dependencias
npm ls <paquete>
# Ver versiones instaladas
npm list --depth=0
# Verificar incompatibilidades
npm ls | grep UNMETDebugging 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 -- --watchDebugging 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:3000Error: 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 dependenciesError: 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
-
Reproduce el error de forma consistente
Antes de pedir ayuda, verifica: - ¿El error ocurre siempre? - ¿En qué condiciones específicas? - ¿Pasos exactos para reproducirlo? -
Aísla el problema
- Comenta código hasta que funcione - Identifica la línea exacta que falla - Crea un ejemplo mínimo reproducible -
Lee el error completo
No solo la primera línea: - Stack trace completo - Archivos involucrados - Números de línea -
Usa logs estratégicos
console.log('Antes de fetch:', data); const result = await fetch(url); console.log('Después de fetch:', result); -
Verifica lo obvio primero
- ¿Servidor corriendo? - ¿Base de datos conectada? - ¿Variables de entorno configuradas? - ¿Dependencias instaladas?
⚠️ Evita
-
No hagas múltiples cambios a la vez
❌ Malo: Cambiar 5 cosas y ver qué pasa ✅ Bueno: Cambiar 1 cosa, verificar, continuar -
No ignores warnings
Los warnings a menudo son precursores de errores -
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 proyectoReducció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 .prettierrcFase 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 errorFase 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áticamentePaso 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: tareasFase 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.ymlPaso 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ñaResumen
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-reviewSalida 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 --commentEsto 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:
| Prioridad | Tipo | Ejemplos |
|---|---|---|
| P1 (Crítico) | Bugs que causan crashes, vulnerabilidades de seguridad | SQL injection, null pointer dereference, credenciales expuestas |
| P2 (Alto) | Bugs funcionales, problemas serios de rendimiento | Lógica incorrecta, memory leaks, N+1 queries |
| P3 (Medio) | Code smells, mejoras recomendadas | Duplicación, funciones largas, acoplamiento alto |
| P4 (Bajo) | Estilo, sugerencias menores | Nombres 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.mdModificar la línea:
Filter out any issues with a score less than 80Ejemplos 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 0Integració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 APIEjemplo 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 frontendCon este CLAUDE.md, Claude Code revisará automáticamente:
- ✅ Que no haya credenciales expuestas
- ✅ Que SQL use prepared statements
- ✅ Que no se use
anyen 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-appPaso 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 1Paso 3: Configurar secrets
En tu repositorio de GitHub:
- Ir a Settings → Secrets and variables → Actions
- Añadir
ANTHROPIC_API_KEYcon 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-reviewPatró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.mdContenido:
---
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
- Si recibiste $ARGUMENTS, analiza ese archivo/directorio
- Si no hay argumentos, ejecuta
git diff --stagedpara revisar cambios staged - Lee CLAUDE.md si existe
- Analiza código línea por línea
- Genera reporte con problemas encontrados
- Ordena por severidad (P1 → P4)
- 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 .
/reviewComparación: /code-review vs /review Custom
| Característica | /code-review (nativo) | /review (custom) |
|---|---|---|
| Instalación | Incluido en Claude Code | Crear manualmente |
| Arquitectura | 4 agentes paralelos | 1 agente |
| Velocidad | Rá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 recomendado | PRs completos | Casos específicos |
Cuándo usar cada uno:
-
Usa
/code-reviewpara:- Revisión completa de PRs
- Integración en CI/CD
- Máxima velocidad
- Scoring automático de confianza
-
Usa
/reviewcustom 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-reviewPaso 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
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🔵 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 push2. 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 presente3. Combinar review automático + humano
Claude Code Review → Arreglar problemas obvios → Human Review → MergearClaude 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 seriamente2. No revisar sin contexto
❌ Malo:
/code-review sin leer el PR description
✅ Bueno:
Leer qué intenta lograr el PR → /code-review → Evaluar sugerencias3. No depender 100% de automatización
Claude Code ≠ reemplaza code review humano
Claude Code = complementa code review humanoIntegració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 → ListoSonarQube
# 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.jsonGitHub 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 --commentResultado: 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/componentsPró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-reviewen 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:
- Terminal CLI → Análisis profundo, multi-archivo, operaciones complejas
- 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 AnthropicMétodo 2: Desde terminal
code --install-extension anthropic.claude-codeRequisitos:
- 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 glob4. 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ón | Mac | Windows/Linux |
|---|---|---|
| Abrir Claude Code | Cmd+Esc | Ctrl+Esc |
| Nueva conversación | Cmd+N | Ctrl+N |
| Nueva pestaña | Cmd+Shift+Esc | Ctrl+Shift+Esc |
| Insertar @-mención | Option+K | Alt+K |
| Enviar mensaje | Enter | Enter |
| Nueva línea en mensaje | Shift+Enter | Shift+Enter |
| Paleta de comandos | Cmd+Shift+P | Ctrl+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 CodeO 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 IDEActivación
1. Tools → Claude Code [Beta] → Settings
2. Configurar comando: claude (default)
3. Habilitar multi-línea, auto-updatesAtajos de Teclado
| Acción | Mac | Windows/Linux |
|---|---|---|
| Abrir Claude Code | Cmd+Esc | Ctrl+Esc |
| Insertar referencia | Cmd+Option+K | Ctrl+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 Plugin | Claude Agent Nativo |
|---|---|
| Plugin beta | Nativo en IDE |
| Requiere Claude Code CLI | Incluido en JetBrains AI subscription |
| Ventana separada | Integrado en IDE UI |
| Menos features nativas | Má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
| IDE | Instalación | Madurez | Visual Features | Recomendado Para |
|---|---|---|---|---|
| VS Code | Extension marketplace | ✅ Oficial | ⭐⭐⭐⭐⭐ | Mayoría de usuarios |
| Cursor | IDE standalone | ✅ Maduro | ⭐⭐⭐⭐⭐ | Flow state, exploración |
| Windsurf | IDE standalone | ✅ Maduro | ⭐⭐⭐⭐ | Usuarios de Cascade |
| JetBrains | Plugin marketplace | ⚠️ Beta | ⭐⭐⭐⭐ | Usuarios Java/Kotlin/Python |
| Neovim | Plugin manager | ⚠️ Community | ⭐⭐⭐ | Power users, terminal-first |
| Vim | Plugin manager | ⚠️ Community | ⭐⭐ | Puristas vim |
| Emacs | Package 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 masivasEscenario 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 onboardingConfiguració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 activamenteSettings 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 wishlistPaso 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 passPaso 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 WishlistButtonClaude genera:
src/components/WishlistButton.tsxsrc/hooks/useWishlist.tssrc/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 agregarClaude 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 foundSolución:
# Verificar instalación
which claude
# Si no está instalado:
npm install -g @anthropic-ai/claude-code
# Reiniciar VS CodeProblema 2: Cambios no se reflejan en IDE
Síntomas:
Claude dice que modificó archivo.ts
pero no veo cambios en el editorSolución:
// settings.json
{
"claudeCode.autosave": true // ← Activar
}Problema 3: Contexto muy grande
Síntomas:
⚠️ Context window 95% fullSolución:
# En VS Code Extension
Cmd+Shift+P → "Claude Code: Compact Conversation"
# O en terminal
/compactProblema 4: MCP servers ralentizan IDE
Síntomas:
VS Code Extension muy lentoSolució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
| Tarea | Terminal CLI | VS Code Ext | Cursor | JetBrains |
|---|---|---|---|---|
| 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/WebStormPró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
- Comandos Git básicos con Claude
- Crear ramas y features
- Commits inteligentes
- Pull Requests automatizadas
- Gestión de conflictos
- Workflow completo de feature
- Buenas prácticas
- 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/mainVer cambios pendientes
> Muéstrame qué cambios he hechoClaude 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 debuggingVer historial de commits
> Muéstrame los últimos commitsClaude 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-authenticationOpción 2: Especificar el nombre
> Crea una rama llamada feature/user-authClaude ejecuta:
git checkout -b feature/user-authVerificar en qué rama estás
> ¿En qué rama estoy?Claude verifica y te responde:
Estás en la rama feature/user-authCambiar entre ramas
> Cámbiate a maingit checkout main> Vuelve a la rama anteriorgit 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 cambiosClaude:
- Ejecuta
git statuspara ver qué hay modificado - Ejecuta
git diffpara analizar los cambios - Genera un mensaje descriptivo
- Añade los archivos relevantes con
git add - 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.tsClaude:
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:
| Prefijo | Uso |
|---|---|
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 ramaClaude ejecuta:
git log --oneline --graph --decorate -10Pull 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 ramaClaude:
- Verifica que estés en una rama de feature
- Ejecuta
git statusygit diff main...HEADpara ver todos los cambios - Analiza todos los commits desde que divergiste de main (no solo el último)
- Pushea la rama al remoto si es necesario
- Genera título y descripción completa
- 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 CodePR 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 @sarahClaude:
gh pr create --title "..." --body "..." \
--reviewer john,sarahVer PRs abiertas
> Muéstrame las PRs abiertas de este repoClaude ejecuta:
gh pr listY 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 #42Claude ejecuta:
gh pr view 42Gestión de conflictos
Claude puede ayudarte a resolver conflictos de merge de forma inteligente.
Detectar conflictos
> Intenta mergear main en mi ramaClaude ejecuta:
git merge mainSi 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 resolverlosClaude:
- Lee los archivos con conflictos
- Analiza las diferencias entre
<<<<<<< HEADy>>>>>>> main - Propone una resolución inteligente
- Aplica los cambios
- 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 statusY 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-searchPaso 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 completosClaude 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 servicePaso 4: Code review local
> Haz un code review de todos los cambios antes de crear la PRClaude analiza el código y sugiere mejoras.
Paso 5: Aplicar mejoras
> Aplica las mejoras P1 y P2Claude refactoriza y hace commit:
✓ refactor: improve search query performancePaso 6: Verificar tests
> Ejecuta todos los testsnpm test✅ All tests passing (42 tests, 0 failures)Paso 7: Crear PR
> Crea un pull requestClaude:
- Push de la rama:
git push -u origin feature/user-search- 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
)"- Te da el URL:
✅ Pull Request created: https://github.com/user/repo/pull/43Paso 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:
- Implementa los cambios
- Ejecuta tests
- Hace commit
- Pushea automáticamente
✓ feat: improve query parameter validation
✓ Pushed to feature/user-searchLa 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-branchO si prefieres merge commit:
gh pr merge 43 --mergeBuenas prácticas
1. Commits atómicos
❌ Evitar:
> Haz commit de todoResultado: Un mega-commit con 20 archivos diferentes.
✅ Mejor:
> Haz commit de los cambios en el servicio de autenticaciónLuego:
> Ahora haz commit de los tests2. Mensajes descriptivos
❌ Evitar:
> Haz commit con mensaje "fix stuff"✅ Mejor:
> Haz commit con un mensaje descriptivo sobre los cambios que hicimosClaude 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 hacerloClaude muestra el git diff --staged y espera confirmación.
4. No commitear archivos sensibles
Claude tiene protecciones integradas:
> Haz commit de todos los cambiosClaude 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 historialClaude ejecuta:
git rebase -i HEAD~3Y 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 ramaClaude:
git cherry-pick abc123Stash de cambios
> Guarda temporalmente mis cambios sin hacer commitClaude:
git stash save "WIP: user search implementation"Para recuperarlos:
> Restaura los cambios que guardégit stash popRevertir commits
> El commit abc123 introdujo un bug. Reviértelo.Claude:
git revert abc123Comparar ramas
> ¿Qué diferencias hay entre mi rama y main?Claude ejecuta:
git diff main...HEADY 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 eliminacionesActualizar rama con main
> Actualiza mi rama con los últimos cambios de mainClaude puede hacer merge o rebase según tu preferencia:
Opción merge:
git fetch origin
git merge origin/mainOpción rebase:
git fetch origin
git rebase origin/mainTrabajar con forks
> Configura este repo como fork de upstreamClaude:
git remote add upstream https://github.com/original/repo.git
git fetch upstreamPara sincronizar:
> Sincroniza mi fork con upstreamgit fetch upstream
git checkout main
git merge upstream/main
git push origin mainGestión de tags
> Crea un tag v1.0.0 para marcar el releaseClaude:
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0Ver 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.tsY te responde:
Línea 42 fue modificada por John Doe en el commit abc123:
"feat: add email validation"
Hace 3 díasWorkflow 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 checksY te muestra:
✅ Tests (passed)
✅ Linter (passed)
❌ Build (failed)Ver logs de un check fallido
> Muéstrame por qué falló el buildClaude 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 PRClaude:
gh pr checks --watchEjemplo 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ómicosTroubleshooting común
"Changes not staged for commit"
Si Claude reporta este mensaje:
> Muéstrame qué archivos están modified pero no stagedClaude 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
- Implementa una feature completa siguiendo el workflow de este documento
- Crea al menos 3 commits atómicos durante el desarrollo
- Pide code review antes de crear la PR
- Genera una PR automática y revisa que la descripción sea útil
- 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 -yPaso 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 prettierPaso 3: Configurar TypeScript
npx tsc --initModifica 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áticamentetitle: 3-100 caracteres, obligatoriodescription: 0-500 caracteres, opcionalstatus: 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 testsnpm test -- --watch→ Tests en modo watchnpm 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:
claudePide 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 testVerificació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 testVerificació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 testVerificació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 internosPaso 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.tsPaso 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álidoPaso 2.6: Implementar GET by ID
Implementa GET /api/tasks/:idVerificación completa:
npm testTodos 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 testCopia 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ónnpm testVerificació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-reviewPaso 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 encontrastePaso 4.4: Verificar que nada se rompió
npm testVerificació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:
- Abre VS Code
Cmd+Shift+X(Mac) oCtrl+Shift+X(Windows/Linux)- Busca "Claude Code"
- Instala la extensión oficial de Anthropic
Configurar:
Cmd+,para abrir Settings- Buscar "Claude Code"
- Configurar:
- Model:
claude-sonnet-4-20250514 - Permission Mode:
default - Location:
sidebar
- Model:
Probar:
Cmd+Escpara abrir Claude Code- En el chat, escribe:
Explica el flujo completo de cómo funciona POST /api/tasks- Selecciona código en
src/routes/tasks.ts - En el chat:
Optimiza esta funciónVerificación: Deberías ver diffs visuales y poder aceptar/rechazar cambios.
Paso 5.2: Cursor (alternativo)
Si prefieres Cursor:
- Abre el proyecto en Cursor
Cmd+Lpara abrir chat- Prueba mencionar archivos con
@:
Compara @src/routes/tasks.ts con @src/services/taskService.ts
y explica las responsabilidades de cada unoPaso 5.3: JetBrains (si usas WebStorm/IntelliJ)
- File → Settings → Plugins
- Marketplace → "Claude Code [Beta]"
- Install y reiniciar
Cmd+Escpara 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/ohmyzshPaso 6.2: Análisis inicial
claudeHaz estas preguntas:
1. Explica la estructura general de este proyecto2. ¿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ódigoEjercicio 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íoPaso 7.3: Ejecutar tests (fallan)
npm testVerificación: Tests fallan ❌
Paso 7.4: Implementar
Ahora implementa la funcionalidad para pasar los testsPaso 7.5: Tests pasan
npm testVerificación: Tests pasan ✅
Paso 7.6: Code review
/code-reviewO manual:
Haz code review de los cambios que acabamos de hacerPaso 7.7: Refactor (si es necesario)
Si Claude sugiere mejoras:
Aplica las mejoras sugeridas manteniendo los tests pasandoPaso 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)
claudeFeature: Actualizar tareas existentes
Endpoint: PATCH /api/tasks/:id
Campos actualizables: title, description, status, priority
Dame un planPaso 8.2: Tests (Terminal)
Escribe los tests primeroPaso 8.3: Implementación básica (Terminal)
Implementa la funcionalidad básicaPaso 8.4: Ajustes finos (IDE)
Abre VS Code con la extensión:
- Abre
src/routes/tasks.ts - Selecciona la función del endpoint PATCH
- 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.- Revisa el diff visual
- Accept si está bien
Paso 8.5: Tests finales (Terminal)
npm testVerificación: Todo pasa ✅
Ejercicio 9: Debugging de error complejo
Simularás un error real de producción.
Paso 9.1: Crear endpoint DELETE
claudeImplementa DELETE /api/tasks/:id con TDD
Tests:
1. DELETE existente retorna 204
2. DELETE inexistente retorna 404
3. DELETE con ID inválido retorna 400Paso 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 conditionPaso 9.4: Debugging
El test falla con este error:
[pegar error]
Explica qué está pasando y cómo arreglarloPaso 9.5: Aplicar fix
Implementa la soluciónVerificació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 vitestClaude no respeta CLAUDE.md
- Verifica que el archivo existe:
cat CLAUDE.md - Ejecuta
/memorypara 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 IDETests 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 completosReto 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 APIReto C: API completa con paginación
Implementa paginación en GET /api/tasks:
- Query params: page, limit
- Response con metadata: total, pages, currentPage
- Tests exhaustivosReto 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 resultadosReto E: Documentación automática
Genera documentación OpenAPI/Swagger automáticamente:
- Tipos desde TypeScript
- Ejemplos desde tests
- Deploy a GitHub PagesProyecto 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