Módulo 2: Mental Models para AI Code
Ejercicio Integrador: Aplicar los 3 Mental Models
Ejercicio Integrador: Aplicar los 3 Mental Models
Descripción de la cápsula
Esta es la cápsula más importante del módulo. Las 3 cápsulas anteriores te dieron los modelos: Managing an Intern (cómo supervisar), Circuit Breaker (cuándo pausar), y Trust Calibration (cuánto confiar). Ahora los aplicas juntos a escenarios reales con código Python/FastAPI.
Vas a trabajar con 3 escenarios de complejidad creciente. En cada uno, Claude Code generó código y tu trabajo es aplicar los 3 mental models para validarlo. No basta con decir "está bien" o "está mal" — necesitas documentar tu proceso: qué modelo aplicaste, qué encontraste, qué decidiste, y por qué.
Este ejercicio simula exactamente lo que harás en el proyecto integrador del módulo 8, donde recibirás un codebase completo y harás code review profesional usando estos modelos. Piensa en esta cápsula como un ensayo general.
Cómo Trabajar los Escenarios
El proceso de los 3 modelos
Para cada escenario, sigue este proceso en orden:
PASO 1: Trust Calibration
├── ¿Qué tipo de tarea es?
├── ¿Cuál es mi confianza base?
├── ¿Qué factores ajustan mi confianza?
├── ¿Cuál es mi confianza final?
└── ¿Qué profundidad de revisión corresponde?
PASO 2: Managing an Intern
├── ¿Qué nivel MIT asigno? (1, 2, o 3)
├── ¿Qué superviso con detalle?
├── ¿Qué delego con confianza?
├── Aplicar las 5 preguntas del buen manager
└── Documentar findings
PASO 3: Circuit Breaker
├── ¿Dónde pongo los checkpoints?
├── ¿El código pasa cada checkpoint?
├── ¿Hay señales de "trip the breaker"?
└── Decisión: aceptar, editar, regenerar, o rechazar
Formato de documentación
Para cada escenario, documenta tu validación con este formato:
=== VALIDACIÓN DE [nombre del escenario] ===
TRUST CALIBRATION:
- Tipo de tarea: [tipo]
- Confianza base: [%]
- Ajustes: [lista]
- Confianza final: [%]
- Profundidad: [nivel]
MANAGING AN INTERN:
- Nivel MIT: [1/2/3]
- Superviso: [lista]
- Delego: [lista]
- Preguntas del manager:
1. ¿Hace lo que le pedí? [respuesta]
2. ¿Valores de negocio correctos? [respuesta]
3. ¿Seguridad? [respuesta]
4. ¿Manejo de errores? [respuesta]
5. ¿Diseño apropiado? [respuesta]
CIRCUIT BREAKER:
- Checkpoints definidos: [lista]
- Checkpoint results: [PASS/FAIL por cada uno]
- ¿Trip the breaker? [sí/no, por qué]
DECISIÓN FINAL: [aceptar / editar / regenerar / rechazar]
TIEMPO INVERTIDO: [minutos]
JUSTIFICACIÓN: [1-2 oraciones]
Escenario 1: CRUD Endpoint de Tareas (Routine)
Contexto
Le pediste a Claude Code: "Genera endpoints CRUD para un sistema de tareas con FastAPI. Cada tarea tiene título, descripción, prioridad, y estado."
Código generado
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime
from enum import Enum
import uuid
app = FastAPI(title="Task Manager API", version="1.0.0")
tasks_db: dict = {}
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
URGENT = "urgent"
class Status(str, Enum):
TODO = "todo"
IN_PROGRESS = "in_progress"
REVIEW = "review"
DONE = "done"
class TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: Optional[str] = Field(None, max_length=2000)
priority: Priority = Priority.MEDIUM
status: Status = Status.TODO
class TaskUpdate(BaseModel):
title: Optional[str] = Field(None, min_length=1, max_length=200)
description: Optional[str] = Field(None, max_length=2000)
priority: Optional[Priority] = None
status: Optional[Status] = None
class Task(TaskCreate):
id: str
created_at: datetime
updated_at: datetime
@app.post("/tasks", response_model=Task, status_code=201)
async def create_task(task: TaskCreate):
task_id = str(uuid.uuid4())
now = datetime.utcnow()
new_task = Task(
id=task_id,
created_at=now,
updated_at=now,
**task.model_dump(),
)
tasks_db[task_id] = new_task
return new_task
@app.get("/tasks", response_model=List[Task])
async def list_tasks(
status: Optional[Status] = None,
priority: Optional[Priority] = None,
skip: int = Query(default=0, ge=0),
limit: int = Query(default=20, ge=1, le=100),
):
tasks = list(tasks_db.values())
if status:
tasks = [t for t in tasks if t.status == status]
if priority:
tasks = [t for t in tasks if t.priority == priority]
tasks.sort(key=lambda t: t.created_at, reverse=True)
return tasks[skip : skip + limit]
@app.get("/tasks/{task_id}", response_model=Task)
async def get_task(task_id: str):
if task_id not in tasks_db:
raise HTTPException(status_code=404, detail="Task not found")
return tasks_db[task_id]
@app.patch("/tasks/{task_id}", response_model=Task)
async def update_task(task_id: str, task_update: TaskUpdate):
if task_id not in tasks_db:
raise HTTPException(status_code=404, detail="Task not found")
existing = tasks_db[task_id]
update_data = task_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(existing, field, value)
existing.updated_at = datetime.utcnow()
tasks_db[task_id] = existing
return existing
@app.delete("/tasks/{task_id}")
async def delete_task(task_id: str):
if task_id not in tasks_db:
raise HTTPException(status_code=404, detail="Task not found")
del tasks_db[task_id]
return {"message": "Task deleted successfully"}
Tu turno
Aplica los 3 mental models al código anterior. Documenta tu proceso completo antes de ver la solución.
Ver solución
Validación del Escenario 1: CRUD de Tareas
TRUST CALIBRATION:
- Tipo de tarea: CRUD endpoints
- Confianza base: 65%
- Ajustes:
- +10%: Patrón estándar, lo he visto muchas veces
- +5%: No hay seguridad ni lógica de negocio compleja
- -5%: No hay tests
- Confianza final: 75%
- Profundidad: Revisión enfocada (5-8 minutos)
MANAGING AN INTERN:
- Nivel MIT: 2 (revisión enfocada) con partes en Nivel 3
- Superviso:
- Validaciones de input (¿son completas?)
- Error handling (¿respuestas correctas?)
- Lógica de filtrado y paginación
- Delego:
- Imports y boilerplate FastAPI
- Definición de Enums
- Estructura de modelos Pydantic
Preguntas del buen manager:
-
¿Hace lo que le pedí? Sí. CRUD completo: create, read (list + single), update, delete. Incluye filtrado y paginación que no pedí pero es útil.
-
¿Valores de negocio correctos?
- Prioridades (low, medium, high, urgent) → Razonables
- Estados (todo, in_progress, review, done) → Razonables
- max_length 200 para título, 2000 para descripción → Razonables
- Paginación default 20, max 100 → Razonable
- No hay valores de negocio complejos → N/A
-
¿Seguridad?
- ⚠️ No hay autenticación. Cualquiera puede CRUD todas las tareas.
- Para un prototipo: OK. Para producción: necesita auth.
- No hay SQL (in-memory dict), no hay injection risk.
- No hay secrets.
-
¿Manejo de errores?
- ✅ 404 para task no encontrado en get, update, delete
- ✅ 201 para create (correcto)
- ✅ Pydantic valida inputs automáticamente (422 para invalid)
- ✅ Query params validados (ge=0, ge=1, le=100)
-
¿Diseño apropiado?
- ✅ Usa Enum para priority y status (no strings libres)
- ✅ Usa UUID para IDs
- ✅ PATCH para actualización parcial (no PUT)
- ✅ updated_at se actualiza en patch
- ⚠️ In-memory dict → OK para prototipo, no para producción
- ⚠️ Delete es hard delete → ¿soft delete sería mejor?
CIRCUIT BREAKER:
- Checkpoints definidos: 1 (después de generación, es un solo archivo)
- Checkpoint result: PASS con observaciones menores
- ¿Trip the breaker? No. No hay issues críticos. Las observaciones son mejoras, no bugs.
DECISIÓN FINAL: Aceptar con 2 notas para el futuro
- Agregar auth cuando deje de ser prototipo
- Considerar soft delete en producción
TIEMPO INVERTIDO: 7 minutos
JUSTIFICACIÓN: CRUD estándar con buenas prácticas (Enums, UUID, PATCH, validaciones). No hay lógica de negocio compleja ni seguridad. Las mejoras son para producción, no para funcionalidad.
Escenario 2: Procesamiento de Pagos (Crítico)
Contexto
Le pediste a Claude Code: "Genera un servicio de procesamiento de pagos que cobre al usuario, registre la transacción, y envíe confirmación por email."
Código generado
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime
from decimal import Decimal
from enum import Enum
import uuid
import httpx
app = FastAPI(title="Payment Service")
transactions_db: dict = {}
class PaymentMethod(str, Enum):
CREDIT_CARD = "credit_card"
DEBIT_CARD = "debit_card"
BANK_TRANSFER = "bank_transfer"
class Currency(str, Enum):
USD = "USD"
EUR = "EUR"
MXN = "MXN"
class PaymentRequest(BaseModel):
user_id: str
amount: float
currency: Currency = Currency.USD
payment_method: PaymentMethod
card_number: Optional[str] = None
card_expiry: Optional[str] = None
card_cvv: Optional[str] = None
description: str = Field(..., max_length=500)
email: EmailStr
class Transaction(BaseModel):
id: str
user_id: str
amount: float
currency: Currency
status: str
payment_method: PaymentMethod
description: str
created_at: datetime
class PaymentResponse(BaseModel):
transaction_id: str
status: str
amount: float
message: str
STRIPE_API_KEY = "sk_test_abc123def456"
STRIPE_API_URL = "https://api.stripe.com/v1"
async def charge_payment(
amount: float,
currency: str,
payment_method: str,
card_number: str,
) -> dict:
async with httpx.AsyncClient() as client:
response = await client.post(
f"{STRIPE_API_URL}/charges",
headers={"Authorization": f"Bearer {STRIPE_API_KEY}"},
data={
"amount": int(amount * 100),
"currency": currency.lower(),
"source": card_number,
"description": "Payment charge",
},
)
return response.json()
async def send_confirmation_email(
email: str, transaction_id: str, amount: float
) -> None:
async with httpx.AsyncClient() as client:
await client.post(
"https://api.sendgrid.com/v3/mail/send",
headers={
"Authorization": "Bearer SG.xxxxx",
"Content-Type": "application/json",
},
json={
"personalizations": [{"to": [{"email": email}]}],
"from": {"email": "payments@myapp.com"},
"subject": "Payment Confirmation",
"content": [
{
"type": "text/plain",
"value": f"Payment of ${amount} confirmed. "
f"Transaction ID: {transaction_id}",
}
],
},
)
@app.post("/payments", response_model=PaymentResponse)
async def process_payment(payment: PaymentRequest):
charge_result = await charge_payment(
amount=payment.amount,
currency=payment.currency.value,
payment_method=payment.payment_method.value,
card_number=payment.card_number,
)
transaction = Transaction(
id=str(uuid.uuid4()),
user_id=payment.user_id,
amount=payment.amount,
currency=payment.currency,
status="completed",
payment_method=payment.payment_method,
description=payment.description,
created_at=datetime.utcnow(),
)
transactions_db[transaction.id] = transaction
await send_confirmation_email(
email=payment.email,
transaction_id=transaction.id,
amount=payment.amount,
)
return PaymentResponse(
transaction_id=transaction.id,
status="completed",
amount=payment.amount,
message="Payment processed successfully",
)
@app.get("/payments/{transaction_id}")
async def get_transaction(transaction_id: str):
if transaction_id not in transactions_db:
raise HTTPException(status_code=404, detail="Transaction not found")
return transactions_db[transaction_id]
Tu turno
Este escenario es crítico — procesamiento de pagos. Aplica los 3 mental models con el rigor correspondiente. Documenta cada issue que encuentres.
Ver solución
Validación del Escenario 2: Procesamiento de Pagos
TRUST CALIBRATION:
- Tipo de tarea: Procesamiento financiero
- Confianza base: 10%
- Ajustes:
- -10%: No hay tests
- -5%: Maneja datos de tarjeta (PCI compliance)
- -5%: Integración con API externa (Stripe)
- El tipo de tarea ya es lo más bajo → 0% práctico
- Confianza final: ~0% (desconfianza total, revisar cada línea)
- Profundidad: Línea por línea + verificar contra docs + escribir tests
MANAGING AN INTERN:
- Nivel MIT: 1 (supervisión directa) para TODO el archivo
- Superviso: cada línea, cada decisión
- Delego: nada
Preguntas del buen manager:
-
¿Hace lo que le pedí? Parcialmente. Cobra, registra transacción, envía email. Pero la implementación tiene problemas fundamentales.
-
¿Valores de negocio correctos?
- ⚠️
amount: float— Usa float para dinero.Decimales obligatorio.0.1 + 0.2 != 0.3con floats. - ⚠️ No valida que amount sea positivo (puede cobrar -$100).
- ⚠️ No tiene monto mínimo ni máximo.
- ⚠️ Status siempre es "completed" — no verifica si el cargo realmente fue exitoso.
- ⚠️
-
¿Seguridad? (MÚLTIPLES ISSUES CRÍTICOS)
- ❌
STRIPE_API_KEY = "sk_test_abc123def456"— API key hardcoded en el código fuente. Esto es un deal-breaker absoluto. Debe ser variable de entorno. - ❌
"Authorization": "Bearer SG.xxxxx"— API key de SendGrid hardcoded. - ❌
card_number,card_expiry,card_cvvpasan por el backend. En un sistema PCI-compliant, los datos de tarjeta nunca tocan tu servidor — van directamente a Stripe via Stripe Elements/Tokens. Pasar card_number a tu backend te pone en scope de PCI-DSS Level 1 (costo: $50k-$500k/año). - ❌ No hay autenticación. Cualquiera puede procesar pagos.
- ❌ No hay rate limiting. Un atacante puede hacer miles de cargos.
- ❌
-
¿Manejo de errores?
- ❌
charge_paymentno maneja errores. Si Stripe retorna error, el código continúa y registra la transacción como "completed". - ❌ Si
send_confirmation_emailfalla, no se catchea el error. El response al usuario falla aunque el pago se procesó. - ❌ No hay retry logic para ninguna operación.
- ❌ No hay idempotency key. Si el usuario hace doble click, se cobra dos veces.
- ❌ No hay transacción atómica. Si se registra la transacción pero el email falla, la transacción queda sin confirmación.
- ❌
-
¿Diseño apropiado?
- ❌ Stripe API se usa incorrectamente. No se usan PaymentIntents (el approach moderno). Se usa el endpoint de Charges legacy.
- ❌ No se verifica el response de Stripe.
response.json()se retorna sin validar status code. - ❌ In-memory storage para transacciones financieras. Un restart pierde todo el historial.
- ⚠️
int(amount * 100)para convertir a centavos. Con float, puede dar resultados incorrectos:int(19.99 * 100)=1998en vez de1999.
CIRCUIT BREAKER:
- Checkpoints: 1 (es un solo archivo)
- Checkpoint result: FAIL
- ¿Trip the breaker? SÍ, inmediatamente.
- API keys hardcoded → breaker trip (seguridad)
- Datos de tarjeta en backend → breaker trip (compliance)
- No verifica resultado de Stripe → breaker trip (lógica financiera)
- Float para dinero → breaker trip (precisión financiera)
DECISIÓN FINAL: RECHAZAR completamente.
TIEMPO INVERTIDO: 25 minutos
JUSTIFICACIÓN: El código tiene al menos 10 issues críticos, incluyendo API keys hardcoded, datos de tarjeta pasando por el backend (PCI violation), falta de verificación de resultados de Stripe, float para dinero, y zero error handling en operaciones financieras. La estructura fundamental está equivocada — no es cuestión de editar algunas líneas. Se debe regenerar con un prompt que especifique: usar Stripe PaymentIntents, no recibir datos de tarjeta, variables de entorno para secrets, Decimal para montos, idempotency keys, y error handling completo. Idealmente, se debe usar la documentación oficial de Stripe como referencia en el prompt.
Issues encontrados (resumen):
| # | Issue | Severidad | Categoría |
|---|---|---|---|
| 1 | Stripe API key hardcoded | Crítico | Seguridad |
| 2 | SendGrid API key hardcoded | Crítico | Seguridad |
| 3 | Card data touches backend (PCI) | Crítico | Compliance |
| 4 | No auth en endpoint | Crítico | Seguridad |
| 5 | Float para dinero | Alto | Precisión financiera |
| 6 | No verifica resultado de Stripe | Alto | Lógica de negocio |
| 7 | No idempotency key | Alto | Reliability |
| 8 | Zero error handling | Alto | Reliability |
| 9 | Charges API (legacy) | Medio | Best practices |
| 10 | In-memory storage | Medio | Arquitectura |
| 11 | No rate limiting | Medio | Seguridad |
| 12 | No valida amount > 0 | Medio | Validación |
Escenario 3: Middleware de Request Logging (Medio)
Contexto
Le pediste a Claude Code: "Genera un middleware para FastAPI que loggee todas las requests con método, path, status code, duración, y IP del cliente. Incluye configuración para excluir ciertos paths del logging."
Código generado
import time
import logging
from typing import Callable, List, Optional
from fastapi import FastAPI, Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from pydantic import BaseModel
logger = logging.getLogger("request_logger")
class LoggingConfig(BaseModel):
enabled: bool = True
excluded_paths: List[str] = ["/health", "/metrics", "/docs", "/openapi.json"]
log_headers: bool = False
log_body: bool = False
slow_request_threshold_ms: float = 1000.0
class RequestLoggingMiddleware(BaseHTTPMiddleware):
def __init__(self, app: FastAPI, config: Optional[LoggingConfig] = None):
super().__init__(app)
self.config = config or LoggingConfig()
async def dispatch(
self, request: Request, call_next: Callable
) -> Response:
if not self.config.enabled:
return await call_next(request)
if request.url.path in self.config.excluded_paths:
return await call_next(request)
start_time = time.perf_counter()
client_ip = request.client.host if request.client else "unknown"
method = request.method
path = request.url.path
query = str(request.query_params) if request.query_params else ""
log_data = {
"method": method,
"path": path,
"query": query,
"client_ip": client_ip,
}
if self.config.log_headers:
log_data["headers"] = dict(request.headers)
if self.config.log_body and method in ("POST", "PUT", "PATCH"):
try:
body = await request.body()
log_data["body"] = body.decode("utf-8")[:10000]
except Exception:
log_data["body"] = "<unable to read body>"
try:
response = await call_next(request)
except Exception as exc:
duration_ms = (time.perf_counter() - start_time) * 1000
log_data.update({
"status_code": 500,
"duration_ms": round(duration_ms, 2),
"error": str(exc),
})
logger.error("Request failed", extra=log_data)
raise
duration_ms = (time.perf_counter() - start_time) * 1000
log_data.update({
"status_code": response.status_code,
"duration_ms": round(duration_ms, 2),
})
if duration_ms > self.config.slow_request_threshold_ms:
logger.warning("Slow request detected", extra=log_data)
elif response.status_code >= 400:
logger.warning("Request error", extra=log_data)
else:
logger.info("Request completed", extra=log_data)
return response
def setup_request_logging(
app: FastAPI,
config: Optional[LoggingConfig] = None,
) -> None:
middleware_config = config or LoggingConfig()
app.add_middleware(RequestLoggingMiddleware, config=middleware_config)
if not logger.handlers:
handler = logging.StreamHandler()
handler.setFormatter(
logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s "
"- %(method)s %(path)s %(status_code)s %(duration_ms)sms"
)
)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# Ejemplo de uso:
from fastapi import FastAPI
app = FastAPI()
config = LoggingConfig(
excluded_paths=["/health", "/metrics", "/docs", "/openapi.json", "/favicon.ico"],
log_headers=False,
log_body=False,
slow_request_threshold_ms=500.0,
)
setup_request_logging(app, config)
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"user_id": user_id, "name": "Test User"}
# Output esperado al llamar GET /users/1:
# 2024-03-14 10:30:45 - request_logger - INFO - Request completed
# - GET /users/1 200 12.34ms
Tu turno
Este escenario es de complejidad media — middleware de infraestructura. No es CRUD trivial pero tampoco es seguridad crítica. Aplica los 3 modelos y documenta.
Ver solución
Validación del Escenario 3: Request Logging Middleware
TRUST CALIBRATION:
- Tipo de tarea: Middleware/Infraestructura + Logging
- Confianza base: 55% (entre CRUD y lógica de negocio)
- Ajustes:
- +10%: Patrón conocido (request logging es estándar)
- +5%: Código bien estructurado y legible
- -5%: Log de headers/body puede exponer datos sensibles
- -5%: Middleware puede afectar performance de toda la app
- Confianza final: 60%
- Profundidad: Revisión detallada (10-15 minutos)
MANAGING AN INTERN:
- Nivel MIT: 2 (revisión enfocada) con partes en Nivel 1
- Superviso:
- Qué se loggea (¿puede exponer datos sensibles?)
- Error handling del middleware (¿puede crashear la app?)
- Performance (¿el middleware agrega latencia significativa?)
- Delego:
- Estructura general del middleware
- Configuración Pydantic
- Formato de logging
Preguntas del buen manager:
-
¿Hace lo que le pedí? ✅ Sí. Loggea método, path, status code, duración, IP. Permite excluir paths. Incluye extras útiles: slow request detection, error logging, body/header logging opcional. Incluye ejemplo de uso.
-
¿Valores de negocio correctos?
- slow_request_threshold_ms: 1000ms default → Razonable
- body truncado a 10,000 chars → Razonable
- Paths excluidos default → Razonables
- N/A para valores de negocio — es infraestructura
-
¿Seguridad?
- ⚠️
log_headers=Trueloggaría headers de Authorization (tokens, API keys). El middleware debería sanitizar headers sensibles antes de loggarlos, o al menos excluir Authorization, Cookie, y Set-Cookie por default. - ⚠️
log_body=Trueloggaría passwords en login requests, datos de tarjeta en payment requests. Debería haber un mecanismo para redactar campos sensibles. - ⚠️
client_ipse obtiene derequest.client.host. Detrás de un proxy/load balancer, esto da la IP del proxy, no del cliente real. Debería usarX-Forwarded-Forheader (con cuidado de IP spoofing). - ✅ Ambas opciones están en False por default — bien.
- ⚠️
-
¿Manejo de errores?
- ✅ Si
request.body()falla, catchea la excepción y loggea<unable to read body>. - ✅ Si el handler lanza excepción, el middleware loggea el error y re-raise.
- ⚠️
str(exc)en el error log podría exponer información interna (stack traces, paths del servidor). En producción, sería mejor loggear el tipo de error sin detalles internos expuestos al log output que podría ir a servicios de terceros. - ✅ El middleware no crashea la app si hay error en el logging.
- ✅ Si
-
¿Diseño apropiado?
- ✅ Usa BaseHTTPMiddleware de Starlette (approach correcto).
- ✅ Configuración via Pydantic model (limpio y validado).
- ✅
time.perf_counter()para medir duración (más preciso quetime.time()). - ✅ Función
setup_request_loggingpara setup fácil. - ✅ Guard contra handlers duplicados (
if not logger.handlers). - ⚠️ El formatter usa
%(method)s %(path)setc. que requiere que esos campos estén enextra. Si el logger se usa en otro lugar sin esos campos, podría dar KeyError. Considerar usar un formato que no dependa de extra. - ⚠️ Excluded_paths usa comparación exacta.
/healthexcluye/healthpero no/health/ni/health?check=true. Considerar usar prefix matching o regex.
CIRCUIT BREAKER:
- Checkpoints: 1 (un solo archivo con ejemplo de uso)
- Checkpoint result: PASS con observaciones
- ¿Trip the breaker? No. No hay issues críticos, solo mejoras de seguridad proactivas.
DECISIÓN FINAL: Aceptar con edits menores.
TIEMPO INVERTIDO: 12 minutos
JUSTIFICACIÓN: El middleware está bien implementado para el caso de uso estándar. Las funcionalidades peligrosas (log_headers, log_body) están deshabilitadas por default. Los issues encontrados son mejoras de seguridad proactivas (sanitizar headers si se activa log_headers, manejar X-Forwarded-For) que pueden implementarse como follow-up. El código es limpio, bien estructurado, y el ejemplo de uso es claro.
Edits recomendados:
- Agregar sanitización de headers sensibles cuando
log_headers=True - Usar
X-Forwarded-Forpara IP del cliente con fallback arequest.client.host - Considerar prefix matching para excluded_paths
Meta-Ejercicio: Reflexión sobre los Modelos
Después de completar los 3 escenarios, responde estas preguntas:
1. ¿Qué modelo fue más útil en cada escenario?
Ver solución
- Escenario 1 (CRUD): Trust Calibration fue el más útil. La calibración alta (75%) te dijo que una revisión rápida era suficiente. Sin calibración, podrías haber gastado 20 minutos en CRUD routine.
- Escenario 2 (Pagos): Managing an Intern fue el más útil. Las 5 preguntas del manager revelaron cada issue. Circuit Breaker confirmó que debías detenerte, pero MIT te dijo qué estaba mal.
- Escenario 3 (Middleware): Los 3 modelos contribuyeron equitativamente. Trust Calibration definió la profundidad (60%), MIT te dijo dónde mirar (seguridad del logging), y Circuit Breaker confirmó que podías continuar con edits menores.
2. ¿Cuánto tiempo habrías gastado sin los modelos?
Ver solución
Sin modelos, la tendencia es una de dos:
- Revisión uniforme: ~15 minutos por escenario × 3 = 45 minutos. Problema: gastas demasiado en CRUD y no suficiente en pagos.
- Revisión por "feeling": Variable e inconsistente. A veces 5 minutos en todo, a veces 30 minutos en lo trivial.
Con modelos:
- Escenario 1: 7 minutos (calibración alta → revisión rápida)
- Escenario 2: 25 minutos (calibración baja → revisión exhaustiva)
- Escenario 3: 12 minutos (calibración media → revisión enfocada)
- Total: 44 minutos
El tiempo total es similar, pero la distribución es radicalmente diferente. Sin modelos, los 44 minutos se distribuyen uniformemente (o al azar). Con modelos, el 57% del tiempo se invierte en el código más riesgoso (pagos).
3. ¿Cuál de los 3 modelos internalizarías primero?
Ver solución
Trust Calibration es el más fácil de internalizar primero porque:
- Es el más accionable: literalmente puedes usar la tabla mañana.
- No requiere cambiar tu flujo de trabajo — solo te dice cuánto revisar.
- Se refuerza con cada uso: cada vez que revisas código, tu tabla se actualiza.
Managing an Intern es el segundo: requiere cambiar cómo piensas sobre revisión (supervisar vs micro-manage).
Circuit Breaker es el tercero: requiere cambiar tu flujo de trabajo (agregar pausas explícitas), que es el cambio más difícil.
Sin embargo, los 3 funcionan mejor juntos. Trust Calibration sin MIT es incompleta (sabes cuánto confiar pero no qué supervisar). MIT sin Circuit Breaker es riesgoso (sabes qué supervisar pero no cuándo pausar).
Ejercicio Extra: Tu Propio Escenario
Genera código real con Claude Code
Este ejercicio es el más valioso del módulo. Necesitas Claude Code (o tu AI coding tool de preferencia):
- Elige una tarea real de tu trabajo o proyecto personal
- Genera el código con Claude Code
- Aplica los 3 modelos usando el formato de documentación de esta cápsula
- Documenta tu proceso completo: calibración, preguntas del manager, checkpoints, decisión
Ver guía de evaluación
Tu documentación debería:
- ✅ Tener confianza base con justificación
- ✅ Tener ajustes de confianza con factores específicos
- ✅ Tener nivel MIT con justificación
- ✅ Responder las 5 preguntas del manager con respuestas específicas al código
- ✅ Definir al menos 1 checkpoint con criterio de pass/fail
- ✅ Tener una decisión final justificada
- ✅ Incluir tiempo invertido
- ✅ Si encontraste issues, listarlos con severidad
Tu documentación no debería:
- ❌ Tener confianza sin justificación ("confío 50%" sin explicar por qué)
- ❌ Tener respuestas genéricas ("se ve bien")
- ❌ Saltar alguno de los 3 modelos
- ❌ No tener decisión final
Conexión con Proyecto
Del ejercicio al proyecto integrador
Los escenarios de esta cápsula son versiones simplificadas de lo que enfrentarás en el módulo 8:
| Esta cápsula | Módulo 8 |
|---|---|
| 3 escenarios aislados | 1 codebase completo con múltiples archivos |
| Código generado para ti | Código que tú revisas como si fuera de un PR |
| Issues plantados | Issues plantados + issues sutiles |
| 3 tipos de riesgo | 15-20 problemas en 3 zonas de riesgo |
| 44 minutos | 90-120 minutos |
La diferencia: en el módulo 8, los archivos están interconectados. Un issue en models.py puede causar problemas en service.py que se manifiestan en routes.py. Tu capacidad de aplicar los 3 modelos de forma fluida — sin tener que consultar esta cápsula — determina tu efectividad.
Preparación
Si puedes completar los 3 escenarios de esta cápsula en menos de 50 minutos con documentación completa, estás listo para el módulo 8. Si te toma más, practica con el ejercicio extra (genera tu propio escenario) hasta que el proceso sea fluido.
Troubleshooting
Problema 1: "Me cuesta aplicar los 3 modelos — se siente repetitivo"
Causa: Los modelos se solapan intencionalmente. La repetición es parte del diseño. Solución: Piensa en los modelos como perspectivas, no pasos. No necesitas aplicarlos secuencialmente en producción. Con práctica, los aplicarás simultáneamente: "esta tarea es CRUD (Trust: 65%), reviso validaciones y error handling (MIT Nivel 2), y pongo un checkpoint después de cada archivo (Circuit Breaker)." Una sola oración cubre los 3.
Problema 2: "Mi calibración del escenario 2 fue muy alta — no encontré todos los issues"
Causa: Under-estimated el riesgo de procesamiento de pagos. Solución: Para código financiero, la calibración correcta siempre es < 20%. Si tu calibración fue mayor, ajusta tu tabla. La regla: si el código toca dinero, datos de tarjeta, o compliance, tu confianza base es automáticamente < 20%.
Problema 3: "No sé cuándo dejar de buscar issues"
Causa: No tienes un criterio de "suficiente." Solución: Tu criterio de suficiencia viene de Trust Calibration:
- Confianza 80%+: Para cuando la estructura general se ve bien (2-3 min).
- Confianza 50-70%: Para cuando la lógica principal y edge cases están cubiertos (5-10 min).
- Confianza 10-30%: Para cuando cada línea ha sido revisada y los tests existen (20+ min).
Problema 4: "El formato de documentación es tedioso para uso diario"
Causa: El formato completo es para aprendizaje, no para producción. Solución: En tu trabajo diario, la documentación se simplifica a notas mentales o un comentario en el PR:
# Trust: 60% (CRUD + familiar domain)
# MIT: Nivel 2 — revisé validaciones y error handling
# CB: Checkpoint post-generation → PASS
# Decision: Accept with minor edits (email validation)
4 líneas. 10 segundos de documentación. El proceso mental completo sigue corriendo en tu cabeza.
Resumen
En esta cápsula aplicaste:
- Los 3 mental models juntos a 3 escenarios de complejidad creciente
- Trust Calibration para determinar la profundidad de revisión: 75% (CRUD), 0% (pagos), 60% (middleware)
- Managing an Intern para identificar qué supervisar con las 5 preguntas del manager
- Circuit Breaker para decidir si continuar, editar, o detenerte
- La distribución del tiempo es clave: el 57% del tiempo se invirtió en el 33% del código (el más riesgoso)
- Los modelos se complementan: Trust Calibration dice cuánto, MIT dice cómo, Circuit Breaker dice cuándo
Resultado de los 3 escenarios:
| Escenario | Confianza | Decisión | Issues | Tiempo |
|---|---|---|---|---|
| CRUD Tareas | 75% | Aceptar | 2 menores | 7 min |
| Pagos | 0% | Rechazar | 12 críticos | 25 min |
| Middleware | 60% | Aceptar + edits | 3 mejoras | 12 min |
Próximo módulo: Detectar Hallucinations en Código — aplicar estos mental models al error más sutil y peligroso.
Recursos Adicionales
- The Pragmatic Programmer — Dave Thomas & Andy Hunt - Filosofía de verificación y calidad en software
- Google — Code Review Developer Guide - Cómo Google estructura code review por riesgo
- Stripe — Security Best Practices - Best practices para integración de pagos (referencia para Escenario 2)
- OWASP — Logging Cheat Sheet - Qué logear y qué no (referencia para Escenario 3)
- Anthropic — Claude Code Documentation - Documentación oficial de Claude Code
- FastAPI — Middleware Documentation - Referencia oficial de middleware en FastAPI
Debugging & Code Review with Claude Code — Módulo 2, Cápsula 05 Claude Code Agentic Development Path — Guía #6 de 11