Código con IA que escala: Claude Code Hooks y buenas prácticas para proyectos mantenibles
Patrón para evitar complejidad accidental en proyectos con IA: hooks automáticos, orquestación de agentes y arquitectura simple.
Colaboradores: Ivan Garcia
He visto a desarrolladores generar una feature completa con IA en 15 minutos. Funciona perfectamente. Tres meses después, nadie quiere tocar ese módulo porque se ha convertido en un nudo de condicionales anidados y dependencias cruzadas. Este es el dilema de la velocidad sin dirección: la IA optimiza para resolver el prompt actual, pero tu trabajo es optimizar para el proyecto a largo plazo.
En este post explico cómo configurar Claude Code Hooks, orquestar múltiples agentes y cambiar tu mentalidad de “prompter” a arquitecto de sistemas. No se trata de limitar la IA, sino de guiarla hacia soluciones que escalan.
¿Por qué la IA genera complejidad accidental?
La diferencia entre “fácil” y “simple” explica el problema. Un agente puede generar un endpoint que resuelve 5 casos de negocio con un bloque monolítico de 200 líneas. Es fácil: funciona inmediatamente. Pero no es simple: cada nuevo requisito obliga a tocar el mismo bloque.
// Fácil pero no simple: un endpoint que lo hace todo
app.post('/api/process', async (req, res) => {
if (req.body.type === 'payment') {
if (req.body.method === 'card') {
// 50 líneas de lógica de tarjeta
} else if (req.body.method === 'transfer') {
// 40 líneas de lógica de transferencia
if (req.body.currency === 'USD') {
// validaciones específicas USD
}
// más condicionales anidados...
}
} else if (req.body.type === 'refund') {
// otro bloque de 80 líneas
}
// 5 casos más...
});
La versión simple usa un patrón strategy:
// Simple: cada caso es un módulo independiente
const processors = {
payment: {
card: new CardProcessor(),
transfer: new TransferProcessor()
},
refund: new RefundProcessor()
};
app.post('/api/process', async (req, res) => {
const processor = processors[req.body.type]?.[req.body.method]
|| processors[req.body.type];
return processor.handle(req.body);
});
El segundo enfoque requiere más diseño inicial, pero cada caso nuevo es un módulo independiente. No es un defecto de la IA — es simplemente un rol diferente.
¿Cómo cambiar de “prompter” a arquitecto?
Spec-Driven Development: diseño antes de código
El error más habitual es abrir la terminal y pedir directamente “implementa un sistema de notificaciones”. Sin contexto ni restricciones, el agente improvisa una solución que funciona pero no encaja en tu arquitectura.
Crea un documento de diseño antes de escribir código:
# spec.md - Sistema de Notificaciones
## Estructura de archivos
- src/notifications/
- core/NotificationService.ts (interfaz principal)
- channels/EmailChannel.ts, PushChannel.ts
- templates/TemplateEngine.ts
- queue/NotificationQueue.ts
## Flujo de datos
Usuario → NotificationService → TemplateEngine → Channel → Queue → Delivery
## Patrones permitidos
- Strategy para canales (email, push, SMS)
- Factory para templates
- Observer para eventos de delivery
## Patrones prohibidos
- No Singleton para NotificationService
- No lógica de templates dentro de channels
- No dependencias directas entre channels
Con este spec, el prompt cambia de “implementa notificaciones” a “Siguiendo el spec en spec.md, implementa el módulo EmailChannel respetando la interfaz Channel y usando el TemplateEngine para renderizar contenido”.
CLAUDE.md: la constitución de tu proyecto
Claude Code lee automáticamente el archivo CLAUDE.md en la raíz del proyecto y adapta sus propuestas. Funciona como la “constitución” de tu codebase:
# CLAUDE.md
## Stack tecnológico
- Node.js + TypeScript
- Express + Zod para validaciones
- Prisma + PostgreSQL
- Jest para testing
## Convenciones de código
- Funciones máximo 20 líneas
- Máximo 3 niveles de anidamiento
- Imports absolutos desde `@/`
- Un concepto por archivo
## Reglas arquitectónicas
- Separación clara entre controladores, servicios y modelos
- No lógica de negocio en controladores
- Servicios sin efectos de lado en constructores
- Modelos sin dependencias externas
## Dependencies permitidas
- Para validación: solo Zod
- Para HTTP: solo Axios
- Para fechas: solo date-fns
Con este archivo, cuando pides “añade validación a este endpoint”, el agente usa Zod automáticamente. Cuando pides “implementa autenticación”, respeta la separación en capas.
¿Qué son los Claude Code Hooks y por qué los necesitas?
Los Hooks son controles de calidad automáticos que se ejecutan en momentos deterministas del ciclo. No dependen de que la IA “decida” hacer las cosas bien — son reglas inflexibles.
Se configuran en .claude/settings.json o usando el comando /hooks. Existen tres tipos:
PreToolUse Hooks: el filtro de entrada
Se ejecuta antes de que Claude use una herramienta (escribir archivo, ejecutar comando). Si el hook devuelve exit code ≠ 0, la acción se cancela.
{
"hooks": {
"preToolUse": {
"command": "./scripts/pre-action-check.sh",
"description": "Valida permisos antes de escribir archivos"
}
}
}
#!/bin/bash
# scripts/pre-action-check.sh
# Impedir modificaciones fuera de /src/features/
if [[ "$CLAUDE_TOOL_NAME" == "Edit" || "$CLAUDE_TOOL_NAME" == "Write" ]]; then
FILE_PATH="$CLAUDE_TOOL_ARGS_FILE_PATH"
if [[ ! "$FILE_PATH" == *"/src/features/"* ]]; then
echo "❌ Acceso denegado. Solo se permiten cambios en /src/features/"
exit 1
fi
fi
# Bloquear commits directos a main
if [[ "$CLAUDE_TOOL_NAME" == "Bash" && "$CLAUDE_TOOL_ARGS_COMMAND" == *"git commit"* ]]; then
BRANCH=$(git branch --show-current)
if [[ "$BRANCH" == "main" ]]; then
echo "❌ No se permiten commits directos a main. Crea una rama feature."
exit 1
fi
fi
PostToolUse Hooks: verificación automática
Se ejecuta después de cada modificación de archivo. Convierte cada edición en un mini-ciclo de CI local:
{
"hooks": {
"postToolUse": {
"command": "./scripts/post-edit-validation.sh",
"description": "Lint + tests automáticos después de cada cambio"
}
}
}
#!/bin/bash
# scripts/post-edit-validation.sh
if [[ "$CLAUDE_TOOL_NAME" == "Edit" || "$CLAUDE_TOOL_NAME" == "Write" ]]; then
FILE_PATH="$CLAUDE_TOOL_ARGS_FILE_PATH"
# Lint automático
if [[ "$FILE_PATH" == *.ts || "$FILE_PATH" == *.js ]]; then
echo "🔍 Ejecutando ESLint en $FILE_PATH..."
npx eslint "$FILE_PATH" --fix
if [ $? -ne 0 ]; then
echo "❌ Errores de lint detectados. Claude debe corregirlos."
exit 1
fi
fi
# Tests del módulo afectado
TEST_FILE="${FILE_PATH%.*}.test.ts"
if [ -f "$TEST_FILE" ]; then
echo "🧪 Ejecutando tests de $TEST_FILE..."
npx jest "$TEST_FILE"
if [ $? -ne 0 ]; then
echo "❌ Tests fallando. Claude debe arreglarlos antes de continuar."
exit 1
fi
fi
fi
Hooks basados en prompts: el revisor ligero
En lugar de un script de terminal, el hook es un prompt evaluado por un modelo rápido (Claude Haiku). Ideal para revisiones semánticas que un linter no puede hacer:
{
"hooks": {
"postToolUse": {
"prompt": "¿Esta función excede 30 líneas o tiene más de 3 niveles de anidamiento? Responde JSON: {\"pass\": true/false, \"reason\": \"...\"}",
"model": "claude-haiku",
"description": "Verificar complejidad ciclomática"
}
}
}
¿Cuándo necesitas orquestación de agentes?
Cuando un proyecto crece, un solo agente no basta. La orquestación permite dividir tareas entre agentes especializados manteniendo contexto acotado.
Patrón: agente planificador + ejecutores
| Componente | Responsabilidad | Contexto |
|---|---|---|
| Agente planificador | Recibe tarea completa, genera plan, descompone en subtareas | Todo el proyecto |
| Agentes ejecutores | Implementan cada subtarea específica | Solo archivos relevantes |
| Verificador final | Evalúa coherencia entre módulos | Plan + resultados |
// Ejemplo con Inngest para orquestación
export const implementFeature = inngest.createFunction(
{ id: "implement-feature" },
{ event: "feature.requested" },
async ({ event, step }) => {
// 1. Agente planificador
const plan = await step.run("generate-plan", async () => {
return plannerAgent.decompose(event.data.requirements);
});
// 2. Ejecutores en paralelo
const implementations = await step.run("parallel-implementation", async () => {
return Promise.all(
plan.tasks.map(task =>
executorAgent.implement(task, { scope: task.files })
)
);
});
// 3. Verificación final
const verification = await step.run("verify-integration", async () => {
return reviewerAgent.checkIntegration(plan, implementations);
});
if (!verification.passed) {
await step.sendEvent("implementation.retry", {
feedback: verification.issues
});
}
}
);
Patrón: code review entre agentes
Después de que un agente genera código, otro agente lo evalúa con un prompt de reviewer estricto:
const reviewerPrompt = `
Eres un code reviewer senior. Evalúa este código:
CRITERIOS:
- ¿Respeta los patrones del archivo CLAUDE.md?
- ¿Máximo 20 líneas por función?
- ¿Nombres descriptivos sin abreviaciones?
- ¿Tests suficientes para los casos edge?
- ¿Documentación clara para APIs públicas?
Responde JSON: {
"approved": boolean,
"issues": [{"line": number, "severity": "error|warning", "message": string}],
"suggestions": [string]
}
`;
const reviewResult = await reviewerAgent.evaluate(generatedCode, reviewerPrompt);
if (!reviewResult.approved) {
return originalAgent.fix(generatedCode, reviewResult.issues);
}
¿Cómo limitar el alcance de cada agente?
Nunca dar a un agente acceso a todo el proyecto. Acota a los archivos y módulos relevantes:
| ❌ Mal | ✅ Bien |
|---|---|
| ”Implementa autenticación en la aplicación" | "Implementa AuthService en src/auth/ usando las interfaces en src/types/auth.ts” |
| Acceso completo al directorio src/ | Solo src/auth/ y archivos de tipos relacionados |
| 50 archivos en contexto | 5-8 archivos específicos |
Esto reduce errores, evita cambios no deseados y hace más fácil auditar los resultados.
Errores comunes
El hook que bloquea todo
Configurar hooks tan estrictos que Claude no puede trabajar. Si el PreToolUse hook rechaza el 90% de las acciones, el problema es la configuración, no el agente.
Síntoma: Claude se queda en bucles intentando hacer la misma acción una y otra vez. Solución: Empezar con hooks permisivos y ir restringiendo gradualmente.
Agentes con contexto excesivo
Dar a un agente acceso a 100 archivos “por si acaso”. El resultado: cambios inesperados en módulos no relacionados.
Síntoma: Pedir “arregla este bug” y el agente modifica archivos de configuración o tests de otros módulos. Solución: Contexto mínimo viable. Añadir archivos solo cuando sea estrictamente necesario.
Hooks que duplican el trabajo
Configurar un hook PostToolUse que ejecuta tests completos después de cada cambio menor.
Síntoma: Cada edición tarda 2-3 minutos por los tests. Solución: Tests rápidos en hooks (lint, tests unitarios del archivo). Tests completos en CI.
Prompts vagos con agentes especializados
Usar agentes ejecutores como si fueran generalistas. “Implementa la feature de pagos” a un agente que debería solo crear interfaces.
Síntoma: Agentes que salen de su responsabilidad asignada. Solución: Prompts específicos que refuerzan el rol: “Como agente de interfaces, define solo los tipos TypeScript para el módulo de pagos, sin implementación”.
Checklist de implementación
- Archivo CLAUDE.md creado con stack, convenciones y reglas arquitectónicas
- Al menos un hook configurado (PreToolUse para permisos o PostToolUse para lint)
- Documentos de diseño (spec.md) antes de implementar features complejas
- Contexto de agentes limitado a archivos relevantes (máximo 10-15 archivos)
- Proceso de code review automático entre agentes (para proyectos grandes)
- Scripts de hooks testeados en un proyecto pequeño primero
- Métricas de calidad tracking (cobertura de tests, complejidad ciclomática)
Preguntas Frecuentes
¿Qué hooks debería configurar primero?
Empezar con un PostToolUse hook que ejecute lint automático después de cada cambio. Es el que más valor aporta con menor fricción. Una vez que funciona bien, añadir un PreToolUse hook para bloquear cambios fuera de directorios permitidos.
¿Los hooks ralentizan mucho el desarrollo?
Un hook de lint tarda 1-3 segundos. Un hook de tests unitarios del archivo modificado, 5-15 segundos. Es mucho más rápido que arreglar bugs en producción o limpiar código legacy más tarde. Para proyectos grandes, configurar hooks que ejecuten solo tests relacionados, no la suite completa.
¿Cuántos agentes debería usar para una feature?
Para features pequeñas (1-3 archivos), un solo agente basta. Para features medianas (4-10 archivos), usar planificador + ejecutor. Para features grandes (10+ archivos), añadir un agente verificador que revise la coherencia entre módulos.
¿Qué pasa si el hook de verificación rechaza todos los cambios?
Significa que las reglas son demasiado estrictas o que falta contexto en el CLAUDE.md. Revisar los mensajes de error del hook y ajustar progresivamente. Es mejor empezar con hooks permisivos y ir restringiendo que bloquear todo desde el principio.
¿Claude Code Hooks funciona con otros editores además de VS Code?
Los hooks son scripts de terminal que se ejecutan independientemente del editor. Funcionan con cualquier herramienta que use Claude Code: terminal, Cursor, editores con extensiones de Anthropic, o integraciones personalizadas vía API.