Módulo 4: Code Review de Output AI
Qué Buscar Primero: La Pirámide de Prioridades
Qué Buscar Primero: La Pirámide de Prioridades
Descripción de la cápsula
No puedes revisarlo todo. Ni deberías. Un code review profesional no es una lectura exhaustiva línea por línea — es una búsqueda estratégica de lo que importa. Si solo tienes 5 minutos para revisar un PR generado por Claude Code, ¿qué revisas? Si tienes 30 minutos, ¿cómo distribuyes el tiempo? La respuesta está en la pirámide de prioridades: una jerarquía clara que te dice qué revisar primero, qué revisar después, y qué puedes dejar para el final.
La pirámide no es una sugerencia — es un protocolo. Seguirla te protege de la trampa más común del code review: gastar 20 minutos discutiendo si un nombre de variable es descriptivo mientras un SQL injection pasa desapercibido. Prioridad, no exhaustividad.
La Pirámide de Prioridades
▲
/ \
/ 1 \ ← SEGURIDAD
/ \ Siempre. Sin excepción.
/-------\
/ 2 \ ← LÓGICA DE NEGOCIO
/ \ ¿Hace lo que el negocio necesita?
/-------------\
/ 3 \ ← EDGE CASES
/ \ ¿Qué pasa con inputs inesperados?
/-------------------\
/ 4 \ ← PERFORMANCE
/ \ ¿Es eficiente? (Solo cuando relevante)
/-------------------------\
/ 5 \ ← ESTILO Y CONVENCIONES
/ \ Naming, formato, consistencia
/-------------------------------\
La regla de oro
Nunca revises un nivel inferior sin haber cubierto los superiores. Si encontraste un SQL injection (nivel 1) y no lo has resuelto, no tiene sentido revisar si los nombres de variables son descriptivos (nivel 5). Esto parece obvio escrito, pero en la práctica es sorprendentemente fácil caer en la trampa: el estilo es lo más visible y lo más fácil de opinar.
Nivel 1: Seguridad — Siempre, Sin Excepción
Por qué es el nivel más alto
Un bug de seguridad puede significar datos filtrados, cuentas comprometidas, o multas regulatorias. Un bug de lógica pierde dinero o tiempo. Un bug de estilo no pierde nada. La diferencia de impacto es de órdenes de magnitud.
Qué buscar específicamente
SEGURIDAD — Checklist rápido:
□ ¿Hay secrets hardcoded? (API keys, passwords, tokens, connection strings)
□ ¿Las queries SQL usan parameterized queries?
□ ¿Los endpoints sensibles tienen autenticación y autorización?
□ ¿Los tokens tienen expiración?
□ ¿Los inputs del usuario están sanitizados?
□ ¿Los datos sensibles están encriptados en tránsito y en reposo?
□ ¿Hay rate limiting en endpoints públicos?
□ ¿Los mensajes de error NO exponen información interna?
Ejemplo: Lo que AI genera vs lo que debería ser
Claude Code genera un endpoint de búsqueda:
from fastapi import FastAPI, Query
import sqlite3
app = FastAPI()
@app.get("/users/search")
async def search_users(name: str = Query(...)):
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
cursor.execute(f"SELECT * FROM users WHERE name LIKE '%{name}%'")
results = cursor.fetchall()
conn.close()
return {"users": results}
Lo que un review de seguridad detecta en 30 segundos:
❌ SQL Injection: f-string con input del usuario directo en SQL
Ataque: name = "'; DROP TABLE users; --"
❌ No hay autenticación: cualquiera puede buscar usuarios
❌ Expone todos los campos: SELECT * incluye password_hash, email, etc.
⚠️ Sin rate limiting: un atacante puede enumerar toda la tabla
La versión corregida:
from fastapi import FastAPI, Query, Depends
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel
from typing import List
import sqlite3
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class UserSearchResult(BaseModel):
id: int
name: str
created_at: str
@app.get("/users/search", response_model=List[UserSearchResult])
async def search_users(
name: str = Query(..., min_length=1, max_length=100),
token: str = Depends(oauth2_scheme),
):
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
cursor.execute(
"SELECT id, name, created_at FROM users WHERE name LIKE ?",
(f"%{name}%",),
)
results = cursor.fetchall()
conn.close()
return [
UserSearchResult(id=r[0], name=r[1], created_at=r[2])
for r in results
]
Tiempo recomendado
- Código simple (CRUD, utils): 2-5 minutos
- Código con auth/datos sensibles: 10-15 minutos
- Código financiero/compliance: 20-30 minutos + revisión por segundo par de ojos
Nivel 2: Lógica de Negocio — ¿Hace Lo Que el Negocio Necesita?
Por qué es el segundo nivel
La lógica de negocio incorrecta es el error más caro después de seguridad. Un descuento mal calculado, un filtro invertido, una condición incorrecta — estos errores pasan tests, pasan linters, se ven profesionales, pero hacen algo diferente a lo que el negocio necesita. Y con código AI, este riesgo se multiplica porque AI no entiende tu negocio.
Qué buscar específicamente
LÓGICA DE NEGOCIO — Checklist rápido:
□ ¿Los cálculos son correctos? (precios, taxes, descuentos, totales)
□ ¿Las condiciones son correctas? (>, <, >=, <=, ==, !=)
□ ¿Los filtros incluyen/excluyen lo correcto?
□ ¿Los estados y transiciones son válidos?
□ ¿Las reglas de negocio están completas? (no solo el caso feliz)
□ ¿El código resuelve el problema que pediste, no uno ligeramente diferente?
Ejemplo: El descuento que se ve correcto pero no lo es
Requisito: "Descuento del 15% para compras mayores a $100."
Claude Code genera:
from pydantic import BaseModel
from decimal import Decimal
class Order(BaseModel):
items: list
subtotal: Decimal
def calculate_discount(order: Order) -> Decimal:
if order.subtotal >= Decimal("100"):
discount = order.subtotal * Decimal("0.15")
return order.subtotal - discount
return order.subtotal
Lo que un review de lógica de negocio detecta:
⚠️ Condición: >= 100 vs > 100
"Mayores a $100" → debería ser > 100, no >= 100
Impacto: una orden de exactamente $100 recibe descuento cuando no debería
⚠️ Return: devuelve el total con descuento, no el monto del descuento
El nombre "calculate_discount" sugiere que devuelve el descuento
Pero devuelve el subtotal menos descuento
¿Qué esperaba el código que llama a esta función?
⚠️ No hay límite al descuento
¿Hay un monto máximo de descuento? ¿$500? ¿$1000?
El requisito no lo dice, pero el negocio probablemente tiene uno
Nota cómo ninguno de estos issues es un "bug" en el sentido tradicional. El código compila, ejecuta correctamente, y un test básico pasa. Pero no hace lo que el negocio necesita.
Tiempo recomendado
- Lógica simple (CRUD sin reglas): 1-3 minutos
- Reglas de negocio (descuentos, permisos): 5-15 minutos
- Lógica financiera o compliance: 15-30 minutos
Nivel 3: Edge Cases — ¿Qué Pasa Cuando Las Cosas Van Mal?
Por qué es el tercer nivel
Los edge cases son donde el código pasa de "funciona en demo" a "funciona en producción." AI es notoriamente mala en edge cases porque entrena con ejemplos del "happy path." El código que genera funciona con inputs ideales y se rompe con inputs reales.
Qué buscar específicamente
EDGE CASES — Checklist rápido:
□ ¿Qué pasa con None/null?
□ ¿Qué pasa con listas vacías?
□ ¿Qué pasa con strings vacíos?
□ ¿Qué pasa con números negativos o cero?
□ ¿Qué pasa con el primer y último elemento?
□ ¿Qué pasa con valores muy grandes?
□ ¿Qué pasa si la base de datos está vacía?
□ ¿Qué pasa si el servicio externo no responde?
□ ¿Qué pasa con requests concurrentes?
Ejemplo: La paginación que crashea
Claude Code genera paginación:
from fastapi import FastAPI, Query
from typing import List
from pydantic import BaseModel
app = FastAPI()
class PaginatedResponse(BaseModel):
items: list
total: int
page: int
pages: int
@app.get("/items", response_model=PaginatedResponse)
async def list_items(
page: int = Query(default=1, ge=1),
per_page: int = Query(default=20, ge=1, le=100),
):
all_items = get_all_items_from_db()
total = len(all_items)
pages = total // per_page
start = (page - 1) * per_page
end = start + per_page
items = all_items[start:end]
return PaginatedResponse(
items=items,
total=total,
page=page,
pages=pages,
)
Lo que un review de edge cases detecta:
❌ pages = total // per_page
Si hay 21 items y per_page=20 → pages = 1 (debería ser 2)
Fix: pages = (total + per_page - 1) // per_page o math.ceil(total / per_page)
❌ page > pages
Si pides page=5 pero solo hay 2 páginas → devuelve items vacíos sin error
¿Debería devolver 404? ¿O una lista vacía con metadata correcta?
❌ Base de datos vacía
total = 0, pages = 0, page = 1 → page > pages pero no hay error
⚠️ all_items = get_all_items_from_db()
Carga TODOS los items en memoria antes de paginar
Con 1M de items, esto crashea por memoria
Debería paginar en la query SQL, no en Python
Tiempo recomendado
- Código sin inputs de usuario: 1-2 minutos
- Código con inputs validados: 3-5 minutos
- Código con inputs libres/complejos: 5-10 minutos
Nivel 4: Performance — ¿Es Eficiente? (Cuando Relevante)
Por qué es el cuarto nivel
Performance importa, pero importa menos que seguridad, lógica de negocio, y edge cases. Un endpoint lento es un inconveniente. Un endpoint inseguro es un desastre. Revisa performance solo cuando es relevante — no en cada PR.
Cuándo es relevante
Revisar performance cuando:
✅ El endpoint maneja grandes volúmenes de datos
✅ El código se ejecuta en un loop o batch
✅ Hay queries a base de datos en loops (N+1)
✅ El código se ejecuta en cada request (middleware)
✅ Hay procesamiento de archivos grandes
✅ El sistema tiene requisitos de latencia definidos
NO revisar performance cuando:
❌ Es un endpoint de admin usado 3 veces al día
❌ Es un script que se ejecuta una vez
❌ Es un prototipo/MVP
❌ No hay datos de que performance sea un problema
Ejemplo: El N+1 clásico que AI genera
Claude Code genera un endpoint de usuarios con sus órdenes:
from fastapi import FastAPI
from typing import List
app = FastAPI()
@app.get("/users-with-orders")
async def get_users_with_orders():
users = db.query("SELECT * FROM users")
result = []
for user in users:
orders = db.query(
"SELECT * FROM orders WHERE user_id = ?",
(user["id"],),
)
result.append({
"user": user,
"orders": orders,
"total_orders": len(orders),
})
return result
Lo que un review de performance detecta:
❌ N+1 Query Problem
Si hay 100 usuarios → 1 query (users) + 100 queries (orders) = 101 queries
Si hay 10,000 usuarios → 10,001 queries
Fix con JOIN:
SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id = o.user_id
O con 2 queries:
1. SELECT * FROM users
2. SELECT * FROM orders WHERE user_id IN (1, 2, 3, ...)
⚠️ SELECT *
Trae todas las columnas aunque no se necesiten
Fix: especificar columnas necesarias
⚠️ No hay paginación
Si hay 10,000 usuarios con 50 órdenes cada uno → 500,000 filas en memoria
Tiempo recomendado
- Código de baja frecuencia: 0 minutos (skip)
- Código de alta frecuencia simple: 2-3 minutos
- Código con queries/loops complejos: 5-10 minutos
Nivel 5: Estilo y Convenciones — Lo Menos Importante
Por qué es el último nivel
El estilo no afecta la funcionalidad, seguridad, ni performance del código. Un nombre de variable malo no causa bugs (excepto en casos extremos de confusión). Revisar estilo es útil para mantenibilidad, pero nunca debería consumir más del 10% de tu tiempo de review.
Lo que AI generalmente hace bien
AI es consistentemente buena en:
✅ Formateo (indentación, espacios)
✅ Convenciones de estilo del lenguaje
✅ Docstrings y type hints
✅ Estructura de archivos
✅ Orden de imports
Lo que vale la pena revisar
Solo revisa estilo cuando:
□ Los nombres son activamente confusos (no solo "no ideales")
□ El código viola convenciones del proyecto (no del lenguaje)
□ La estructura hace difícil entender el flujo
□ Hay inconsistencia obvia con el resto del codebase
Ejemplo: Dónde el estilo sí importa
def process(d, f=True):
r = []
for i in d:
if f:
x = transform_a(i)
else:
x = transform_b(i)
if x > 0:
r.append(x)
return r
Aquí sí vale la pena comentar: d, f, r, x, i son nombres inaceptables. Pero esto es raro en código AI — Claude Code generalmente usa nombres descriptivos. Si lo ves, probablemente es una señal de que algo salió mal con el prompt.
Tiempo recomendado
- En la mayoría de casos: 0-1 minuto (revisión visual rápida)
- Si algo se ve confuso: 2-3 minutos máximo
La Pirámide en Acción: Distribución de Tiempo
Si tienes 5 minutos
5 minutos disponibles:
├── 4 min → Seguridad (nivel 1)
│ ├── ¿Secrets hardcoded?
│ ├── ¿SQL injection?
│ └── ¿Auth en endpoints sensibles?
└── 1 min → Lógica de negocio (nivel 2)
└── ¿El código hace lo que pediste? (lectura rápida)
Si tienes 15 minutos
15 minutos disponibles:
├── 5 min → Seguridad (nivel 1)
│ └── Checklist completo de seguridad
├── 5 min → Lógica de negocio (nivel 2)
│ └── Verificar condiciones, cálculos, filtros
├── 3 min → Edge cases (nivel 3)
│ └── null, vacío, límites
└── 2 min → Visual rápido (niveles 4-5)
└── ¿Algo se ve raro?
Si tienes 30 minutos
30 minutos disponibles:
├── 8 min → Seguridad (nivel 1)
│ └── Checklist completo + verificar contra OWASP
├── 10 min → Lógica de negocio (nivel 2)
│ └── Trazar cada regla de negocio en el código
├── 7 min → Edge cases (nivel 3)
│ └── Todos los inputs, todos los escenarios de error
├── 3 min → Performance (nivel 4)
│ └── N+1, queries en loops, carga en memoria
└── 2 min → Estilo (nivel 5)
└── Naming, convenciones del proyecto
Si tienes 60+ minutos (review exhaustivo)
Si tienes más de 60 minutos, probablemente el PR es demasiado grande. Pide que lo dividan en PRs más pequeños. PRs de más de 200-300 líneas tienen review quality significativamente menor.
Por Qué Este Orden y No Otro
"¿Por qué no poner performance antes de edge cases?"
Porque un edge case no manejado puede causar un crash en producción. Un problema de performance causa lentitud. Un crash es peor que lentitud.
"¿Por qué lógica de negocio antes de edge cases?"
Porque si la lógica base es incorrecta, los edge cases no importan. No tiene sentido verificar qué pasa cuando quantity=0 si el descuento ya está mal calculado para quantity=15.
"¿Por qué seguridad siempre es #1?"
Porque los otros niveles afectan tu aplicación. Seguridad afecta a tus usuarios. Un descuento mal calculado te cuesta dinero a ti. Un data breach le cuesta privacidad a tus usuarios y confianza a tu empresa.
"¿Nunca debo saltar un nivel?"
Puedes saltar hacia abajo (no revisar estilo o performance), pero nunca hacia arriba (no saltar seguridad para ir directo a edge cases). La pirámide es flexible en la base y rígida en la cima.
Conexión con Proyecto
Cómo se conecta con el proyecto integrador (Módulo 8)
En el proyecto integrador, los 15-20 problemas plantados están distribuidos en la pirámide:
| Nivel | Problemas esperados | Severidad |
|---|---|---|
| Seguridad | 3-4 | Critical/High |
| Lógica de negocio | 3-4 | High |
| Edge cases | 3-4 | Medium/High |
| Performance | 2-3 | Medium |
| Estilo | 0-1 | Low |
Si sigues la pirámide, encontrarás los issues de mayor severidad primero. Si empiezas por abajo, podrías gastar todo tu tiempo en issues de estilo y perder los critical.
Troubleshooting
Problema 1: "La seguridad se siente obvia — ¿realmente necesito un checklist?"
Causa: Los issues de seguridad que AI genera no son siempre obvios. Un f-string en un SQL es obvio. Un fallback key en un os.getenv("SECRET", "default") no lo es.
Solución: El checklist no es para detectar lo obvio — es para no olvidar lo sutil. Usa el checklist especialmente cuando el código "se ve limpio." Ahí es donde los issues de seguridad se esconden.
Problema 2: "No sé suficiente de seguridad para hacer el review de nivel 1"
Causa: Seguridad es un tema profundo y es normal no dominarlo todo. Solución: Los 8 items del checklist rápido de nivel 1 cubren el 80% de issues comunes. No necesitas ser experto en seguridad — necesitas verificar esos 8 puntos. Si algo no pasa, investiga o pide ayuda. Eso es mejor que no revisar.
Problema 3: "El PR tiene 500 líneas y solo tengo 15 minutos"
Causa: PR demasiado grande para el tiempo disponible. Solución: Dos opciones: (1) Pide que dividan el PR. (2) Si no puedes, aplica la distribución de 15 minutos: seguridad primero en los archivos más críticos (auth, database, payments), lógica de negocio en los endpoints principales, edge cases rápido. Documenta que hiciste un review parcial y qué áreas no cubriste.
Problema 4: "No estoy seguro de qué nivel corresponde un issue"
Causa: Algunos issues tocan múltiples niveles. Solución: Clasifica por el impacto más alto. Un input no validado que puede causar SQL injection es nivel 1 (seguridad), no nivel 3 (edge case). Un cálculo que falla con valores negativos podría ser nivel 2 (lógica) o nivel 3 (edge case) — clasifícalo como nivel 2 si el negocio maneja valores negativos regularmente.
Ejercicios
Ejercicio 1: Clasificar issues por nivel (Fácil)
Clasifica cada issue en el nivel correcto de la pirámide (1-5):
- Una función se llama
get_userpero devuelve una lista de usuarios - Un endpoint de login no tiene rate limiting
- Una función calcula el IVA al 16% pero el país tiene IVA del 21%
- Un query SELECT trae todas las columnas cuando solo necesita 2
- Un endpoint acepta
page=-1y crashea - Una API key de Stripe está en el código fuente
- El endpoint
/admin/delete-allno requiere autenticación - Un loop anidado tiene complejidad O(n²) en un endpoint que se llama 1 vez al mes
Ver solución
-
Nivel 5 (Estilo) — Naming confuso pero no causa bug funcional. Un developer que lea el código se confundirá, pero el programa funciona.
-
Nivel 1 (Seguridad) — Rate limiting en login previene ataques de brute force. Sin él, un atacante puede probar miles de passwords por segundo.
-
Nivel 2 (Lógica de negocio) — El IVA incorrecto es un error de negocio. El código "funciona" pero calcula mal. Impacto financiero y legal.
-
Nivel 4 (Performance) — SELECT * vs SELECT específico. Funciona correctamente pero es ineficiente. Solo relevante si la tabla tiene muchas columnas o alto volumen.
-
Nivel 3 (Edge case) — Input inesperado (negativo) causa crash. El happy path funciona, pero un input inusual rompe el sistema.
-
Nivel 1 (Seguridad) — Secret hardcoded. Cualquiera con acceso al repo tiene la API key. Crítico.
-
Nivel 1 (Seguridad) — Endpoint de admin sin auth. Cualquiera puede borrar todos los datos. Crítico.
-
Nivel 4 (Performance) — O(n²) suena mal, pero si se llama 1 vez al mes y
nes pequeño, no importa. Contexto determina severidad.
Nota: Los issues 2, 6, y 7 son todos nivel 1. Esto es intencional — la seguridad tiene muchas formas y todas son prioridad máxima.
Ejercicio 2: Distribución de tiempo (Medio)
Tienes 20 minutos para revisar este PR generado por Claude Code. Es un endpoint de e-commerce que procesa órdenes. Planifica tu distribución de tiempo antes de empezar el review.
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel, Field
from typing import List, Optional
from decimal import Decimal
from datetime import datetime
import uuid
import os
app = FastAPI()
SHIPPING_API_KEY = os.getenv("SHIPPING_KEY", "shp_test_12345")
TAX_RATE = 0.08
class OrderItem(BaseModel):
product_id: str
quantity: int = Field(..., ge=1)
unit_price: Decimal
class CreateOrderRequest(BaseModel):
customer_id: str
items: List[OrderItem]
shipping_address: str
coupon_code: Optional[str] = None
class OrderResponse(BaseModel):
order_id: str
subtotal: Decimal
tax: Decimal
shipping: Decimal
total: Decimal
status: str
created_at: datetime
@app.post("/orders", response_model=OrderResponse)
async def create_order(order: CreateOrderRequest):
subtotal = sum(
item.unit_price * item.quantity for item in order.items
)
discount = Decimal("0")
if order.coupon_code:
discount = get_coupon_discount(order.coupon_code, subtotal)
taxable = subtotal - discount
tax = taxable * Decimal(str(TAX_RATE))
shipping = calculate_shipping(
order.shipping_address, len(order.items)
)
total = taxable + tax + shipping
order_record = {
"order_id": str(uuid.uuid4()),
"customer_id": order.customer_id,
"items": [item.model_dump() for item in order.items],
"subtotal": subtotal,
"discount": discount,
"tax": tax,
"shipping": shipping,
"total": total,
"status": "pending",
"created_at": datetime.utcnow(),
}
save_order(order_record)
return OrderResponse(**order_record)
Describe: (1) tu plan de distribución de tiempo, (2) qué buscas en cada bloque, (3) qué issues encontraste.
Ver solución
Plan de distribución (20 minutos):
├── 7 min → Seguridad (nivel 1)
├── 7 min → Lógica de negocio (nivel 2)
├── 4 min → Edge cases (nivel 3)
└── 2 min → Performance + estilo (niveles 4-5)
Seguridad (7 min):
⚠️ SHIPPING_API_KEY = os.getenv("SHIPPING_KEY", "shp_test_12345")
Fallback con key de test. Si no se configura en prod, usa key de test.
→ Debería fallar si no está configurada, no usar fallback.
⚠️ No hay autenticación en el endpoint
Cualquiera puede crear órdenes. ¿Dónde está el auth?
⚠️ No hay validación del customer_id
¿El customer existe? ¿Puede crear órdenes?
✅ No hay SQL injection (no hay SQL directo visible)
✅ No hay secrets en el response
Lógica de negocio (7 min):
⚠️ TAX_RATE = 0.08 (float, no Decimal)
Luego hace Decimal(str(TAX_RATE)) — funciona, pero
¿por qué no definirlo como Decimal desde el inicio?
⚠️ Tax rate hardcoded a 8%
¿Varía por estado/país? En e-commerce real, sí.
⚠️ Discount se resta del subtotal antes de tax
¿Es correcto? Depende de la jurisdicción.
En algunos lugares, tax se calcula sobre subtotal ANTES del descuento.
⚠️ get_coupon_discount y calculate_shipping no están definidas
¿Existen? ¿Qué devuelven si el cupón es inválido?
⚠️ No hay validación de stock
¿Los productos existen? ¿Hay stock suficiente?
Edge cases (4 min):
⚠️ items puede ser lista vacía
List[OrderItem] permite [] — una orden sin items
→ Agregar min_length=1 o validación
⚠️ unit_price no tiene mínimo
Puede ser 0 o negativo (Decimal permite negativos)
→ Agregar validación gt=0
⚠️ coupon_code inválido
Si get_coupon_discount lanza exception, no hay try/except
⚠️ ¿Qué pasa si shipping_address es un string vacío?
Performance/Estilo (2 min):
✅ Usa Decimal para montos (bien)
✅ Usa uuid4 para order_id (bien)
✅ Nombres descriptivos
⚠️ Cálculo in-line en el endpoint — podría moverse a un service
Resumen de findings:
| # | Issue | Nivel | Severidad |
|---|---|---|---|
| 1 | Fallback key de shipping | 1 (Seg) | High |
| 2 | Sin auth en endpoint | 1 (Seg) | High |
| 3 | Sin validación de customer | 1 (Seg) | Medium |
| 4 | Tax rate hardcoded | 2 (Lógica) | Medium |
| 5 | Discount antes de tax (¿correcto?) | 2 (Lógica) | Medium |
| 6 | Sin validación de stock | 2 (Lógica) | High |
| 7 | Lista de items vacía | 3 (Edge) | Medium |
| 8 | unit_price sin mínimo | 3 (Edge) | Medium |
Ejercicio 3: Priorizar en código AI vs humano (Medio)
Para cada snippet, identifica si el issue principal es uno que encontrarías igual en código humano o si es específico de código AI. Explica por qué.
Snippet A:
from fastapi import FastAPI
from sklearn.metrics import roc_auc_multiclass
app = FastAPI()
@app.post("/predict")
async def predict(data: dict):
score = roc_auc_multiclass(data["y_true"], data["y_pred"])
return {"score": score}
Snippet B:
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.delete("/users/{user_id}")
async def delete_user(user_id: int):
user = get_user(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
delete_from_db(user_id)
delete_user_files(user_id)
send_deletion_email(user["email"])
return {"status": "deleted"}
Snippet C:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
import abc
import dataclasses
from enum import Enum, IntFlag
app = FastAPI()
class TaskPriority(IntFlag):
LOW = 1
MEDIUM = 2
HIGH = 4
URGENT = 8
CRITICAL = 16
class TaskStatus(str, Enum):
BACKLOG = "backlog"
TODO = "todo"
IN_PROGRESS = "in_progress"
IN_REVIEW = "in_review"
QA = "qa"
STAGING = "staging"
DONE = "done"
ARCHIVED = "archived"
class BaseTaskProcessor(abc.ABC):
@abc.abstractmethod
def process(self, task): ...
@abc.abstractmethod
def validate(self, task): ...
@dataclasses.dataclass
class TaskMetadata:
source: str
processor: str
version: int
class TaskCreate(BaseModel):
title: str
priority: TaskPriority = TaskPriority.MEDIUM
status: TaskStatus = TaskStatus.TODO
@app.post("/tasks")
async def create_task(task: TaskCreate):
return {"id": 1, "title": task.title}
Ver solución
Snippet A — Issue ESPECÍFICO de AI:
roc_auc_multiclass no existe en scikit-learn. La función real es roc_auc_score con multi_class como parámetro. Esto es una hallucination — AI inventó un nombre de función que suena real pero no existe.
Un developer humano no inventaría un nombre de función de una librería que usa. Podría equivocarse en los parámetros, pero no inventaría la función.
Prioridad pirámide: Nivel 1 (Seguridad/Funcionalidad) — el código no funciona en absoluto. Es un ImportError.
Snippet B — Issue que encontrarías en CÓDIGO HUMANO también:
El problema es la falta de atomicidad. Si delete_user_files falla, el usuario ya fue borrado de la DB pero sus archivos persisten. Si send_deletion_email falla, el usuario fue borrado pero no fue notificado.
Este es un error de diseño clásico que tanto humanos como AI cometen. No es específico de AI.
Prioridad pirámide: Nivel 2 (Lógica) — la operación no es atómica, puede dejar el sistema en estado inconsistente.
Snippet C — Issue ESPECÍFICO de AI:
Sobre-ingeniería masiva. El prompt probablemente fue "crea un endpoint para crear tareas." El código incluye:
IntFlagpara prioridades (para un CRUD simple,str Enumbasta)- 8 estados de tarea (para un endpoint que solo crea, no procesa)
abc.ABCcon clase abstracta que nadie va a implementardataclasses.dataclasspara metadata que nunca se usa- Mezcla de Pydantic, dataclasses, y abc en el mismo archivo
Un developer humano no haría esto para un CRUD simple. AI tiende a "demostrar conocimiento" agregando patrones que no se necesitan.
Prioridad pirámide: Nivel 5 (Estilo) — el código funciona, pero es innecesariamente complejo. Sin embargo, es un red flag de AI que sugiere que debes revisar si hay más sobre-ingeniería en otros archivos.
Ejercicio 4: Tu propia pirámide (Difícil)
Toma un PR real de tu trabajo (o genera código con Claude Code) y aplica la pirámide:
- Establece cuánto tiempo tienes para el review
- Distribuye el tiempo según la pirámide
- Ejecuta el review nivel por nivel
- Documenta: qué encontraste, en qué nivel, cuánto tiempo real usaste
Ver guía de evaluación
Tu review debería demostrar:
- ✅ Distribución de tiempo documentada ANTES de empezar
- ✅ Nivel 1 revisado antes que nivel 2
- ✅ Más del 50% del tiempo en niveles 1-2
- ✅ Issues clasificados por nivel
- ✅ Tiempo real vs planificado documentado
No debería:
- ❌ Empezar por naming o estilo
- ❌ Distribuir tiempo uniformemente entre niveles
- ❌ Documentar issues sin nivel de pirámide
- ❌ Gastar más de 15% del tiempo en nivel 5
Ejercicio 5: Pirámide bajo presión (Difícil)
Escenario: Es viernes a las 5:45pm. Un PR generado por Claude Code necesita merge antes del deploy de las 6pm. Solo tienes 5 minutos. El PR modifica el endpoint de checkout de tu e-commerce.
¿Qué haces? Documenta tu plan de 5 minutos y justifica cada decisión.
Ver solución
Plan de 5 minutos para PR de checkout:
Minuto 0-1: Scope rápido
├── ¿Cuántos archivos? ¿Cuántas líneas?
├── ¿Qué archivos son críticos? (payments, auth)
└── ¿Hay cambios en modelos de datos?
Minuto 1-4: Seguridad del checkout
├── ¿Hay secrets nuevos o modificados?
├── ¿Los endpoints de pago mantienen auth?
├── ¿Hay input del usuario que llega a queries?
├── ¿Los montos se calculan en el servidor (no del cliente)?
└── ¿Se valida el payment method antes de cobrar?
Minuto 4-5: Lógica de negocio crítica
├── ¿El total se calcula correctamente?
├── ¿Los descuentos/cupones se aplican bien?
└── ¿El estado de la orden se maneja correctamente?
Justificación:
- Solo niveles 1-2. No hay tiempo para edge cases, performance, ni estilo.
- Dentro de seguridad, priorizo lo que aplica a checkout: payments, auth, montos.
- Si encuentro algo en nivel 1 → no apruebo. Retraso el deploy.
- Si nivel 1 pasa → apruebo con nota: "Review de seguridad y lógica básica OK. Review completo pendiente para lunes."
- Nunca apruebo un PR de checkout sin revisar seguridad, sin importar la presión.
Lo que NO hago:
- ❌ Revisar nombres de variables
- ❌ Verificar formato de código
- ❌ Preocuparme por performance
- ❌ Leer los tests (no hay tiempo)
- ❌ Aprobar sin revisar seguridad "porque ya son las 6"
Resumen
En esta cápsula aprendiste:
- La pirámide de prioridades define qué revisar primero: Seguridad → Lógica → Edge Cases → Performance → Estilo
- Nunca revises un nivel inferior sin cubrir los superiores — un SQL injection importa más que un nombre de variable
- Seguridad es siempre #1 porque afecta a tus usuarios, no solo a tu aplicación
- Lógica de negocio es #2 porque AI no entiende tu negocio — solo tú puedes verificar que el código hace lo que necesitas
- Edge cases son #3 porque la diferencia entre "funciona en demo" y "funciona en producción" está aquí
- Performance es #4 y solo se revisa cuando es relevante — no en cada PR
- Estilo es #5 y nunca debería consumir más del 10% de tu tiempo
- La distribución de tiempo cambia según cuánto tiempo tengas: 5 min (solo niveles 1-2), 15 min (niveles 1-3), 30 min (todos)
- La pirámide es flexible en la base y rígida en la cima — puedes saltar hacia abajo pero nunca hacia arriba
Próxima cápsula: Checklist de Code Review AI — construir tu checklist profesional de 15+ items.
Recursos Adicionales
- OWASP Top 10 - Las 10 vulnerabilidades más comunes — tu referencia para Nivel 1
- Google — Code Review Speed - Cómo Google balancea velocidad y rigor en code review
- Stripe — Security Best Practices - Checklist de seguridad para integraciones de pago
- FastAPI — Security Tutorial - Implementación correcta de auth en FastAPI
- CWE/SANS Top 25 - Los 25 errores de software más peligrosos
- Anthropic — Claude Code Documentation - Documentación oficial de Claude Code
Debugging & Code Review with Claude Code — Módulo 4, Cápsula 02 Claude Code Agentic Development Path — Guía #6 de 11