Módulo 4: Code Review de Output AI
Checklist Profesional de Code Review para AI Code
Checklist Profesional de Code Review para AI Code
Descripción de la cápsula
En la cápsula anterior aprendiste a priorizar: la pirámide te dice qué revisar primero. Ahora necesitas saber qué revisar exactamente en cada nivel. Esta cápsula construye tu checklist profesional de code review para código generado por AI — el artefacto más importante de todo el módulo.
Este no es un checklist genérico de "buenas prácticas." Cada item es específico, verificable, y accionable. No dice "revisar el código" — dice "verificar que cada import existe ejecutando pip show <paquete> o buscando en la documentación oficial." No dice "revisar seguridad" — dice "confirmar que no hay string concatenation en queries SQL."
El checklist tiene 20 items organizados en 5 categorías, alineados con la pirámide de prioridades. Es una herramienta viva: la usas hoy, la adaptas mañana, la mejoras con cada review. Al final de tu carrera, este checklist será diferente — pero siempre tendrá como base lo que construyes aquí.
Principios del Checklist
Antes de ver los items, entiende los principios que los guían:
1. Específico y verificable
❌ Malo: "Revisar la seguridad del código"
✅ Bueno: "Verificar que no hay API keys, passwords, o tokens
hardcoded en el código fuente"
❌ Malo: "Verificar que el código funciona"
✅ Bueno: "Ejecutar el endpoint con un input válido y uno inválido
para verificar happy path y error handling"
2. Accionable en menos de 5 minutos
Cada item del checklist debe ser verificable en 1-5 minutos. Si un item toma más, necesita dividirse en sub-items.
3. Priorizado por impacto
Los primeros items del checklist son los más importantes. Si solo revisas los primeros 5, cubres los riesgos más altos.
4. Adaptado a AI code
Cada item existe porque AI comete este error con frecuencia. Items que no son relevantes para AI (como "verificar que no hay merge conflicts") no están incluidos.
El Checklist: 20 Items en 5 Categorías
Categoría 1: Seguridad (Items 1-5)
Estos se revisan siempre, sin excepción. No importa el tamaño del PR, el nivel de urgencia, o tu confianza en el código.
Item 1: No hay secrets hardcoded en el código
QUÉ VERIFICAR:
- API keys (Stripe, SendGrid, AWS, etc.)
- Passwords y tokens de acceso
- Connection strings de bases de datos
- Claves de encripción
CÓMO VERIFICAR:
- Buscar strings sospechosos: "sk_", "Bearer ", "password",
"secret", "key", "token"
- Verificar que os.getenv() NO tiene fallback con valores reales
- Revisar archivos de configuración (.env.example, config.py)
EJEMPLO DE FALLA (AI genera frecuentemente):
# AI genera esto con frecuencia
SECRET_KEY = os.getenv("SECRET_KEY", "mi-clave-secreta-de-desarrollo")
DATABASE_URL = "postgresql://admin:password123@localhost:5432/mydb"
STRIPE_KEY = "sk_live_abc123def456"
# Versión correcta
SECRET_KEY = os.environ["SECRET_KEY"] # Falla si no existe — bien
DATABASE_URL = os.environ["DATABASE_URL"]
STRIPE_KEY = os.environ["STRIPE_API_KEY"]
SEVERIDAD SI FALLA: Critical
TIEMPO ESTIMADO: 1-2 minutos
Item 2: Queries SQL usan parameterized queries (no string concatenation)
QUÉ VERIFICAR:
- Cualquier query SQL construida con f-strings, .format(), o + concatenation
- Inputs del usuario que llegan directamente a queries
- ORM queries que permiten raw SQL
CÓMO VERIFICAR:
- Buscar: f"SELECT, f"INSERT, f"UPDATE, f"DELETE
- Buscar: .format( en contexto de queries
- Buscar: cursor.execute( con strings concatenadas
EJEMPLO DE FALLA:
# SQL Injection — AI genera esto regularmente con sqlite3
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")
# Con .format() — igual de peligroso
query = "SELECT * FROM users WHERE id = {}".format(user_id)
cursor.execute(query)
# Correcto — parameterized query
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))
# Con SQLAlchemy
stmt = select(User).where(User.name == name)
SEVERIDAD SI FALLA: Critical
TIEMPO ESTIMADO: 2-3 minutos
Item 3: Endpoints sensibles tienen autenticación y autorización
QUÉ VERIFICAR:
- Endpoints que modifican datos (POST, PUT, PATCH, DELETE)
- Endpoints que exponen datos sensibles
- Endpoints de admin
- Que auth !== solo autenticación — también autorización
(¿el usuario PUEDE hacer esta acción?)
CÓMO VERIFICAR:
- Revisar cada endpoint: ¿tiene Depends() con auth?
- Verificar que los roles/permisos son correctos
- Buscar endpoints que acceden a datos de otros usuarios
EJEMPLO DE FALLA:
# Sin auth — cualquiera puede borrar usuarios
@app.delete("/users/{user_id}")
async def delete_user(user_id: int):
db.execute("DELETE FROM users WHERE id = ?", (user_id,))
return {"status": "deleted"}
# Con auth y autorización
@app.delete("/users/{user_id}")
async def delete_user(
user_id: int,
current_user: User = Depends(get_current_admin_user),
):
if current_user.role != "admin":
raise HTTPException(status_code=403, detail="Not authorized")
db.execute("DELETE FROM users WHERE id = ?", (user_id,))
return {"status": "deleted"}
SEVERIDAD SI FALLA: Critical
TIEMPO ESTIMADO: 2-3 minutos
Item 4: Inputs del usuario están validados y sanitizados
QUÉ VERIFICAR:
- Inputs que llegan como query params, path params, o body
- Tipos correctos (int vs str, etc.)
- Rangos razonables (age > 0, price >= 0, page >= 1)
- Longitud máxima en strings
- Formatos esperados (email, URL, fecha)
CÓMO VERIFICAR:
- Revisar modelos Pydantic: ¿tienen Field() con constraints?
- Revisar Query() y Path(): ¿tienen ge, le, min_length, max_length?
- ¿Hay inputs que llegan como `dict` o `Any`? — Red flag
EJEMPLO DE FALLA:
# Sin validación — acepta cualquier cosa
@app.post("/products")
async def create_product(data: dict):
save_product(data)
return data
# Con validación completa
class ProductCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=200)
price: Decimal = Field(..., gt=0, le=999999)
category: str = Field(..., min_length=1, max_length=50)
description: Optional[str] = Field(None, max_length=5000)
@app.post("/products", response_model=ProductResponse)
async def create_product(product: ProductCreate):
return save_product(product)
SEVERIDAD SI FALLA: High
TIEMPO ESTIMADO: 2-3 minutos
Item 5: Mensajes de error no exponen información interna
QUÉ VERIFICAR:
- Stack traces en responses HTTP
- Rutas del sistema de archivos
- Nombres de tablas/columnas de la DB
- Versiones de software
- Detalles de configuración
CÓMO VERIFICAR:
- Buscar: str(e), str(exc), repr(e) en error handlers
- Buscar: detail= con variables que podrían exponer info
- Verificar que los except blocks usan mensajes genéricos
EJEMPLO DE FALLA:
# Expone información interna
@app.get("/users/{user_id}")
async def get_user(user_id: int):
try:
user = db.query(f"SELECT * FROM users WHERE id = {user_id}")
return user
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Podría exponer: "relation 'users' does not exist"
# o rutas del filesystem
# Mensaje genérico para el cliente, detalle en logs
import logging
logger = logging.getLogger(__name__)
@app.get("/users/{user_id}")
async def get_user(user_id: int):
try:
user = db.get_user(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
except HTTPException:
raise
except Exception as e:
logger.error(f"Error fetching user {user_id}: {e}")
raise HTTPException(
status_code=500,
detail="Internal server error",
)
SEVERIDAD SI FALLA: Medium-High
TIEMPO ESTIMADO: 1-2 minutos
Categoría 2: Lógica de Negocio (Items 6-9)
Estos se revisan siempre que el código implemente reglas de negocio. Si es puro boilerplate sin lógica, puedes pasar rápido.
Item 6: El código resuelve el problema que pediste (no uno diferente)
QUÉ VERIFICAR:
- ¿El endpoint hace lo que especificaste en el prompt?
- ¿Hay funcionalidad que nadie pidió?
- ¿Falta funcionalidad que sí pediste?
- ¿El approach es el que esperabas?
CÓMO VERIFICAR:
- Comparar tu prompt/requisito original con el código generado
- Listar: qué pediste, qué recibiste, qué sobra, qué falta
ESTE ITEM ES ESPECÍFICO DE AI:
Un developer humano rara vez resuelve un problema diferente al pedido.
AI lo hace frecuentemente — resuelve un problema SIMILAR pero no
idéntico. El código se ve correcto porque resuelve UN problema,
solo que no es el tuyo.
SEVERIDAD SI FALLA: High
TIEMPO ESTIMADO: 2-3 minutos
Item 7: Cálculos y condiciones son correctos
QUÉ VERIFICAR:
- Operadores de comparación: > vs >=, < vs <=, == vs !=
- Cálculos matemáticos: orden de operaciones, redondeo, precisión
- Tipo de datos para dinero: Decimal, no float
- Fórmulas de negocio: descuentos, taxes, comisiones, tarifas
CÓMO VERIFICAR:
- Trazar manualmente con 2-3 valores de ejemplo
- Prestar atención especial a boundary values (límite exacto)
- Verificar contra la regla de negocio documentada
EJEMPLO DE FALLA:
# "Descuento del 10% para compras mayores a $100"
# AI genera: (¿es > o >= ?)
def apply_discount(total: Decimal) -> Decimal:
if total >= Decimal("100"): # >= cuando debería ser >
return total * Decimal("0.90")
return total
# Verificación manual:
# total = 100 → con >=, aplica descuento (¿correcto? "mayores a" = >)
# total = 100.01 → con >, aplica descuento (correcto)
# total = 99.99 → no aplica (correcto en ambos)
SEVERIDAD SI FALLA: High
TIEMPO ESTIMADO: 3-5 minutos (requiere pensar con valores reales)
Item 8: Estados y transiciones son válidos
QUÉ VERIFICAR:
- ¿Los estados posibles son los correctos?
- ¿Las transiciones de estado son válidas?
(e.g., no puedes ir de "cancelled" a "active")
- ¿Hay estados faltantes?
- ¿Hay transiciones que no deberían existir?
CÓMO VERIFICAR:
- Dibujar el diagrama de estados mentalmente (o en papel)
- Verificar que el código no permite transiciones inválidas
- Buscar: dónde se cambia el status y qué validaciones hay
EJEMPLO DE FALLA:
# AI permite cualquier transición — no hay validación
@app.patch("/orders/{order_id}/status")
async def update_order_status(order_id: str, new_status: str):
order = get_order(order_id)
order.status = new_status # ¿De "delivered" a "pending"? Sí, permite
save_order(order)
return order
# Transiciones validadas
VALID_TRANSITIONS = {
"pending": ["confirmed", "cancelled"],
"confirmed": ["processing", "cancelled"],
"processing": ["shipped", "cancelled"],
"shipped": ["delivered"],
"delivered": ["returned"],
"cancelled": [],
"returned": [],
}
@app.patch("/orders/{order_id}/status")
async def update_order_status(order_id: str, new_status: OrderStatus):
order = get_order(order_id)
valid_next = VALID_TRANSITIONS.get(order.status, [])
if new_status.value not in valid_next:
raise HTTPException(
status_code=400,
detail=f"Cannot transition from {order.status} to {new_status.value}",
)
order.status = new_status.value
save_order(order)
return order
SEVERIDAD SI FALLA: High
TIEMPO ESTIMADO: 3-5 minutos
Item 9: Operaciones que deben ser atómicas lo son
QUÉ VERIFICAR:
- Secuencias de operaciones que deben completarse todas o ninguna
- Transferencias de dinero (débito + crédito)
- Operaciones de inventario (reservar + cobrar)
- Multi-step workflows (crear usuario + enviar email + asignar rol)
CÓMO VERIFICAR:
- Para cada secuencia: ¿qué pasa si el paso N falla?
- ¿Hay rollback? ¿Hay transacciones de DB?
- ¿Hay idempotency keys para prevenir duplicados?
EJEMPLO DE FALLA:
# No atómico — si save_transfer falla, el sender ya perdió dinero
async def transfer(sender_id: str, receiver_id: str, amount: Decimal):
sender = get_account(sender_id)
sender.balance -= amount
save_account(sender) # Si esto pasa...
receiver = get_account(receiver_id)
receiver.balance += amount
save_account(receiver) # ...pero esto falla → dinero desapareció
save_transfer(sender_id, receiver_id, amount)
# Atómico con transacción de DB
async def transfer(sender_id: str, receiver_id: str, amount: Decimal):
async with db.transaction():
sender = await get_account_for_update(sender_id)
if sender.balance < amount:
raise InsufficientFundsError()
sender.balance -= amount
receiver = await get_account_for_update(receiver_id)
receiver.balance += amount
await save_transfer(sender_id, receiver_id, amount)
SEVERIDAD SI FALLA: High-Critical (dependiendo del contexto)
TIEMPO ESTIMADO: 2-3 minutos
Categoría 3: Edge Cases (Items 10-13)
Estos se revisan frecuentemente, especialmente en código que recibe input de usuarios o sistemas externos.
Item 10: Maneja null/None, vacío, y valores por defecto
QUÉ VERIFICAR:
- ¿Qué pasa si un campo Optional es None?
- ¿Qué pasa con listas vacías?
- ¿Qué pasa con strings vacíos ("" vs None)?
- ¿Los defaults son razonables?
CÓMO VERIFICAR:
- Para cada input Optional: seguir el flujo con valor None
- Para cada lista: ¿funciona con []?
- Buscar: accesos a .attribute sin check de None
EJEMPLO DE FALLA:
# Crashea si user.address es None
def get_shipping_zone(user: User) -> str:
return user.address.state # AttributeError si address es None
# Maneja None correctamente
def get_shipping_zone(user: User) -> str:
if not user.address:
raise ValueError("User has no shipping address configured")
return user.address.state
SEVERIDAD SI FALLA: Medium
TIEMPO ESTIMADO: 2-3 minutos
Item 11: Error handling es completo y correcto
QUÉ VERIFICAR:
- ¿Los errores esperados se manejan? (DB not found, service timeout)
- ¿Los HTTP status codes son correctos? (404 vs 400 vs 500)
- ¿Los errores se propagan correctamente?
- ¿Los try/except no son demasiado amplios? (no `except Exception`)
CÓMO VERIFICAR:
- Para cada operación que puede fallar: ¿hay handling?
- ¿Los except blocks son específicos?
- ¿Se distingue entre error del cliente (4xx) y del servidor (5xx)?
EJEMPLO DE FALLA:
# Catch demasiado amplio — oculta bugs reales
@app.get("/users/{user_id}")
async def get_user(user_id: int):
try:
return db.get_user(user_id)
except Exception:
raise HTTPException(status_code=404, detail="Not found")
# ¿Y si el error es un connection timeout?
# Devuelve 404 en vez de 500/503
# Manejo específico por tipo de error
@app.get("/users/{user_id}")
async def get_user(user_id: int):
try:
user = db.get_user(user_id)
except ConnectionError:
raise HTTPException(status_code=503, detail="Service unavailable")
except DatabaseError as e:
logger.error(f"Database error: {e}")
raise HTTPException(status_code=500, detail="Internal error")
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
SEVERIDAD SI FALLA: Medium-High
TIEMPO ESTIMADO: 2-3 minutos
Item 12: Paginación y límites son correctos
QUÉ VERIFICAR:
- ¿El cálculo de páginas es correcto? (ceil, no floor)
- ¿Qué pasa con página 0 o negativa?
- ¿Hay límite máximo en per_page/limit?
- ¿La paginación se hace en la DB o carga todo en memoria?
CÓMO VERIFICAR:
- Calcular manualmente: 21 items / 20 per_page = ¿2 o 1 páginas?
- Probar: page=0, page=-1, per_page=0, per_page=999999
SEVERIDAD SI FALLA: Medium
TIEMPO ESTIMADO: 1-2 minutos
Item 13: Operaciones concurrentes no causan race conditions
QUÉ VERIFICAR:
- ¿Dos requests simultáneos pueden corromper datos?
- ¿Hay read-modify-write sin locking?
- ¿Los contadores se incrementan atómicamente?
- ¿Las reservas de inventario usan locking?
CÓMO VERIFICAR:
- Para cada write operation: ¿qué pasa si llegan 2 requests al
mismo tiempo?
- Buscar: patterns de read → modify → write sin transacción
EJEMPLO DE FALLA:
# Race condition en inventario
@app.post("/purchase/{product_id}")
async def purchase(product_id: str):
product = get_product(product_id)
if product.stock > 0: # Thread A lee stock=1
product.stock -= 1 # Thread A: stock=0
save_product(product) # Thread A guarda
# Thread B también leyó stock=1 ANTES de que A guardara
# Thread B: stock=0, vende lo mismo → oversold
return {"status": "purchased"}
raise HTTPException(status_code=400, detail="Out of stock")
# Con locking optimista
@app.post("/purchase/{product_id}")
async def purchase(product_id: str):
async with db.transaction():
product = await get_product_for_update(product_id)
if product.stock <= 0:
raise HTTPException(status_code=400, detail="Out of stock")
product.stock -= 1
await save_product(product)
return {"status": "purchased"}
SEVERIDAD SI FALLA: High (en operaciones de inventario/financieras)
TIEMPO ESTIMADO: 2-3 minutos
Categoría 4: AI-Specific (Items 14-17)
Estos items solo existen porque el código fue generado por AI. No los encontrarías en un checklist de code review tradicional.
Item 14: Todos los imports existen y son correctos
QUÉ VERIFICAR:
- ¿Cada paquete importado existe en pip/PyPI?
- ¿Las funciones/clases importadas existen en esos paquetes?
- ¿Los imports coinciden con la versión del paquete que usas?
- ¿Hay imports de funciones que suenan reales pero no existen?
CÓMO VERIFICAR:
- Para cada import no estándar: verificar en docs del paquete
- Buscar en PyPI si el paquete existe
- Si un import te suena "raro" pero plausible → verificar
EJEMPLO DE FALLA (hallucination clásica):
from fastapi import FastAPI, BackgroundScheduler # BackgroundScheduler no existe en FastAPI
from pydantic import BaseModel, validate_email # validate_email no es de pydantic
from sqlalchemy.ext.async import AsyncSession # El path correcto es sqlalchemy.ext.asyncio
from sklearn.metrics import roc_auc_multiclass # No existe, es roc_auc_score
SEVERIDAD SI FALLA: High (el código no ejecuta)
TIEMPO ESTIMADO: 2-3 minutos
Item 15: No hay sobre-ingeniería para el problema
QUÉ VERIFICAR:
- ¿Hay patrones de diseño que no se necesitan? (Factory, Strategy,
Observer para un CRUD simple)
- ¿Hay abstracciones que solo tienen una implementación?
- ¿La complejidad del código es proporcional a la complejidad
del problema?
- ¿Hay clases abstractas sin implementaciones concretas?
CÓMO VERIFICAR:
- Comparar: ¿qué tan simple es el problema vs qué tan complejo
es el código?
- Contar clases/archivos: ¿son proporcionales a la funcionalidad?
- Buscar: abc.ABC, @abstractmethod, __init_subclass__
EJEMPLO DE FALLA:
# Para un endpoint que devuelve "Hello, World"
# AI genera un sistema con Factory, Strategy, y Registry:
class GreetingStrategy(abc.ABC):
@abc.abstractmethod
def greet(self, name: str) -> str: ...
class EnglishGreeting(GreetingStrategy):
def greet(self, name: str) -> str:
return f"Hello, {name}"
class GreetingFactory:
_strategies: dict = {}
@classmethod
def register(cls, lang: str, strategy: GreetingStrategy):
cls._strategies[lang] = strategy
@classmethod
def get(cls, lang: str) -> GreetingStrategy:
return cls._strategies[lang]
# Cuando lo único que necesitas es:
@app.get("/hello/{name}")
async def hello(name: str):
return {"message": f"Hello, {name}"}
SEVERIDAD SI FALLA: Low-Medium (funciona, pero es innecesariamente complejo)
TIEMPO ESTIMADO: 1-2 minutos
Item 16: Las APIs y funciones de librerías se usan correctamente
QUÉ VERIFICAR:
- ¿Las funciones se llaman con los parámetros correctos?
- ¿Los parámetros tienen los nombres correctos?
- ¿Se usa la versión actual de la API, no una deprecated?
- ¿Los valores de retorno se manejan correctamente?
CÓMO VERIFICAR:
- Comparar cada llamada a librería con la documentación oficial
- Buscar: deprecation warnings, versiones de API
- Verificar que los parámetros named son correctos
EJEMPLO DE FALLA:
# AI usa API de versión anterior
import jwt
# pyjwt < 2.0 (deprecated)
token = jwt.encode(payload, secret, algorithm="HS256")
# En pyjwt >= 2.0, encode() devuelve str, no bytes
# AI podría agregar .decode("utf-8") que ya no es necesario
decoded = jwt.decode(token, secret, algorithm="HS256")
# En pyjwt >= 2.0, el parámetro es algorithms=[...], no algorithm=...
# Esto lanza un error sutil
# Versión correcta para pyjwt >= 2.0
import jwt
token = jwt.encode(payload, secret, algorithm="HS256") # Devuelve str
decoded = jwt.decode(token, secret, algorithms=["HS256"]) # Lista, no string
SEVERIDAD SI FALLA: Medium-High (puede causar runtime errors sutiles)
TIEMPO ESTIMADO: 3-5 minutos (requiere verificar docs)
Item 17: No hay código que mezcle patrones de diferentes frameworks
QUÉ VERIFICAR:
- ¿Se mezcla Flask con FastAPI?
- ¿Se mezcla Django ORM con SQLAlchemy?
- ¿Se usan patrones de un framework en otro?
- ¿Los decoradores y middleware son del framework correcto?
CÓMO VERIFICAR:
- Verificar que todos los imports son del mismo ecosistema
- Buscar patrones que "se ven raros" para el framework usado
- Verificar que los decoradores corresponden al framework
EJEMPLO DE FALLA:
from fastapi import FastAPI
from flask import jsonify # ¿Flask en un proyecto FastAPI?
app = FastAPI()
@app.route("/users") # @app.route es Flask, no FastAPI
def get_users(): # Sin async — patrón Flask, no FastAPI
users = get_all_users()
return jsonify(users) # jsonify es Flask — FastAPI usa return directo
SEVERIDAD SI FALLA: Medium (puede causar errores difíciles de diagnosticar)
TIEMPO ESTIMADO: 1-2 minutos
Categoría 5: Calidad de Código (Items 18-20)
Estos se revisan cuando hay tiempo. Son importantes para mantenibilidad pero no bloquean el merge.
Item 18: Los response models filtran datos sensibles
QUÉ VERIFICAR:
- ¿Los endpoints devuelven solo los campos necesarios?
- ¿Hay password_hash, tokens, o datos internos en el response?
- ¿Los response_model de Pydantic están definidos?
- ¿SELECT * se convierte en response filtrado?
CÓMO VERIFICAR:
- Comparar: ¿qué campos tiene el model de DB vs qué devuelve
el endpoint?
- Buscar: response_model= en cada endpoint
EJEMPLO DE FALLA:
# Devuelve todo, incluyendo password_hash
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return db.get_user(user_id)
# Response: {"id": 1, "name": "Ana", "email": "...",
# "password_hash": "$2b$12$...", "role": "admin"}
# Solo devuelve campos públicos
class UserPublic(BaseModel):
id: int
name: str
email: str
@app.get("/users/{user_id}", response_model=UserPublic)
async def get_user(user_id: int):
return db.get_user(user_id)
SEVERIDAD SI FALLA: Medium-High (data leak)
TIEMPO ESTIMADO: 1-2 minutos
Item 19: La estructura del código sigue las convenciones del proyecto
QUÉ VERIFICAR:
- ¿Los archivos nuevos están en el directorio correcto?
- ¿Los nombres de archivos siguen las convenciones?
- ¿Las funciones están en el módulo correcto?
(no lógica de negocio en routes)
- ¿Los imports siguen el orden del proyecto?
CÓMO VERIFICAR:
- Comparar estructura del código nuevo con código existente
- Verificar: ¿service logic en service.py o en routes.py?
SEVERIDAD SI FALLA: Low
TIEMPO ESTIMADO: 1 minuto
Item 20: Los tests (si existen) verifican comportamiento real
QUÉ VERIFICAR:
- ¿Los tests verifican el comportamiento correcto, no solo
que "no crashea"?
- ¿Hay tests para los edge cases identificados?
- ¿Los assertions verifican valores específicos?
- ¿Los tests son independientes entre sí?
- Si AI generó los tests, ¿verifican lo correcto o solo
confirman lo que AI cree que es correcto?
CÓMO VERIFICAR:
- Leer cada assertion: ¿qué verifica exactamente?
- Buscar: tests que solo verifican status_code 200 sin
revisar el body
- Buscar: tests con assert True (no verifican nada)
EJEMPLO DE FALLA:
# Test que no verifica nada útil
def test_create_user():
response = client.post("/users", json={"name": "Ana", "email": "ana@test.com"})
assert response.status_code == 200 # Solo verifica que no crashea
# No verifica: ¿el usuario se creó? ¿Los datos son correctos?
# ¿El password se hasheó? ¿El response tiene el formato correcto?
# Test que verifica comportamiento real
def test_create_user():
response = client.post(
"/users",
json={"name": "Ana", "email": "ana@test.com", "password": "secure123"},
)
assert response.status_code == 201
data = response.json()
assert data["name"] == "Ana"
assert data["email"] == "ana@test.com"
assert "password" not in data
assert "password_hash" not in data
assert "id" in data
SEVERIDAD SI FALLA: Medium
TIEMPO ESTIMADO: 3-5 minutos
Checklist de Bolsillo: Versión Rápida
Para uso diario, esta es la versión compacta que puedes tener a mano:
CODE REVIEW DE AI CODE — CHECKLIST RÁPIDO
==========================================
SEGURIDAD (siempre):
□ 1. No hay secrets hardcoded
□ 2. SQL usa parameterized queries
□ 3. Endpoints sensibles tienen auth
□ 4. Inputs validados y sanitizados
□ 5. Errores no exponen info interna
LÓGICA (siempre que haya reglas):
□ 6. Resuelve el problema pedido (no uno diferente)
□ 7. Cálculos y condiciones correctos (> vs >=)
□ 8. Transiciones de estado válidas
□ 9. Operaciones atómicas cuando necesario
EDGE CASES (frecuentemente):
□ 10. Maneja None/vacío/defaults
□ 11. Error handling completo y correcto
□ 12. Paginación/límites correctos
□ 13. Sin race conditions en writes
AI-SPECIFIC (siempre en código AI):
□ 14. Todos los imports existen
□ 15. No hay sobre-ingeniería
□ 16. APIs de librerías usadas correctamente
□ 17. No mezcla frameworks
CALIDAD (cuando hay tiempo):
□ 18. Response models filtran datos sensibles
□ 19. Estructura sigue convenciones del proyecto
□ 20. Tests verifican comportamiento real
Conexión con Proyecto
El checklist como herramienta del proyecto integrador
En el módulo 8, vas a recibir un codebase FastAPI con 15-20 problemas. Tu checklist es tu arma principal. Cada item del checklist corresponde a un tipo de problema que podrías encontrar:
| Item del checklist | Problema típico en M8 |
|---|---|
| Item 1 (secrets) | API key hardcoded en config.py |
| Item 2 (SQL) | String concatenation en una query de búsqueda |
| Item 7 (cálculos) | Descuento calculado con operador incorrecto |
| Item 14 (imports) | Import de función que no existe en la librería |
| Item 15 (sobre-ingeniería) | Factory pattern para un solo tipo de objeto |
Si tu checklist no tiene un item para un tipo de problema, no lo encontrarás. Por eso el checklist es una herramienta viva — lo mejoras cada vez que un problema se te escapa.
Troubleshooting
Problema 1: "20 items es demasiado — no puedo recordarlos todos"
Causa: No necesitas memorizarlos. Necesitas internalizarlos. Solución: Empieza con los primeros 5 (seguridad). Úsalos en cada review. Cuando sean automáticos, agrega los siguientes 4 (lógica). Progresa gradualmente. En 2-3 semanas, los 20 serán segunda naturaleza. Mientras tanto, ten el checklist de bolsillo visible.
Problema 2: "Algunos items no aplican a mi código"
Causa: No todo código tiene SQL, transiciones de estado, o concurrencia. Solución: Marca "N/A" en items que no aplican y sigue adelante. El checklist cubre los casos más comunes — si un item no aplica, son 5 segundos de "N/A", no 5 minutos de revisión. Nunca elimines items del checklist; mejor tener un item que no aplica el 80% del tiempo que perder un issue crítico el 20%.
Problema 3: "El item 16 (APIs correctas) toma mucho tiempo"
Causa: Verificar documentación de cada librería es lento.
Solución: No verificas CADA llamada. Verificas: (1) imports que no reconoces, (2) funciones con parámetros que te suenan raros, (3) cualquier cosa que te genere duda. Para llamadas estándar que has usado 100 veces (como FastAPI() o BaseModel), confía en tu experiencia. El item 16 es para detectar hallucinations sutiles, no para re-verificar toda la documentación.
Problema 4: "Mi equipo tiene su propio checklist — ¿uso ambos?"
Causa: Checklists existentes probablemente son para código humano. Solución: Fusiona. Tu checklist de equipo probablemente tiene items de la Categoría 1 (seguridad) y Categoría 5 (calidad). Los items de Categoría 4 (AI-Specific) probablemente no están. Agrega los items 14-17 al checklist de tu equipo. Propón el cambio como "items adicionales para código generado por AI."
Ejercicios
Ejercicio 1: Aplicar el checklist (Medio)
Aplica el checklist de 20 items al siguiente código. Para cada item, marca: ✅ (pasa), ❌ (falla), ⚠️ (parcial), o N/A.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
import uuid
app = FastAPI()
products_db = {}
class Product(BaseModel):
name: str
price: float
category: str
stock: int
class ProductResponse(Product):
id: str
created_at: datetime
@app.post("/products", response_model=ProductResponse)
async def create_product(product: Product):
product_id = str(uuid.uuid4())
record = ProductResponse(
id=product_id,
created_at=datetime.utcnow(),
**product.model_dump(),
)
products_db[product_id] = record
return record
@app.get("/products", response_model=List[ProductResponse])
async def list_products(category: Optional[str] = None):
products = list(products_db.values())
if category:
products = [p for p in products if p.category == category]
return products
@app.get("/products/{product_id}", response_model=ProductResponse)
async def get_product(product_id: str):
if product_id not in products_db:
raise HTTPException(status_code=404, detail="Product not found")
return products_db[product_id]
Ver solución
| # | Item | Resultado | Nota |
|---|---|---|---|
| 1 | Secrets hardcoded | ✅ | No hay secrets |
| 2 | SQL parameterized | N/A | No hay SQL |
| 3 | Auth en endpoints | ⚠️ | POST sin auth — ¿debería tener? |
| 4 | Inputs validados | ⚠️ | Sin min_length, price puede ser negativo |
| 5 | Errores no exponen info | ✅ | Solo "Product not found" |
| 6 | Resuelve problema pedido | ✅ | CRUD básico correcto |
| 7 | Cálculos correctos | N/A | No hay cálculos |
| 8 | Transiciones de estado | N/A | No hay estados |
| 9 | Operaciones atómicas | N/A | Single-step operations |
| 10 | Maneja None/vacío | ✅ | Optional category funciona con None |
| 11 | Error handling | ⚠️ | Solo 404, no hay handling para otros errores |
| 12 | Paginación | ❌ | No hay paginación — devuelve TODO |
| 13 | Race conditions | N/A | In-memory, single process |
| 14 | Imports existen | ✅ | Todos son imports reales |
| 15 | Sobre-ingeniería | ✅ | Simple y proporcional |
| 16 | APIs correctas | ✅ | FastAPI y Pydantic usados correctamente |
| 17 | Mezcla frameworks | ✅ | Solo FastAPI |
| 18 | Response filtra datos | ✅ | ProductResponse no tiene datos sensibles |
| 19 | Estructura convenciones | ✅ | Estructura estándar |
| 20 | Tests | N/A | No hay tests |
Resumen: 2 issues principales:
price: floatdebería serDecimalcongt=0— acepta negativos y tiene imprecisión- Sin paginación en list_products — peligroso con muchos productos
Ejercicio 2: Mejorar un item del checklist (Medio)
El item 4 dice: "Inputs del usuario están validados y sanitizados."
Reescribe este item para hacerlo más específico y verificable. Tu versión debe incluir:
- Exactamente qué verificar (3-5 puntos)
- Cómo verificar cada punto
- Un ejemplo de falla
Ver solución
Item 4 mejorado: Inputs del usuario están validados con Pydantic constraints
QUÉ VERIFICAR:
1. ¿Cada string tiene max_length definido?
→ Previene DoS por inputs de 10MB
2. ¿Los números tienen rangos definidos? (ge, le, gt, lt)
→ Previene precios negativos, cantidades de 999999999
3. ¿Los Enums se usan para valores limitados?
→ status debería ser Enum, no str libre
4. ¿Hay inputs que llegan como dict o Any?
→ Bypass completo de validación
5. ¿Los emails usan EmailStr, no str?
→ str acepta "no-soy-email" como email
CÓMO VERIFICAR:
- Abrir cada BaseModel: revisar cada Field()
- Buscar campos sin constraints: str sin max_length, int sin ge/le
- Buscar: dict, Any, str donde debería haber Enum
EJEMPLO DE FALLA:
# Sin constraints
class UserCreate(BaseModel):
name: str # Puede ser "" o un string de 10MB
age: int # Puede ser -50 o 999999
role: str # Puede ser "superadmin_hackeado"
email: str # Puede ser "no soy email"
# Con constraints
class UserCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
age: int = Field(..., ge=0, le=150)
role: UserRole # Enum con valores válidos
email: EmailStr
Ejercicio 3: Encontrar el item faltante (Difícil)
El checklist de 20 items no cubre un escenario que encontraste en tu review: el código generado por AI usa datetime.utcnow() que está deprecated en Python 3.12+ (favor de usar datetime.now(timezone.utc)).
¿En qué categoría agregarías este item? Escribe el item completo con el formato del checklist.
Ver solución
Categoría: AI-Specific (junto a items 14-17)
Este item va en AI-Specific porque AI entrena con código que incluye datetime.utcnow() extensivamente (era el standard por años). AI seguirá generando este patrón incluso después de que fue deprecated. Un developer humano actualizado lo sabría; AI no necesariamente.
Item 16.5: No usa APIs deprecated del lenguaje o librerías
QUÉ VERIFICAR:
- datetime.utcnow() → deprecated en Python 3.12+
- datetime.utcfromtimestamp() → deprecated
- asyncio.get_event_loop() → changed behavior en 3.10+
- pkg_resources → replaced by importlib.resources
- unittest.TestCase con nose-style → prefer pytest
CÓMO VERIFICAR:
- Buscar: utcnow(), utcfromtimestamp()
- Verificar versión de Python del proyecto
- Consultar "What's New" docs de la versión actual
EJEMPLO DE FALLA:
# Deprecated en Python 3.12+
from datetime import datetime
created_at = datetime.utcnow() # No timezone-aware
# Correcto
from datetime import datetime, timezone
created_at = datetime.now(timezone.utc) # Timezone-aware
SEVERIDAD SI FALLA: Low-Medium (funciona pero genera deprecation warnings,
y puede causar bugs de timezone)
TIEMPO ESTIMADO: 1-2 minutos
Nota: Este es un ejemplo perfecto de por qué el checklist es una herramienta viva. Cada review donde algo se te escapa es una oportunidad de agregar un item.
Ejercicio 4: Checklist para tu dominio (Difícil)
Agrega 3 items al checklist que sean específicos para tu dominio de trabajo. Si trabajas en fintech, healthtech, e-commerce, o cualquier otro dominio, hay checks específicos que el checklist genérico no cubre.
Ver ejemplos por dominio
Fintech:
- Item F1: ¿Los montos usan Decimal con precisión definida, nunca float?
- Item F2: ¿Hay audit trail para cada transacción? (quién, cuándo, qué)
- Item F3: ¿Las operaciones financieras tienen idempotency keys?
Healthtech:
- Item H1: ¿Los datos de salud están cifrados en reposo (HIPAA)?
- Item H2: ¿Hay logs de acceso a records de pacientes?
- Item H3: ¿Los endpoints de datos médicos requieren autenticación multi-factor?
E-commerce:
- Item E1: ¿Los precios se calculan server-side, nunca desde el cliente?
- Item E2: ¿El inventario se verifica al momento del pago, no solo al agregar al carrito?
- Item E3: ¿Los cupones tienen validación de uso único/expiración?
Tu turno: Define 3 items para TU dominio con el formato completo (qué verificar, cómo verificar, ejemplo de falla).
Resumen
En esta cápsula construiste:
- Un checklist profesional de 20 items organizado en 5 categorías alineadas con la pirámide
- Categoría 1 (Seguridad): 5 items que se revisan siempre — secrets, SQL, auth, validación, error messages
- Categoría 2 (Lógica): 4 items para reglas de negocio — problema correcto, cálculos, estados, atomicidad
- Categoría 3 (Edge Cases): 4 items para inputs inesperados — None, error handling, paginación, concurrencia
- Categoría 4 (AI-Specific): 4 items que solo aplican a código AI — imports falsos, sobre-ingeniería, APIs incorrectas, mezcla de frameworks
- Categoría 5 (Calidad): 3 items para mantenibilidad — response models, estructura, tests
- Un checklist de bolsillo para uso diario
- El principio de que el checklist es una herramienta viva que mejoras con cada review
Próxima cápsula: Red Flags en Código AI — profundizar en los items 14-17 con ejemplos detallados y patrones específicos.
Recursos Adicionales
- Google — Code Review Developer Guide - El estándar de code review que inspira este checklist
- OWASP — Web Security Testing Guide - Guía exhaustiva para los items de seguridad
- Pydantic — Field Types and Validators - Referencia para validación de inputs (item 4)
- FastAPI — Security Best Practices - Autenticación y autorización en FastAPI (item 3)
- Python — What's New in 3.12 - Deprecations relevantes para items AI-specific
Debugging & Code Review with Claude Code — Módulo 4, Cápsula 03 Claude Code Agentic Development Path — Guía #6 de 11