Módulo 1: Solo 3% Confía — Por Qué y Qué Hacer
Tu Primer Framework de Verificación
Tu Primer Framework de Verificación
Descripción de la cápsula
Has recorrido el arco completo de este módulo: el problema (3%), los extremos (aceptar todo / rechazar todo), y el principio (confianza calibrada). Ahora necesitas algo concreto — un framework que puedas usar mañana. En esta cápsula construyes tu primer framework de verificación: un proceso de 3 niveles que te dice qué revisar siempre, qué revisar a veces, y qué confiar generalmente.
Este framework no es definitivo. En los módulos siguientes lo expandirás con mental models (módulo 2), detección de hallucinations (módulo 3), y un checklist profesional completo (módulo 4). Pero desde hoy, ya tienes algo que funciona. El objetivo es que pases de "reviso sin criterio" a "tengo un proceso" en los próximos 20 minutos.
El Framework de 3 Niveles
Nivel 1: Revisar SIEMPRE (Red Zone)
Estas son las áreas donde un error tiene impacto crítico. Sin excepciones. Aunque confíes en Claude Code, aunque tengas prisa, aunque el código se vea perfecto.
🔴 SIEMPRE revisar:
1. SEGURIDAD
- ¿Hay secrets hardcoded? (API keys, passwords, tokens)
- ¿Las queries SQL son parameterizadas?
- ¿Los endpoints sensibles tienen autenticación?
- ¿Los tokens expiran?
- ¿Los inputs están sanitizados?
2. LÓGICA DE NEGOCIO
- ¿El código hace lo que el negocio necesita?
- ¿Los cálculos son correctos? (precios, descuentos, taxes)
- ¿Las condiciones son correctas? (>, <, >=, <=, ==)
- ¿Los filtros incluyen/excluyen lo correcto?
3. DATOS
- ¿Las operaciones de base de datos son correctas?
- ¿Hay riesgo de corrupción o pérdida de datos?
- ¿Las migraciones son reversibles?
- ¿Los datos sensibles están protegidos?
Tiempo típico: 10-30 minutos dependiendo de la complejidad.
Regla de oro: Si no estás seguro de si algo cae en este nivel, cae en este nivel.
Nivel 2: Revisar FRECUENTEMENTE (Yellow Zone)
Estas áreas requieren atención pero no exhaustividad. Un error aquí causa problemas pero no catástrofes. Revisa con ojo crítico pero no línea por línea.
🟡 FRECUENTEMENTE revisar:
1. EDGE CASES
- ¿Qué pasa con null/None?
- ¿Qué pasa con listas vacías?
- ¿Qué pasa con el primer/último elemento?
- ¿Qué pasa con strings vacíos?
- ¿Qué pasa con números negativos o cero?
2. ERROR HANDLING
- ¿Los errores se manejan o crashean silenciosamente?
- ¿Los mensajes de error son útiles (no exponen info interna)?
- ¿Hay try/except donde debería haber?
- ¿Los HTTP status codes son correctos?
3. VALIDACIÓN DE INPUTS
- ¿Los inputs se validan antes de procesarlos?
- ¿Los tipos son correctos?
- ¿Los rangos son razonables? (edad > 0, precio >= 0)
- ¿Las strings tienen límite de longitud?
4. NAMING Y CLARIDAD
- ¿Los nombres de funciones describen lo que hacen?
- ¿Las variables tienen nombres descriptivos?
- ¿El código es legible sin comentarios?
Tiempo típico: 5-15 minutos.
Cuándo saltar: Si ya hiciste una revisión exhaustiva del Nivel 1 y el código es de baja complejidad, puedes hacer una revisión rápida del Nivel 2.
Nivel 3: Confiar GENERALMENTE (Green Zone)
Estas áreas tienen bajo riesgo y AI es consistentemente buena en ellas. Una revisión visual rápida es suficiente.
🟢 GENERALMENTE confiar:
1. BOILERPLATE
- Setup de proyecto (pip, requirements, etc.)
- Imports estándar
- Configuración básica de frameworks
- Estructura de archivos
2. FORMATEO Y ESTILO
- Indentación
- Orden de imports
- Convenciones de estilo
- Formateo de strings
3. DOCUMENTACIÓN
- Docstrings
- Comentarios explicativos
- README básico
- Type hints
4. CÓDIGO ESTÁNDAR
- Patrones bien conocidos (singleton, factory)
- CRUD operations básicas
- Serialización/deserialización
- Conversiones de tipos simples
Tiempo típico: 1-3 minutos (revisión visual).
Cuándo profundizar: Si algo en la Green Zone "se siente raro" — un import que no reconoces, una configuración inusual — promuévelo a Yellow Zone y revisa con más detalle.
El Framework en Acción: Ejemplo Completo
Claude Code genera un módulo de gestión de usuarios. Apliquemos el framework:
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel, EmailStr, Field
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Optional
import jwt
import os
app = FastAPI()
SECRET_KEY = os.getenv("SECRET_KEY", "fallback-dev-key")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
users_db = {}
class UserCreate(BaseModel):
email: EmailStr
password: str = Field(..., min_length=8)
name: str = Field(..., min_length=1, max_length=100)
class UserResponse(BaseModel):
id: str
email: str
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_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def create_access_token(data: dict) -> str:
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
email = payload.get("sub")
if email is None:
raise HTTPException(status_code=401, detail="Invalid token")
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.PyJWTError:
raise HTTPException(status_code=401, detail="Invalid token")
user = users_db.get(email)
if user is None:
raise HTTPException(status_code=401, detail="User not found")
return user
@app.post("/register", response_model=UserResponse)
async def register(user: UserCreate):
if user.email in users_db:
raise HTTPException(status_code=400, detail="Email already registered")
hashed_password = hash_password(user.password)
new_user = {
"id": str(len(users_db) + 1),
"email": user.email,
"name": user.name,
"password_hash": hashed_password,
"created_at": datetime.utcnow()
}
users_db[user.email] = new_user
return UserResponse(**new_user)
@app.post("/token", response_model=Token)
async def login(email: str, password: str):
user = users_db.get(email)
if not user or not verify_password(password, user["password_hash"]):
raise HTTPException(status_code=401, detail="Invalid credentials")
access_token = create_access_token(data={"sub": user["email"]})
return Token(access_token=access_token, token_type="bearer")
@app.get("/me", response_model=UserResponse)
async def get_me(current_user: dict = Depends(get_current_user)):
return UserResponse(**current_user)
Aplicando el framework
Nivel 1 (Red Zone) — SIEMPRE revisar:
🔴 SEGURIDAD:
✅ Secret — usa os.getenv(), bien. PERO "fallback-dev-key" es
peligroso si se olvida configurar en producción.
→ ACCIÓN: Cambiar a os.getenv("SECRET_KEY") sin fallback,
que falle si no está configurado.
✅ Password hashing — usa bcrypt via passlib. Correcto.
✅ Token expiración — tiene ACCESS_TOKEN_EXPIRE_MINUTES = 30.
Token incluye "exp". Bien.
✅ Token validation — maneja ExpiredSignatureError y PyJWTError.
Bien.
⚠️ Login endpoint — acepta email y password como query params.
→ PROBLEMA: Passwords en query params aparecen en logs del servidor.
→ ACCIÓN: Cambiar a request body con OAuth2PasswordRequestForm.
⚠️ Rate limiting — no hay. Un atacante puede hacer brute force.
→ ACCIÓN: Agregar rate limiting al endpoint /token.
🔴 LÓGICA DE NEGOCIO:
✅ Registro — verifica email duplicado. Bien.
✅ Login — compara password hasheado. Bien.
⚠️ ID generation — usa len(users_db) + 1. Si se borra un usuario,
los IDs se reutilizan.
→ ACCIÓN: Usar UUID en vez de counter.
Nivel 2 (Yellow Zone) — Revisar frecuentemente:
🟡 EDGE CASES:
⚠️ ¿Qué pasa si el email tiene mayúsculas? "User@Email.com" y
"user@email.com" serían usuarios diferentes.
→ ACCIÓN: Normalizar email a lowercase.
⚠️ Password con solo espacios pasa min_length=8.
→ ACCIÓN: Agregar validación de complejidad (o al menos strip).
🟡 ERROR HANDLING:
✅ 401 para credenciales inválidas — no revela si el email existe
o no. Bien (security best practice).
✅ 400 para email duplicado — correcto.
🟡 VALIDACIÓN:
✅ EmailStr valida formato de email — correcto.
✅ min_length en password y name — correcto.
Nivel 3 (Green Zone) — Confiar generalmente:
🟢 BOILERPLATE:
✅ Imports — todos existen y son correctos.
✅ FastAPI setup — estándar.
✅ Pydantic models — bien estructurados.
✅ Type hints — consistentes.
Resultado
Tiempo total de revisión: ~15 minutos
Encontrado:
- 1 issue crítico (password en query params)
- 2 issues altos (fallback secret, sin rate limiting)
- 2 issues medios (ID generation, email normalization)
- 1 issue bajo (password solo espacios)
Decisión: EDITAR (no regenerar)
- El 85% del código está bien
- Los issues son puntuales y corregibles
- La estructura y approach son correctos
Checklist Rápido del Framework
Para uso diario, este es tu checklist de bolsillo:
Antes de aceptar código de Claude Code:
🔴 RED ZONE (siempre):
□ ¿Hay secrets hardcoded?
□ ¿Las queries SQL son seguras?
□ ¿Los endpoints sensibles tienen auth?
□ ¿La lógica de negocio es correcta?
□ ¿Los datos están protegidos?
🟡 YELLOW ZONE (frecuentemente):
□ ¿Maneja null/vacío/edge cases?
□ ¿Los errores se manejan correctamente?
□ ¿Los inputs se validan?
□ ¿Los nombres son descriptivos?
🟢 GREEN ZONE (visual rápido):
□ ¿Los imports se ven correctos?
□ ¿La estructura es estándar?
□ ¿El formato es consistente?
Si todo está OK → Acepta
Si hay issues en Green/Yellow → Edita
Si hay issues en Red → No aceptes sin corregir
Conexión con Proyecto
Cómo se conecta con el proyecto integrador (Módulo 8)
En el módulo 8 recibirás un codebase FastAPI con 15-20 problemas plantados. Tu trabajo será hacer code review profesional completo usando este framework (y las versiones expandidas de los módulos 2-7). Los problemas estarán distribuidos en las 3 zonas:
- Red Zone: Hallucinations, security holes, lógica de negocio incorrecta
- Yellow Zone: Edge cases no manejados, error handling incompleto
- Green Zone: Algunos issues menores que no deberían consumir tu tiempo
El framework te ayuda a priorizar: atacar Red Zone primero, Yellow Zone después, Green Zone al final (o nunca).
Troubleshooting
Problema 1: "El framework se siente demasiado simple"
Causa: Es simple a propósito. Es tu PRIMER framework — un punto de partida. Solución: En los módulos siguientes lo expandirás con mental models (módulo 2), técnicas de detección de hallucinations (módulo 3), un checklist profesional de 15+ items (módulo 4), y patrones de error (módulo 5). Este framework es el esqueleto; los módulos siguientes le agregan músculo.
Problema 2: "No sé en qué zona cae algo"
Causa: Algunos tipos de código están en el límite entre zonas. Solución: Regla: si dudas, promueve a la zona más alta. Es mejor revisar algo de Yellow Zone que en realidad era Green Zone, que confiar en algo de Red Zone tratándolo como Yellow.
Problema 3: "Toma demasiado tiempo revisar la Red Zone"
Causa: Estás revisando más de lo necesario o el código es genuinamente complejo. Solución: Para la Red Zone, enfócate en los 5 checks específicos del checklist rápido. No necesitas entender cada línea — necesitas verificar que esos 5 puntos están cubiertos. Si el código es genuinamente complejo (auth con roles, permisos granulares, multi-tenancy), entonces sí toma tiempo — y es tiempo bien invertido.
Ejercicios
Ejercicio 1: Aplicar el framework (Fácil)
Clasifica cada item en Red / Yellow / Green Zone:
- Un import de
datetime - Una función que calcula el tax rate basado en el estado del usuario
- Un query SQL que busca usuarios por nombre
- Un Dockerfile estándar para Python
- Una función que encripta datos de tarjeta de crédito
Ver solución
- Import de datetime → Green Zone. Import estándar de Python, zero riesgo. Revisión visual.
- Cálculo de tax rate → Red Zone. Lógica de negocio con impacto financiero. Revisar que los rates sean correctos, que las condiciones incluyan todos los estados, que los cálculos sean precisos.
- Query SQL por nombre → Red/Yellow Zone. Red si usa string concatenation (SQL injection). Yellow si usa parameterized queries (verificar lógica del query).
- Dockerfile estándar → Green Zone. Patrón estándar. Revisar versiones base rápidamente.
- Encriptar datos de tarjeta → Red Zone (Crítico). Security + compliance (PCI-DSS). Revisar algoritmo, key management, compliance. Posiblemente no debería generarse con AI sin experto en seguridad.
Ejercicio 2: Review con framework (Medio)
Aplica el framework de 3 niveles a este código generado por Claude Code:
from fastapi import FastAPI, Query
from typing import List, Optional
import sqlite3
app = FastAPI()
def get_db():
conn = sqlite3.connect("tasks.db")
return conn
@app.get("/tasks")
async def search_tasks(
query: Optional[str] = None,
status: Optional[str] = None,
limit: int = Query(default=10, ge=1, le=100)
):
conn = get_db()
cursor = conn.cursor()
sql = "SELECT * FROM tasks WHERE 1=1"
if query:
sql += f" AND title LIKE '%{query}%'"
if status:
sql += f" AND status = '{status}'"
sql += f" LIMIT {limit}"
cursor.execute(sql)
tasks = cursor.fetchall()
conn.close()
return {"tasks": tasks, "count": len(tasks)}
Documenta: (1) qué encontraste en cada zona, (2) qué acciones tomar.
Ver solución
🔴 Red Zone — CRÍTICO:
-
SQL Injection. La línea
sql += f" AND title LIKE '%{query}%'"concatena input del usuario directamente en el SQL. Un atacante puede inyectar SQL arbitrario.Ejemplo de ataque: query = "'; DROP TABLE tasks; --"Acción: Usar parameterized queries:
sql += " AND title LIKE ?" params.append(f"%{query}%") cursor.execute(sql, params) -
Mismo problema con status:
sql += f" AND status = '{status}'"— misma vulnerabilidad.
🟡 Yellow Zone:
-
Conexión no se cierra en caso de error. Si
cursor.execute()falla,conn.close()nunca se ejecuta. Memory leak de conexiones. Acción: Usar context manager (with) o try/finally. -
No valida status. Acepta cualquier string como status. ¿Debería ser un Enum? Acción: Definir valores válidos de status.
-
Devuelve tuples crudas.
fetchall()de sqlite3 devuelve tuples, no dicts. El response no tiene nombres de columnas. Acción: Usarrow_factory = sqlite3.Rowo mapear a Pydantic model.
🟢 Green Zone:
- ✅ Imports correctos
- ✅ Query parameter con validación (ge=1, le=100) — bien
- ✅ Estructura general del endpoint — OK
Decisión: RECHAZAR y regenerar (o editar significativamente). El SQL injection es un deal-breaker. La estructura necesita cambios fundamentales (parameterized queries, connection management). Es más rápido regenerar con un prompt que especifique "usa parameterized queries y connection pooling."
Ejercicio 3: El ejercicio del módulo — Analizar 3 snippets (Difícil)
Este es el ejercicio integrador del módulo 1. Analiza estos 3 snippets generados por AI y para cada uno:
- Aplica el framework de 3 niveles
- Asigna un % de confianza
- Decide: aceptar, editar, o rechazar
- Justifica tu decisión
Snippet A — Utilidad de formateo:
from datetime import datetime
from typing import Optional
def format_timestamp(
dt: Optional[datetime] = None,
fmt: str = "%Y-%m-%d %H:%M:%S"
) -> str:
if dt is None:
dt = datetime.utcnow()
return dt.strftime(fmt)
def time_ago(dt: datetime) -> str:
now = datetime.utcnow()
diff = now - dt
if diff.days > 365:
years = diff.days // 365
return f"{years} year{'s' if years > 1 else ''} ago"
elif diff.days > 30:
months = diff.days // 30
return f"{months} month{'s' if months > 1 else ''} ago"
elif diff.days > 0:
return f"{diff.days} day{'s' if diff.days > 1 else ''} ago"
elif diff.seconds > 3600:
hours = diff.seconds // 3600
return f"{hours} hour{'s' if hours > 1 else ''} ago"
elif diff.seconds > 60:
minutes = diff.seconds // 60
return f"{minutes} minute{'s' if minutes > 1 else ''} ago"
else:
return "just now"
Snippet B — Endpoint de transferencia de dinero:
@app.post("/transfer")
async def transfer_money(
from_account: str,
to_account: str,
amount: float
):
sender = get_account(from_account)
receiver = get_account(to_account)
sender.balance -= amount
receiver.balance += amount
save_account(sender)
save_account(receiver)
return {"status": "success", "amount": amount}
Snippet C — Configuración de logging:
import logging
import sys
def setup_logging(level: str = "INFO") -> logging.Logger:
logger = logging.getLogger("app")
logger.setLevel(getattr(logging, level.upper()))
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(getattr(logging, level.upper()))
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
Ver solución
Snippet A — Utilidad de formateo:
🔴 Red Zone: No hay security ni lógica de negocio → N/A 🟡 Yellow Zone:
- Edge case:
time_agocon fecha futura → da resultados negativos. ¿Manejar? - Edge case:
diff.daysexactamente 365 → no entra en años. Menor. 🟢 Green Zone: Imports correctos, nombres claros, estructura limpia.
Confianza: 80%. Utilidad de bajo riesgo. AI es buena en esto.
Decisión: Aceptar con nota mental de que time_ago no maneja fechas futuras.
Tiempo de revisión: 3 minutos.
Snippet B — Transferencia de dinero:
🔴 Red Zone — MÚLTIPLES PROBLEMAS:
- No valida amount — acepta negativos (transferencia inversa), cero, o montos astronómicos
- No verifica balance suficiente — sender puede quedar en negativo
- No es atómico — si
save_account(sender)funciona perosave_account(receiver)falla, el dinero desaparece - No tiene autenticación — cualquiera puede transferir desde cualquier cuenta
- Usa float para dinero — floating point math causa errores de precisión (0.1 + 0.2 ≠ 0.3)
- No hay logging/auditoría — transferencias financieras sin registro
Confianza: 5%. Lógica de negocio financiera con múltiples fallas críticas. Decisión: RECHAZAR. Regenerar con prompt detallado que especifique validaciones, atomicidad, auth, y Decimal para montos. Tiempo de revisión: 10 minutos (para identificar todos los problemas).
Snippet C — Configuración de logging:
🔴 Red Zone: N/A (no hay security ni lógica de negocio) 🟡 Yellow Zone:
getattr(logging, level.upper())— si level no es válido, lanza AttributeError. Podría manejar con try/except.- Llama
logger.addHandlersin verificar si ya existe — si llamassetup_logging()múltiples veces, añade handlers duplicados. 🟢 Green Zone: Imports correctos, patrón estándar de logging, formato razonable.
Confianza: 75%. Configuración estándar con riesgo bajo. Decisión: Editar. Agregar check de handlers duplicados y manejo de level inválido. Tiempo de revisión: 4 minutos.
Meta-observación: Los 3 snippets demuestran perfectamente la calibración:
- Snippet A (utilidad): alta confianza, acepta rápido
- Snippet B (financiero): confianza casi nula, rechaza
- Snippet C (config): confianza media-alta, acepta con edits menores
Si hubieras aplicado el mismo nivel de revisión a los 3, habrías gastado demasiado tiempo en A y C, o muy poco en B.
Resumen
En esta cápsula aprendiste:
- El framework de 3 niveles (Red / Yellow / Green) te da un proceso claro de verificación
- Red Zone (siempre revisar): seguridad, lógica de negocio, datos
- Yellow Zone (frecuentemente): edge cases, error handling, validación, naming
- Green Zone (confiar generalmente): boilerplate, formateo, documentación, patrones estándar
- El framework se aplica en minutos, no horas — prioriza dónde invertir tiempo
- Si dudas, promueve a la zona más alta
- Este framework es el punto de partida — los módulos 2-7 lo expanden
Próximo módulo: Mental Models para AI Code — los frameworks de pensamiento que elevan tu criterio de verificación.
Recursos Adicionales
- OWASP Top 10 - Las 10 vulnerabilidades más comunes — tu checklist de Red Zone para security
- Python Security Best Practices - Prácticas de seguridad específicas para Python
- FastAPI Security Tutorial - Documentación oficial de seguridad en FastAPI
- SQLAlchemy — Preventing SQL Injection - Cómo usar queries parameterizadas correctamente
- Google — Code Review Developer Guide - Framework de code review de Google
- Clean Code — Robert C. Martin - Principios de naming y claridad que aplican a Yellow Zone
Debugging & Code Review with Claude Code — Módulo 1, Cápsula 05 Claude Code Agentic Development Path — Guía #6 de 11