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:

  1. ¿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.

  2. ¿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
  3. ¿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.
  4. ¿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)
  5. ¿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:

  1. ¿Hace lo que le pedí? Parcialmente. Cobra, registra transacción, envía email. Pero la implementación tiene problemas fundamentales.

  2. ¿Valores de negocio correctos?

    • ⚠️ amount: float — Usa float para dinero. Decimal es obligatorio. 0.1 + 0.2 != 0.3 con 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.
  3. ¿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_cvv pasan 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.
  4. ¿Manejo de errores?

    • ❌ charge_payment no maneja errores. Si Stripe retorna error, el código continúa y registra la transacción como "completed".
    • ❌ Si send_confirmation_email falla, 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.
  5. ¿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) = 1998 en vez de 1999.

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):

#IssueSeveridadCategoría
1Stripe API key hardcodedCríticoSeguridad
2SendGrid API key hardcodedCríticoSeguridad
3Card data touches backend (PCI)CríticoCompliance
4No auth en endpointCríticoSeguridad
5Float para dineroAltoPrecisión financiera
6No verifica resultado de StripeAltoLógica de negocio
7No idempotency keyAltoReliability
8Zero error handlingAltoReliability
9Charges API (legacy)MedioBest practices
10In-memory storageMedioArquitectura
11No rate limitingMedioSeguridad
12No valida amount > 0MedioValidació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:

  1. ¿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.

  2. ¿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
  3. ¿Seguridad?

    • ⚠️ log_headers=True loggarí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=True loggaría passwords en login requests, datos de tarjeta en payment requests. Debería haber un mecanismo para redactar campos sensibles.
    • ⚠️ client_ip se obtiene de request.client.host. Detrás de un proxy/load balancer, esto da la IP del proxy, no del cliente real. Debería usar X-Forwarded-For header (con cuidado de IP spoofing).
    • ✅ Ambas opciones están en False por default — bien.
  4. ¿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.
  5. ¿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 que time.time()).
    • ✅ Función setup_request_logging para setup fácil.
    • ✅ Guard contra handlers duplicados (if not logger.handlers).
    • ⚠️ El formatter usa %(method)s %(path)s etc. que requiere que esos campos estén en extra. 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. /health excluye /health pero 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:

  1. Agregar sanitización de headers sensibles cuando log_headers=True
  2. Usar X-Forwarded-For para IP del cliente con fallback a request.client.host
  3. 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:

  1. Es el más accionable: literalmente puedes usar la tabla mañana.
  2. No requiere cambiar tu flujo de trabajo — solo te dice cuánto revisar.
  3. 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):

  1. Elige una tarea real de tu trabajo o proyecto personal
  2. Genera el código con Claude Code
  3. Aplica los 3 modelos usando el formato de documentación de esta cápsula
  4. 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ápsulaMódulo 8
3 escenarios aislados1 codebase completo con múltiples archivos
Código generado para tiCódigo que tú revisas como si fuera de un PR
Issues plantadosIssues plantados + issues sutiles
3 tipos de riesgo15-20 problemas en 3 zonas de riesgo
44 minutos90-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:

EscenarioConfianzaDecisiónIssuesTiempo
CRUD Tareas75%Aceptar2 menores7 min
Pagos0%Rechazar12 críticos25 min
Middleware60%Aceptar + edits3 mejoras12 min

Próximo módulo: Detectar Hallucinations en Código — aplicar estos mental models al error más sutil y peligroso.


Recursos Adicionales

  1. The Pragmatic Programmer — Dave Thomas & Andy Hunt - Filosofía de verificación y calidad en software
  2. Google — Code Review Developer Guide - Cómo Google estructura code review por riesgo
  3. Stripe — Security Best Practices - Best practices para integración de pagos (referencia para Escenario 2)
  4. OWASP — Logging Cheat Sheet - Qué logear y qué no (referencia para Escenario 3)
  5. Anthropic — Claude Code Documentation - Documentación oficial de Claude Code
  6. 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