Módulo 2: Mental Models para AI Code
Managing an Intern (MIT)
Managing an Intern (MIT)
Descripción de la cápsula
Imagina que te asignan un intern brillante. Sabe programar, trabaja rápido, conoce muchos frameworks. Pero nunca ha trabajado en tu empresa, no entiende tu negocio, y a veces genera soluciones que "se ven bien" pero no cumplen los requisitos reales. ¿Qué haces? No le revisas cada punto y coma. Tampoco lo dejas solo. Lo supervisas: revisas las decisiones importantes, verificas la lógica de negocio, y confías en que el boilerplate está bien.
Eso es exactamente lo que deberías hacer con Claude Code. El modelo "Managing an Intern" (MIT) — popularizado por investigadores del MIT que estudian cómo developers trabajan con AI — te da un framework para decidir qué supervisar, qué delegar, y cómo revisar el output. Es el mental model más intuitivo de los tres porque mapea directamente a una experiencia que muchos developers han vivido: supervisar a alguien junior.
En esta cápsula vas a entender el modelo en profundidad, mapearlo a tu trabajo con Claude Code, y practicar aplicándolo a escenarios reales.
El Modelo: Claude Code como Intern Brillante
La metáfora
Claude Code es como un intern que:
├── ✅ Sabe muchos lenguajes y frameworks
├── ✅ Trabaja increíblemente rápido
├── ✅ Genera código que compila y "funciona"
├── ✅ Sigue patrones estándar correctamente
├── ✅ Es incansable y no se queja
│
├── ❌ No entiende tu negocio
├── ❌ No sabe por qué se tomaron ciertas decisiones
├── ❌ A veces inventa cosas que suenan plausibles
├── ❌ No distingue lo crítico de lo trivial
└── ❌ No pregunta cuando debería preguntar
La metáfora funciona porque la solución es la misma: supervisa lo que importa, delega lo routine. Un buen manager no revisa cada línea del PR de un intern. Revisa las decisiones de diseño, la lógica de negocio, y la seguridad. Confía en que la sintaxis y el formateo están bien.
Lo que un buen manager hace
Un buen manager de un intern no es ni un micro-manager ni un manager ausente. Está en el punto medio:
Micro-manager (ineficiente):
- Revisa cada nombre de variable
- Cuestiona cada import
- Reescribe el código del intern "a su manera"
- El intern no crece, el manager no escala
Manager ausente (peligroso):
- Acepta todo sin revisar
- Asume que "si compila, está bien"
- No da feedback ni contexto
- Los bugs llegan a producción
Buen manager (efectivo):
- Revisa las decisiones de diseño
- Verifica la lógica de negocio
- Hace preguntas: "¿Por qué elegiste este approach?"
- Confía en el boilerplate pero verifica lo crítico
- Da contexto que el intern no tiene
Mapeando el Modelo a Claude Code
Qué supervisar (decisiones de alto impacto)
Estas son las áreas donde Claude Code necesita tu supervisión activa — igual que un intern necesita supervisión en decisiones que no puede tomar solo:
1. Lógica de negocio
Claude Code no conoce tu negocio. Puede generar un cálculo de descuentos perfecto técnicamente, pero con los porcentajes equivocados porque no sabe que los clientes premium tienen 25%, no 20%.
# Claude Code generó esto
def calculate_shipping(weight: float, destination: str) -> float:
if destination == "domestic":
return weight * 2.5
elif destination == "international":
return weight * 8.0
else:
return weight * 5.0
# ¿Los precios son correctos? Solo TÚ lo sabes.
# ¿Falta algún tipo de destino? Solo TÚ lo sabes.
# ¿Hay descuento por peso alto? Solo TÚ lo sabes.
Pregunta del buen manager: "¿Los valores de negocio son correctos?"
2. Decisiones de arquitectura
Claude Code toma decisiones de arquitectura implícitas que pueden no alinearse con tu sistema:
# Claude Code decidió usar un dict in-memory como "base de datos"
users_db: dict = {}
# ¿Es apropiado para tu caso?
# - Prototipo rápido → Sí, está bien
# - Producción → No, necesitas persistencia real
# - Múltiples workers → No, cada worker tiene su propio dict
Pregunta del buen manager: "¿Esta decisión de arquitectura escala para nuestro caso?"
3. Seguridad
Claude Code puede generar código que "funciona" pero tiene vulnerabilidades que no son obvias a primera vista:
# Claude Code generó un endpoint de login
@app.post("/login")
async def login(username: str, password: str):
user = db.query(User).filter(User.username == username).first()
if user and user.password == password: # ⚠️ Comparación en texto plano
return {"token": create_token(user.id)}
raise HTTPException(status_code=401)
Pregunta del buen manager: "¿Hay algo aquí que un atacante podría explotar?"
En este caso: las passwords se comparan en texto plano (deberían estar hasheadas), y los parámetros de login llegan como query params (deberían ser body).
4. Edge cases que importan
Claude Code maneja el "happy path" consistentemente bien. Los edge cases que causan problemas en producción son otra historia:
# Claude Code generó una función de división de pagos
def split_payment(total: float, num_people: int) -> list[float]:
per_person = round(total / num_people, 2)
return [per_person] * num_people
# ¿Qué pasa con num_people = 0? → ZeroDivisionError
# ¿Qué pasa con total = 100.00 y num_people = 3?
# per_person = 33.33
# 33.33 * 3 = 99.99 → ¡Falta 1 centavo!
# ¿Quién paga el centavo extra?
Pregunta del buen manager: "¿Qué pasa con inputs inusuales o edge cases?"
Qué delegar (trabajo routine)
Estas son las áreas donde Claude Code es consistentemente bueno y tu supervisión no agrega valor significativo:
Delegar con confianza:
├── Boilerplate y setup de proyecto
├── Imports estándar
├── Configuración básica de frameworks
├── Modelos Pydantic para datos simples
├── Formateo y estilo de código
├── Type hints
├── Docstrings descriptivas
├── CRUD endpoints que siguen patrones estándar
├── Estructura de archivos y directorios
└── Configuración de testing framework
Esto no significa "ignorar". Significa que una revisión visual rápida de 30 segundos es suficiente. Si algo te llama la atención, profundiza. Si no, sigue adelante.
El Toolkit del Manager
Las 5 preguntas del buen manager
Cuando Claude Code genera código, hazte estas 5 preguntas en orden. Cada una toma segundos y te ahorra horas de debugging:
1. "¿Hace lo que le pedí?"
→ ¿El código cumple con el requisito que describí?
→ ¿Hay algo que pedí que falta?
→ ¿Hay algo que no pedí pero agregó?
2. "¿Los valores de negocio son correctos?"
→ ¿Los números, porcentajes, constantes son los reales?
→ ¿Las condiciones reflejan las reglas de negocio?
3. "¿Hay algo que un atacante podría explotar?"
→ ¿Inputs sin sanitizar?
→ ¿Secrets expuestos?
→ ¿Queries SQL con concatenación?
4. "¿Qué pasa cuando las cosas salen mal?"
→ ¿Maneja errores?
→ ¿Qué pasa con null, vacío, negativo?
→ ¿Los mensajes de error son seguros?
5. "¿Esta decisión de diseño es apropiada para nuestro caso?"
→ ¿La arquitectura escala?
→ ¿Las dependencias son las correctas?
→ ¿Hay alternativas que deberíamos considerar?
Dónde mirar: la revisión eficiente
Un buen manager no lee todo el PR del intern. Sabe dónde mirar:
SIEMPRE mira:
├── Funciones que manejan dinero, permisos, o datos sensibles
├── Queries a base de datos (especialmente WHERE y JOINs)
├── Manejo de autenticación y autorización
├── Validaciones de input del usuario
└── Cualquier cosa que toque state persistente
REVISA RÁPIDO:
├── Modelos de datos (¿los campos tienen sentido?)
├── Rutas y endpoints (¿los paths son coherentes?)
├── Error handling (¿hay try/except donde debería?)
└── Constantes y configuración (¿los valores son razonables?)
CONFÍA (revisión visual):
├── Imports
├── Type hints
├── Formateo
├── Boilerplate de framework
└── Estructura de archivos
Cuándo intervenir
Un buen manager interviene cuando detecta señales de alerta, no por rutina:
Señales de que debes intervenir:
├── El código hace algo que no pediste
├── Hay un import que no reconoces
├── La solución es más compleja de lo esperado
├── Hay lógica de negocio que no puedes verificar
├── Falta error handling en operaciones riesgosas
├── Hay valores hardcoded que deberían ser configurables
└── El approach es inusual para el problema
Ejemplo Completo: Aplicando Managing an Intern
Escenario
Le pides a Claude Code: "Crea un endpoint para registrar usuarios con email y password."
Claude Code genera:
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel, EmailStr, Field
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional
import uuid
import os
app = FastAPI(title="User Registration API")
SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key-change-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
users_db: dict = {}
class UserCreate(BaseModel):
email: EmailStr
password: str = Field(..., min_length=8, max_length=128)
full_name: str = Field(..., min_length=1, max_length=200)
class UserResponse(BaseModel):
id: str
email: str
full_name: str
created_at: datetime
class Token(BaseModel):
access_token: str
token_type: str
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
@app.post("/register", response_model=UserResponse, status_code=201)
async def register_user(user: UserCreate):
if any(u["email"] == user.email for u in users_db.values()):
raise HTTPException(status_code=409, detail="Email already registered")
user_id = str(uuid.uuid4())
new_user = {
"id": user_id,
"email": user.email,
"full_name": user.full_name,
"password_hash": hash_password(user.password),
"created_at": datetime.utcnow(),
}
users_db[user_id] = new_user
return UserResponse(**new_user)
@app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = next(
(u for u in users_db.values() if u["email"] == form_data.username),
None,
)
if not user or not verify_password(form_data.password, user["password_hash"]):
raise HTTPException(status_code=401, detail="Invalid credentials")
access_token = create_access_token(
data={"sub": user["email"]},
expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
)
return Token(access_token=access_token, token_type="bearer")
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
email: str = payload.get("sub")
if email is None:
raise HTTPException(status_code=401, detail="Invalid token")
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
user = next((u for u in users_db.values() if u["email"] == email), None)
if user is None:
raise HTTPException(status_code=401, detail="User not found")
return user
@app.get("/me", response_model=UserResponse)
async def read_users_me(current_user: dict = Depends(get_current_user)):
return UserResponse(**current_user)
Revisión como buen manager
Pregunta 1: "¿Hace lo que le pedí?"
Pedí: endpoint para registrar usuarios con email y password.
Generó: registro + login + token + endpoint /me
→ Generó MÁS de lo que pedí. ¿Es un problema?
- Si quiero un sistema completo de auth → Útil, ahorra tiempo
- Si quiero solo registro → Código extra que mantener
→ Decisión: Lo mantengo, pero noto que agregó scope
Pregunta 2: "¿Los valores de negocio son correctos?"
- min_length=8 para password → ¿Es el mínimo de mi empresa?
- max_length=128 para password → Razonable
- ACCESS_TOKEN_EXPIRE_MINUTES = 30 → ¿Es apropiado para mi app?
- Status code 409 para email duplicado → Correcto (Conflict)
- Status code 201 para registro → Correcto (Created)
→ Verificar contra los requisitos reales del negocio
Pregunta 3: "¿Hay algo que un atacante podría explotar?"
⚠️ SECRET_KEY tiene fallback "dev-secret-key-change-in-production"
→ En producción, si olvidan setear la variable, el secret es público
→ ACCIÓN: Quitar fallback, que falle si no está configurado
✅ Password se hashea con bcrypt → Correcto
✅ Login usa OAuth2PasswordRequestForm (body, no query params) → Correcto
✅ Token tiene expiración → Correcto
✅ Token validation maneja JWTError → Correcto
⚠️ No hay rate limiting en /register ni /token
→ Un atacante puede hacer brute force o spam de registros
→ ACCIÓN: Agregar rate limiting (puede ser en otro momento)
⚠️ El email no se normaliza a lowercase
→ "User@Email.com" y "user@email.com" serían usuarios diferentes
→ ACCIÓN: Normalizar email
Pregunta 4: "¿Qué pasa cuando las cosas salen mal?"
✅ Email duplicado → 409 con mensaje claro
✅ Credenciales inválidas → 401 sin revelar si el email existe
✅ Token inválido → 401
⚠️ ¿Qué pasa si users_db es corrupto? → No aplica con dict in-memory
⚠️ No hay logging → En producción necesitarás logs de auth events
Pregunta 5: "¿Esta decisión de diseño es apropiada?"
⚠️ users_db como dict in-memory
→ Prototipo: OK
→ Producción: Necesita base de datos real
→ ACCIÓN: Si es prototipo, acepto. Si no, cambio.
✅ bcrypt para hashing → Estándar de la industria
✅ jose para JWT → Biblioteca madura y confiable
✅ UUID4 para IDs → Correcto
✅ Pydantic para validación → Correcto
Resultado final:
Tiempo de revisión: ~10 minutos
Issues encontrados: 3 accionables (secret fallback, email normalization,
rate limiting)
Issues menores: 2 (logging, storage in-memory)
Decisión: EDITAR — el 90% del código está bien
Anti-Patrones: Los Extremos
Anti-patrón 1: El micro-manager
El micro-manager revisa cada línea del output de Claude Code como si fuera código de un desconocido en producción crítica:
Micro-manager:
"¿Por qué usó uuid.uuid4() y no uuid.uuid1()?"
"¿Por qué puso el import de os al final?"
"Prefiero CryptContext con schemes=['argon2']"
"Voy a reescribir todos los nombres de variable"
Resultado:
- 45 minutos revisando código de 80 líneas
- Terminó reescribiendo el 70% del código
- Habría sido más rápido escribirlo desde cero
- No encontró el issue real (el SECRET_KEY fallback)
porque se perdió en detalles insignificantes
Por qué es un anti-patrón: El micro-manager gasta todo su tiempo en decisiones de bajo impacto y pierde de vista las decisiones de alto impacto. Es como un manager que corrige la ortografía del email de un intern pero no revisa que el intern envió los datos financieros al cliente equivocado.
Anti-patrón 2: El manager ausente
El manager ausente acepta todo sin revisión — "si Claude Code lo generó, estará bien":
Manager ausente:
"Se ve bien, compila, el endpoint responde."
→ Acepta y deploy
Resultado:
- 0 minutos de revisión
- El SECRET_KEY fallback llega a producción
- No hay rate limiting
- Un bot registra 100,000 cuentas falsas
- 3 semanas después: incidente de seguridad
Por qué es un anti-patrón: El manager ausente no aporta ningún valor al proceso. Claude Code no necesita un manager que solo haga click en "aceptar". Necesita un manager que aporte el contexto que no tiene: reglas de negocio, requisitos de seguridad, limitaciones del sistema.
Anti-patrón 3: El manager inconsistente
El manager inconsistente varía su nivel de supervisión sin criterio — a veces micro-manage, a veces ausente:
Manager inconsistente:
Lunes (con prisa): "Se ve bien, acepto" → auth sin revisión
Martes (tranquilo): "Voy a revisar cada import" → README sobre-revisado
Miércoles (paranoico): "No confío en AI" → reescribe todo manualmente
Resultado:
- El código crítico (lunes) pasó sin revisión
- El código trivial (martes) consumió tiempo innecesario
- El código útil (miércoles) se desperdició
Por qué es un anti-patrón: La inconsistencia significa que tu nivel de supervisión depende de tu estado de ánimo, no del riesgo del código. Esto es exactamente lo que los mental models previenen.
Managing an Intern: Niveles de Supervisión
El modelo MIT define 3 niveles de supervisión que mapean directamente a tipos de código:
Nivel 1: Supervisión directa
Para código donde un error es inaceptable.
Cuándo:
├── Autenticación y autorización
├── Procesamiento de pagos
├── Manejo de datos sensibles (PII, salud, financieros)
├── Criptografía y manejo de secrets
└── Queries que modifican datos (UPDATE, DELETE)
Cómo:
├── Lee cada línea
├── Verifica contra documentación oficial
├── Escribe o revisa tests para cada caso
├── Haz preguntas específicas: "¿Por qué este approach?"
└── No aceptes hasta estar seguro
Nivel 2: Revisión enfocada
Para código donde un error es problemático pero no catastrófico.
Cuándo:
├── Lógica de negocio no financiera
├── API endpoints con validación
├── Queries de lectura complejas
├── Error handling
└── Integraciones con servicios externos
Cómo:
├── Revisa la lógica principal (no cada línea)
├── Verifica edge cases obvios
├── Confirma que el error handling existe
├── Revisa que los tests cubran los escenarios principales
└── Acepta si la estructura y lógica son correctas
Nivel 3: Confianza verificada
Para código routine donde AI es consistentemente buena.
Cuándo:
├── Boilerplate y setup
├── Modelos de datos simples
├── CRUD estándar
├── Configuración de frameworks
└── Documentación y type hints
Cómo:
├── Revisión visual de 30-60 segundos
├── ¿Se ve razonable?
├── ¿Algo salta a la vista?
├── Si todo parece bien → acepta
└── Si algo no "huele" bien → promueve a Nivel 2
Conexión con Proyecto
Cómo usarás Managing an Intern en el Módulo 8
En el proyecto integrador vas a recibir un codebase FastAPI completo con problemas plantados. Tu trabajo es hacer code review profesional. Usando el modelo MIT:
- No revisarás cada línea de cada archivo. Identificarás qué archivos necesitan supervisión directa (auth, pagos), cuáles revisión enfocada (lógica de negocio, endpoints), y cuáles confianza verificada (modelos, config).
- Priorizarás tu tiempo. En vez de gastar 20 minutos en cada archivo, gastarás 30 minutos en auth, 10 minutos en lógica de negocio, y 2 minutos en config. Tu tiempo total es menor y tus resultados mejores.
- Documentarás tu proceso. Como un manager documenta feedback para un intern, tú documentarás qué encontraste, por qué importa, y cómo corregirlo.
Troubleshooting
Problema 1: "No sé si algo es Nivel 1, 2, o 3"
Causa: Falta de experiencia clasificando código por riesgo. Solución: Hazte la pregunta: "Si este código tiene un bug y llega a producción, ¿a quién llaman a las 3 AM?" Si la respuesta es "a nadie" → Nivel 3. Si es "al equipo de producto" → Nivel 2. Si es "al equipo de seguridad y al CEO" → Nivel 1.
Problema 2: "Termino micro-manageando sin darme cuenta"
Causa: Inercia — es más fácil revisar todo que decidir qué revisar. Solución: Antes de revisar, dedica 1 minuto a clasificar el código en niveles. Escribe: "Nivel 1: [archivos]. Nivel 2: [archivos]. Nivel 3: [archivos]." Después revisa en ese orden. Si te descubres revisando imports de un archivo Nivel 3, para y avanza.
Problema 3: "El output tiene más de lo que pedí — ¿reviso todo?"
Causa: Claude Code a veces genera más scope del solicitado. Solución: Primero decide si quieres el scope extra. Si no, elimínalo — no lo revises. Si sí, clasifícalo en niveles y revisa con el nivel apropiado. No revises con Nivel 1 algo que no pediste y que es Nivel 3.
Problema 4: "No conozco bien el dominio para evaluar lógica de negocio"
Causa: Estás trabajando en un área que no dominas. Solución: Cuando no conoces el dominio, todo sube un nivel de supervisión. Lo que sería Nivel 2 se vuelve Nivel 1. Lo que sería Nivel 3 se vuelve Nivel 2. Si no puedes verificar la lógica de negocio, busca a alguien que pueda — un product manager, un domain expert, la documentación del negocio.
Ejercicios
Ejercicio 1: Clasificar en niveles (Fácil)
Clasifica cada bloque de código en Nivel 1 (supervisión directa), Nivel 2 (revisión enfocada), o Nivel 3 (confianza verificada):
- Un import de
loggingysys - Una función que calcula el impuesto sobre la venta basado en el estado
- Un middleware que verifica tokens JWT en cada request
- Un modelo Pydantic con 5 campos para un endpoint de productos
- Una función que envía un email de bienvenida al registrarse
Ver solución
- Import de logging y sys → Nivel 3. Imports estándar de Python. Revisión visual.
- Cálculo de impuesto → Nivel 1. Lógica de negocio financiera. Debes verificar que los porcentajes sean correctos para cada estado, que maneje estados sin impuesto, y que los cálculos sean precisos.
- Middleware JWT → Nivel 1. Seguridad crítica. Debes verificar que valide correctamente, que maneje tokens expirados, que no exponga información en errores.
- Modelo Pydantic de productos → Nivel 3. Boilerplate. Revisión visual rápida: ¿los campos tienen sentido? ¿Los tipos son correctos?
- Email de bienvenida → Nivel 2. No es security-critical, pero necesitas verificar que no envíe a la dirección equivocada, que el contenido sea correcto, y que maneje errores de envío.
Ejercicio 2: Ser el manager (Medio)
Claude Code generó esta función. Aplica las 5 preguntas del buen manager:
from decimal import Decimal
from typing import Optional
from datetime import datetime
def apply_coupon(
subtotal: Decimal,
coupon_code: str,
user_tier: str,
order_date: Optional[datetime] = None,
) -> dict:
coupons = {
"WELCOME10": {"discount": Decimal("0.10"), "min_purchase": Decimal("50.00")},
"SUMMER25": {"discount": Decimal("0.25"), "min_purchase": Decimal("100.00")},
"VIP50": {"discount": Decimal("0.50"), "min_purchase": Decimal("0.00")},
}
if coupon_code not in coupons:
return {"valid": False, "error": "Invalid coupon code"}
coupon = coupons[coupon_code]
if subtotal < coupon["min_purchase"]:
return {
"valid": False,
"error": f"Minimum purchase of ${coupon['min_purchase']} required",
}
if coupon_code == "VIP50" and user_tier != "vip":
return {"valid": False, "error": "This coupon is for VIP members only"}
discount_amount = subtotal * coupon["discount"]
final_total = subtotal - discount_amount
return {
"valid": True,
"original": float(subtotal),
"discount": float(discount_amount),
"total": float(final_total),
"coupon_applied": coupon_code,
}
Ver solución
Pregunta 1: "¿Hace lo que le pedí?" Suponiendo que pediste una función para aplicar cupones: sí, aplica cupones con validaciones. Estructura clara.
Pregunta 2: "¿Los valores de negocio son correctos?"
- WELCOME10 = 10% con mínimo $50 → ¿Correcto según tu negocio?
- SUMMER25 = 25% con mínimo $100 → ¿Correcto?
- VIP50 = 50% sin mínimo → ¿De verdad 50%? ¿Sin límite máximo de descuento?
- ⚠️ Los cupones están hardcoded. En producción deberían venir de base de datos.
- ⚠️ No hay fecha de expiración de cupones. SUMMER25 funciona todo el año.
- ⚠️
order_datese recibe pero nunca se usa.
Pregunta 3: "¿Hay algo que un atacante podría explotar?"
- No directamente (no hay SQL, no hay auth). Pero VIP50 con 50% de descuento sin cap puede ser explotado si alguien conoce el código.
- No hay límite de usos por usuario.
Pregunta 4: "¿Qué pasa cuando las cosas salen mal?"
- Cupón inválido → Manejado con
valid: False - Mínimo no alcanzado → Manejado
- ⚠️ ¿Qué pasa con
subtotalnegativo? No valida. - ⚠️ ¿Qué pasa si
user_tierno es uno válido? Silenciosamente permite el cupón (excepto VIP50).
Pregunta 5: "¿La decisión de diseño es apropiada?"
- ✅ Usa Decimal para dinero (correcto)
- ⚠️ Devuelve float en el resultado — pierde la precisión de Decimal
- ⚠️ Cupones hardcoded — no escala
- El parámetro
order_dateexiste pero no se usa (dead code o feature incompleta)
Decisión: EDITAR. La estructura es sólida (70% OK). Editar: quitar conversión a float, agregar validación de subtotal negativo, decidir qué hacer con order_date, verificar valores de negocio.
Ejercicio 3: Encontrar el nivel incorrecto (Medio)
Un developer aplica Managing an Intern así. ¿Qué está mal?
Revisión de PR generado por Claude Code:
Archivo: auth/jwt_handler.py
- Nivel: 3 (confianza verificada)
- Tiempo: 30 segundos
- Resultado: "Se ve bien, acepto"
Archivo: models/user.py
- Nivel: 1 (supervisión directa)
- Tiempo: 25 minutos
- Resultado: "Cambié los nombres de 4 campos"
Archivo: routes/payment.py
- Nivel: 2 (revisión enfocada)
- Tiempo: 5 minutos
- Resultado: "La lógica parece correcta"
Ver solución
Los niveles están invertidos:
- auth/jwt_handler.py debería ser Nivel 1 (no 3). JWT handling es seguridad crítica. 30 segundos de revisión para código de autenticación es peligrosamente insuficiente.
- models/user.py debería ser Nivel 3 (no 1). Modelos de datos Pydantic son boilerplate. 25 minutos cambiando nombres de campos es micro-management clásico.
- routes/payment.py debería ser Nivel 1 (no 2). Procesamiento de pagos requiere supervisión directa. 5 minutos para código financiero es insuficiente.
Resultado correcto:
- jwt_handler.py → Nivel 1, 20-30 min
- user.py → Nivel 3, 1-2 min
- payment.py → Nivel 1, 15-25 min
El developer gastó la mayor parte del tiempo (25 min) en lo menos importante y casi nada en lo más crítico.
Ejercicio 4: Plan de supervisión (Difícil)
Claude Code va a generar una feature completa de "sistema de notificaciones" con estos archivos:
notifications/
├── models.py (modelos Pydantic)
├── routes.py (endpoints CRUD)
├── service.py (lógica de envío de emails)
├── templates.py (templates de emails)
├── permissions.py (quién puede enviar a quién)
└── config.py (configuración SMTP)
Antes de generar, crea tu "plan de supervisión": para cada archivo, define el nivel y justifica.
Ver solución
| Archivo | Nivel | Justificación | Tiempo estimado |
|---|---|---|---|
| models.py | 3 | Modelos de datos, boilerplate. Revisar que los campos tengan sentido. | 1-2 min |
| routes.py | 2 | Endpoints CRUD, pero verificar validaciones y auth en los endpoints. | 5-8 min |
| service.py | 2 | Lógica de envío. Verificar manejo de errores (¿qué pasa si SMTP falla?), rate limiting de envíos. | 8-12 min |
| templates.py | 3 | Templates de texto. Revisión visual de que el contenido sea correcto. | 1-2 min |
| permissions.py | 1 | Autorización. Quién puede enviar a quién es lógica de seguridad. Verificar cada condición. | 15-20 min |
| config.py | 2 | Configuración SMTP. Verificar que secrets no estén hardcoded, que use variables de entorno. | 3-5 min |
Tiempo total estimado: 33-49 minutos
Sin el modelo MIT: Probablemente 15-20 minutos por archivo × 6 = 90-120 minutos, con la mayor parte del tiempo en archivos de bajo riesgo.
Con el modelo MIT: Enfocas tu tiempo en permissions.py (seguridad) y service.py (lógica), y pasas rápido por models.py y templates.py. Mismo o mejor resultado en la mitad del tiempo.
Ejercicio 5: Escribir las preguntas del manager (Difícil)
Para esta función, escribe las 5 preguntas del buen manager con respuestas específicas:
import hashlib
import hmac
import time
def verify_webhook(
payload: bytes,
signature: str,
secret: str,
tolerance: int = 300,
) -> bool:
timestamp, sig = signature.split(",")
ts = int(timestamp.split("=")[1])
if abs(time.time() - ts) > tolerance:
return False
signed_payload = f"{ts}.{payload.decode('utf-8')}"
expected_sig = hmac.new(
secret.encode("utf-8"),
signed_payload.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected_sig, sig.split("=")[1])
Ver solución
Nivel: 1 (supervisión directa). Es código de seguridad — verificación de webhooks.
Pregunta 1: "¿Hace lo que le pedí?" Sí, verifica un webhook comparando firma HMAC. Incluye tolerancia de tiempo para replay attacks.
Pregunta 2: "¿Los valores de negocio son correctos?"
- Tolerancia de 300 segundos (5 minutos) → ¿Es apropiado? Stripe usa 300, es un buen default.
- ¿El formato de signature (timestamp,sig) coincide con el proveedor de webhooks que estás usando?
Pregunta 3: "¿Hay algo que un atacante podría explotar?"
- ✅ Usa
hmac.compare_digest(timing-safe comparison) → Correcto. - ⚠️
hmac.newdebería serhmac.HMACohmac.new— en Python eshmac.new. Pero espera... no existehmac.new()en Python. La función correcta eshmac.new()que es un alias, o más comúnmente se usa directamentehmac.HMAC(). Verifica en la documentación. - ⚠️ Si
signatureno tiene el formato esperado (no tiene,),split(",")no lanzará error pero los valores serán incorrectos. No hay validación del formato. - ⚠️ Si
payload.decode('utf-8')falla con payload binario → crash sin manejar.
Pregunta 4: "¿Qué pasa cuando las cosas salen mal?"
- Signature mal formada → crash (no maneja ValueError de split/int)
- Payload no-UTF-8 → crash (UnicodeDecodeError)
- Secret vacío → hmac lo procesa pero resultado no tiene sentido
- ⚠️ Falta try/except para formato inválido de signature
Pregunta 5: "¿La decisión de diseño es apropiada?"
- HMAC-SHA256 → Estándar de la industria, correcto
- Tolerancia configurable → Buena decisión de diseño
- ⚠️ El formato de signature asume un proveedor específico (Stripe-like). ¿Es el correcto para tu caso?
Decisión: EDITAR. La lógica core es correcta, pero falta error handling para inputs malformados y hay un posible error con hmac.new.
Resumen
En esta cápsula aprendiste:
- Managing an Intern es el modelo que define cómo supervisas AI code: como un buen manager supervisa a un intern brillante
- Claude Code es un intern que sabe mucho pero no entiende tu negocio, no distingue lo crítico de lo trivial, y a veces inventa
- Un buen manager usa 3 niveles de supervisión: directa (security, lógica de negocio), enfocada (endpoints, error handling), y confianza verificada (boilerplate, config)
- Las 5 preguntas del buen manager te guían: ¿hace lo que pedí?, ¿valores correctos?, ¿seguridad?, ¿qué pasa si falla?, ¿diseño apropiado?
- Los anti-patrones a evitar: micro-manager (revisas todo), ausente (no revisas nada), inconsistente (depende de tu mood)
- La clave es invertir tiempo de supervisión proporcional al riesgo, no al volumen de código
Próxima cápsula: Circuit Breaker — cuándo pausar y verificar antes de continuar.
Recursos Adicionales
- MIT Research — How Developers Use AI - Investigación del MIT sobre cómo developers supervisan AI-generated code
- The Manager's Path — Camille Fournier - Framework de management que inspira el modelo MIT
- Google Engineering Practices — Code Review - Cómo Google escala code review con priorización
- Anthropic — Claude Code Documentation - Documentación oficial de Claude Code
- OWASP Code Review Guide - Checklist de seguridad para code review
- Dan Luu — Developer Productivity - Ensayos sobre productividad en software que aplican a supervisión de AI
Debugging & Code Review with Claude Code — Módulo 2, Cápsula 02 Claude Code Agentic Development Path — Guía #6 de 11