Módulo 8: Proyecto Integrador
Fase 3: Corrección — Implementar y Justificar Cada Fix
Fase 3: Corrección — Implementar y Justificar Cada Fix
Descripción de la cápsula
Tienes un findings document con 12-18 problemas documentados. Ahora viene la parte donde demuestras no solo que puedes encontrar problemas, sino que puedes resolverlos correctamente. Esta fase tiene tres componentes: priorizar qué corregir primero, implementar cada corrección, y justificar por qué tu fix es correcto.
La justificación es igual de importante que el fix. Cualquier developer puede cambiar una línea de código. Un developer profesional explica por qué ese cambio es necesario, por qué la corrección es la adecuada, y cómo verificó que funciona. Eso es lo que te diferencia.
Priorización: Qué Corregir Primero
La regla: impacto descendente
No corriges en el orden que encontraste los problemas. Corriges en orden de impacto:
Critical → High → Medium → Low
¿Por qué? Porque en un escenario real, si solo tuvieras tiempo para corregir la mitad de los problemas, los Critical y High son los que no puedes dejar pasar. Un security hole en producción es una emergencia. Un naming incorrecto es una mejora.
Cómo organizar tus fixes
Ordena tu findings document por severidad y agrupa:
## Correcciones a Implementar
### Bloque 1: Critical (implementar inmediatamente)
1. Finding #X: JWT secret hardcoded → config.py
2. Finding #Y: SQL injection en búsqueda → routes/users.py
3. Finding #Z: Contraseña almacenada en texto plano → routes/auth.py
4. Finding #W: Endpoint sin autenticación → routes/users.py
### Bloque 2: High (implementar a continuación)
5. Finding #A: Lógica de delete incorrecta → services/task_service.py
6. Finding #B: Autorización faltante en get_task_by_id → services/task_service.py
7. Finding #C: Validación de password insuficiente → models.py
8. Finding #D: Estadísticas incluyen tareas eliminadas → services/task_service.py
### Bloque 3: Medium (implementar después)
9. Finding #E: Paginación off-by-one → services/task_service.py
10. Finding #F: División por cero en estadísticas → services/task_service.py
11. Finding #G: Validación de título insuficiente → models.py
12. Finding #H: Filtros con SQL injection → services/task_service.py
### Bloque 4: Low (implementar si hay tiempo)
13. Finding #I: Import inexistente → main.py
14. Finding #J: Validaciones de prioridad/estado faltantes → models.py
15. Finding #K: Paginación acepta page=0 → routes/tasks.py
Decidir: Regenerar vs Editar
Antes de empezar a corregir, para cada finding decide si vas a:
- Editar manualmente: Cambiar las líneas específicas del problema
- Regenerar con Claude Code: Pedir a Claude Code que regenere la función o bloque completo
Framework de decisión (Módulo 7)
| Criterio | Editar | Regenerar |
|---|---|---|
| Scope del cambio | 1-5 líneas | Función completa o más |
| Claridad del fix | Sabes exactamente qué cambiar | No estás seguro del approach correcto |
| Riesgo de efectos secundarios | Bajo — cambio localizado | Alto — muchas dependencias |
| Porcentaje del código correcto | >90% está bien | <50% está bien |
| Complejidad del código | Simple, legible | Complejo, difícil de modificar |
Ejemplos de cuándo editar
Finding: JWT secret hardcoded en config.py
→ EDITAR: Cambiar una línea de `os.getenv("...", "fallback")`
a `os.environ["..."]`. Fix puntual, claro, sin riesgo.
Finding: Validación de password acepta 4 chars en vez de 8
→ EDITAR: Cambiar `if len(v) < 4` a `if len(v) < 8`.
Una línea, el resto de la función está bien.
Ejemplos de cuándo regenerar
Finding: La función get_tasks tiene SQL injection + offset
incorrecto + filtros con concatenación
→ REGENERAR: La función tiene 3 problemas diferentes.
Es más seguro regenerar la función completa con los
requisitos correctos que parchear 3 cosas.
Finding: El flujo de delete es hard delete cuando debería
ser soft delete. Afecta delete_task + get_tasks + get_user_stats
→ REGENERAR las 3 funciones: El cambio afecta múltiples
funciones y la lógica base es incorrecta.
Cómo documentar la decisión
Para cada fix, incluye:
**Decisión regenerar/editar:** [Editar / Regenerar]
**Justificación:** [Por qué elegiste esta opción]
Correcciones por Categoría
A continuación se presentan las correcciones para los problemas del codebase. Intenta implementar las tuyas primero antes de consultar estas soluciones. Las soluciones están ocultas para que las uses como referencia después de tu intento.
Corrección: Security — JWT Secret Hardcoded
Archivo: config.py
Problema: JWT_SECRET_KEY tiene un fallback hardcoded que se usaría en producción si la variable de entorno no existe.
Requisito violado: RF-02.3, RF-06.1
Ver solución
import os
class Settings:
APP_NAME: str = "TaskFlow API"
APP_VERSION: str = "1.0.0"
DEBUG: bool = os.getenv("DEBUG", "false").lower() == "true"
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./taskflow.db")
DATABASE_PATH: str = "taskflow.db"
JWT_SECRET_KEY: str = os.environ["JWT_SECRET_KEY"]
JWT_ALGORITHM: str = "HS256"
JWT_EXPIRATION_MINUTES: int = 30
BCRYPT_ROUNDS: int = 12
DEFAULT_PAGE_SIZE: int = 10
MAX_PAGE_SIZE: int = 100
settings = Settings()
Por qué este fix es correcto:
os.environ["JWT_SECRET_KEY"]lanzaKeyErrorsi la variable no existe, lo cual es el comportamiento correcto: la aplicación no debe arrancar sin configuración de seguridad crítica- Se eliminó el string
"super-secret-key-taskflow-2026"que cualquier persona con acceso al código fuente podría usar para forjar tokens - También se corrigió
DEBUGpara que no esté hardcoded comoTrueen producción
Decisión: Editar. El fix es puntual (2 líneas), el contexto es claro, y el resto del archivo está bien.
Corrección: Security — Contraseña Almacenada en Texto Plano
Archivo: routes/auth.py
Problema: En la función register(), la contraseña se inserta directamente en la base de datos sin hashear.
Requisito violado: RF-01.2
Ver solución
@router.post("/register", response_model=UserResponse)
async def register(user: UserCreate):
existing = execute_query(
"SELECT id FROM users WHERE email = ?", (user.email,)
)
if existing:
raise HTTPException(status_code=400, detail="Email already registered")
hashed = hash_password(user.password)
user_id = execute_insert(
"INSERT INTO users (email, name, password) VALUES (?, ?, ?)",
(user.email, user.name, hashed),
)
created_user = execute_query(
"SELECT id, email, name, created_at FROM users WHERE id = ?",
(user_id,),
)
row = dict(created_user[0])
return UserResponse(**row)
Por qué este fix es correcto:
- Se llama a
hash_password(user.password)antes de insertar en la DB hash_passwordusa bcrypt con salt, lo cual es el estándar de la industria- La verificación en login ya usa
verify_passwordque compara contra el hash, así que el flujo completo es consistente
Decisión: Editar. Solo faltaba una línea (hashed = hash_password(user.password)) y cambiar user.password por hashed en el INSERT. El resto de la función está bien.
Corrección: Security — SQL Injection en Búsqueda de Usuarios
Archivo: routes/users.py
Problema: El endpoint /users/search usa f-string para construir la query SQL y no requiere autenticación.
Requisito violado: RF-06.2, RF-02.4
Ver solución
@router.get("/search")
async def search_users(
query: str,
current_user: dict = Depends(get_current_user),
):
results = execute_query(
"SELECT id, email, name FROM users WHERE name LIKE ?",
(f"%{query}%",),
)
return [dict(row) for row in results]
Por qué este fix es correcto:
- Se reemplazó el f-string en la query SQL con un parámetro
?(parameterized query) - El
%{query}%se pasa como parámetro, no se concatena en la query - Se agregó
Depends(get_current_user)para requerir autenticación - SQLite escapa automáticamente los parámetros, previniendo SQL injection
Decisión: Editar. Los dos cambios son puntuales y claros: parameterizar la query y agregar el dependency de autenticación.
Corrección: Security — SQL Injection en Filtros de Tareas
Archivo: services/task_service.py
Problema: La función get_tasks() usa f-strings para insertar status y priority en la query SQL.
Requisito violado: RF-06.2
Ver solución
def get_tasks(
user_id: int,
status: Optional[str] = None,
priority: Optional[str] = None,
page: int = 1,
size: int = 10,
) -> dict:
base_query = "SELECT * FROM tasks WHERE user_id = ?"
count_query = "SELECT COUNT(*) as total FROM tasks WHERE user_id = ?"
params = [user_id]
if status:
base_query += " AND status = ?"
count_query += " AND status = ?"
params.append(status)
if priority:
base_query += " AND priority = ?"
count_query += " AND priority = ?"
params.append(priority)
offset = (page - 1) * size
base_query += f" ORDER BY created_at DESC LIMIT ? OFFSET ?"
query_params = params + [size, offset]
with get_db() as conn:
cursor = conn.cursor()
cursor.execute(count_query, tuple(params))
total = cursor.fetchone()["total"]
cursor.execute(base_query, tuple(query_params))
rows = cursor.fetchall()
tasks = [dict(row) for row in rows]
return {
"tasks": tasks,
"total": total,
"page": page,
"size": size,
}
Por qué este fix es correcto:
- Se reemplazaron los f-strings
f" AND status = '{status}'"con parámetros" AND status = ?"yparams.append(status) - Se corrigió el offset de
page * sizea(page - 1) * size(se soluciona el off-by-one de paginación al mismo tiempo) - LIMIT y OFFSET también usan parámetros
- Los params de count_query y base_query se manejan correctamente por separado
Decisión: Regenerar. La función tenía 3 problemas (SQL injection en status, SQL injection en priority, offset incorrecto). Con tantos cambios interrelacionados, es más seguro regenerar la función completa.
Corrección: Logic — Autorización Faltante en get_task_by_id
Archivo: services/task_service.py
Problema: get_task_by_id() no filtra por user_id, permitiendo que cualquier usuario autenticado vea cualquier tarea.
Requisito violado: RF-03.4, RF-03.7, RF-05.6
Ver solución
def get_task_by_id(task_id: int, user_id: int) -> Optional[dict]:
query = """
SELECT id, title, description, priority, status,
user_id, created_at, updated_at
FROM tasks WHERE id = ?
"""
rows = execute_query(query, (task_id,))
if not rows:
return None
task = dict(rows[0])
if task["user_id"] != user_id:
from fastapi import HTTPException
raise HTTPException(
status_code=403,
detail="You don't have permission to access this task"
)
return task
Por qué este fix es correcto:
- Primero verifica si la tarea existe (retorna
None→ 404) - Luego verifica si pertenece al usuario (retorna 403)
- Según RF-05.6, debe ser 403 (no 404) cuando la tarea existe pero pertenece a otro usuario
- Este cambio afecta a
get_task,update_task, ydelete_taskque todos llaman aget_task_by_id, así que se propaga automáticamente
Decisión: Editar. La query está bien, solo falta la verificación de propiedad. Se agrega un bloque de 4 líneas.
Corrección: Logic — Delete es Hard Delete en vez de Soft Delete
Archivo: services/task_service.py
Problema: delete_task() ejecuta DELETE FROM tasks en vez de cambiar el estado a "deleted".
Requisito violado: RF-03.6
Ver solución
def delete_task(task_id: int, user_id: int) -> bool:
existing = get_task_by_id(task_id, user_id)
if not existing:
return False
query = "UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?"
execute_query(query, ("deleted", datetime.utcnow().isoformat(), task_id))
return True
Por qué este fix es correcto:
- Cambia
DELETE FROM tasksporUPDATE tasks SET status = 'deleted' - Esto implementa soft delete: la tarea sigue en la base de datos pero con estado "deleted"
- Se actualiza
updated_atpara registrar cuándo se "eliminó" - Los endpoints de listar y estadísticas necesitarán filtrar tareas con estado "deleted" (correcciones separadas)
Decisión: Editar. El cambio es de una línea de SQL, reemplazando DELETE por UPDATE.
Corrección: Logic — Estadísticas Incluyen Tareas Eliminadas
Archivo: services/task_service.py
Problema: get_user_stats() cuenta todas las tareas, incluyendo las eliminadas (soft deleted).
Requisito violado: RF-04.2
Ver solución
def get_user_stats(user_id: int) -> dict:
query = """
SELECT status, priority FROM tasks
WHERE user_id = ? AND status != ?
"""
rows = execute_query(query, (user_id, "deleted"))
total = len(rows)
by_status = {}
by_priority = {}
for row in rows:
row_dict = dict(row)
status = row_dict["status"]
priority = row_dict["priority"]
by_status[status] = by_status.get(status, 0) + 1
by_priority[priority] = by_priority.get(priority, 0) + 1
if total == 0:
completion_percentage = 0.0
else:
completion_percentage = (
by_status.get("completed", 0) / total * 100
)
return {
"total_tasks": total,
"by_status": by_status,
"by_priority": by_priority,
"completion_percentage": round(completion_percentage, 2),
}
Por qué este fix es correcto:
- Se agregó
AND status != ?con parámetro"deleted"para excluir tareas eliminadas - Se agregó manejo del caso
total == 0para evitar división por cero (corrige otro finding al mismo tiempo) - Las estadísticas ahora solo reflejan tareas activas, según RF-04.2
Decisión: Editar. Los cambios son localizados: agregar condición al WHERE y agregar el guard para división por cero.
Corrección: Edge Case — División por Cero en Estadísticas
Archivo: services/task_service.py
Problema: get_user_stats() divide por total sin verificar que sea mayor a cero. Crashea para usuarios sin tareas.
Requisito violado: RF-04.1 (debe retornar estadísticas válidas)
Nota: Esta corrección ya está incluida en la corrección anterior de estadísticas. La solución combinada aborda ambos problemas.
Corrección: Edge Case — Paginación Off-by-One
Archivo: services/task_service.py
Problema: El offset se calcula como page * size en vez de (page - 1) * size, causando que la primera página (page=1) se salte los primeros registros.
Requisito violado: RF-03.2
Nota: Esta corrección ya está incluida en la corrección de get_tasks() de la sección de SQL injection.
Corrección: Hallucination — Import Inexistente
Archivo: main.py
Problema: from pydantic_settings import BaseSettings importa una clase de un paquete que no está en requirements.txt. El paquete pydantic-settings es un paquete separado de pydantic y no está instalado.
Requisito violado: N/A (hallucination)
Ver solución
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from config import settings
from database import init_db
from routes import auth, tasks, users
app = FastAPI(
title=settings.APP_NAME,
version=settings.APP_VERSION,
debug=settings.DEBUG,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.on_event("startup")
async def startup():
init_db()
app.include_router(auth.router)
app.include_router(tasks.router)
app.include_router(users.router)
@app.get("/health")
async def health_check():
return {
"status": "healthy",
"app": settings.APP_NAME,
"version": settings.APP_VERSION,
}
Por qué este fix es correcto:
- Se eliminó
from pydantic_settings import BaseSettingsque no se usa en el archivo y causaríaModuleNotFoundErroral arrancar - El paquete
pydantic-settingsno está enrequirements.txty la claseBaseSettingsno se utiliza en ninguna parte del archivo - El resto del archivo funciona correctamente sin ese import
Decisión: Editar. Eliminar una línea de import. Fix trivial.
Corrección: Hallucination — Función Inexistente en bcrypt
Archivo: services/auth_service.py
Problema: from bcrypt import hashpw, gensalt, checkpw, verify_hash importa verify_hash, una función que no existe en el paquete bcrypt. Las funciones reales de bcrypt son hashpw, gensalt, checkpw, y kdf.
Requisito violado: N/A (hallucination)
Ver solución
from bcrypt import hashpw, gensalt, checkpw
Por qué este fix es correcto:
verify_hashno existe en la API de bcrypt — la función equivalente escheckpw- El código ya usa
checkpwcorrectamente enverify_password(), por lo queverify_hashes un import muerto - Este tipo de hallucination es más sutil que un paquete inexistente: el paquete bcrypt es real, pero la función es inventada
Decisión: Editar. Eliminar verify_hash del import. Fix trivial.
Corrección: Hallucination — Parámetro Inexistente en fetchall()
Archivo: services/task_service.py
Problema: cursor.fetchall(as_dict=True) pasa un parámetro as_dict que no existe en sqlite3.Cursor.fetchall(). El método no acepta ningún parámetro.
Requisito violado: N/A (hallucination)
Ver solución
cursor.execute(base_query, tuple(params))
rows = cursor.fetchall()
Por qué este fix es correcto:
fetchall()de sqlite3 no acepta parámetros — causaráTypeError: fetchall() got an unexpected keyword argument 'as_dict'- Para obtener resultados como diccionarios, se usa
conn.row_factory = sqlite3.Row(que ya está configurado endatabase.py) - Este hallucination es peligroso porque no falla al arrancar sino al ejecutar la query de listar tareas
Decisión: Editar. Eliminar el parámetro. Fix trivial.
Corrección: Validation — Password Acepta 4 Caracteres
Archivo: models.py
Problema: La validación de password acepta 4 caracteres mínimo, pero RF-01.4 requiere 8 caracteres mínimo.
Requisito violado: RF-01.4
Ver solución
class UserCreate(BaseModel):
email: EmailStr
name: str
password: str
@field_validator("password")
@classmethod
def validate_password(cls, v):
if len(v) < 8:
raise ValueError("Password must be at least 8 characters")
return v
Por qué este fix es correcto:
- Se cambió
< 4a< 8para cumplir con RF-01.4 - Se actualizó el mensaje de error para reflejar el requisito correcto
Decisión: Editar. Cambio de un número en una línea.
Corrección: Validation — Título Acepta 1 Carácter
Archivo: models.py
Problema: La validación de título solo verifica que no esté vacío (>= 1 carácter), pero RF-05.1 requiere mínimo 3 y máximo 100 caracteres.
Requisito violado: RF-05.1
Ver solución
class TaskCreate(BaseModel):
title: str
description: Optional[str] = ""
priority: str = "medium"
status: str = "pending"
@field_validator("title")
@classmethod
def validate_title(cls, v):
if len(v) < 3:
raise ValueError("Title must be at least 3 characters")
if len(v) > 100:
raise ValueError("Title must be at most 100 characters")
return v
@field_validator("priority")
@classmethod
def validate_priority(cls, v):
allowed = {"low", "medium", "high"}
if v not in allowed:
raise ValueError(f"Priority must be one of: {', '.join(allowed)}")
return v
@field_validator("status")
@classmethod
def validate_status(cls, v):
allowed = {"pending", "in_progress", "completed"}
if v not in allowed:
raise ValueError(f"Status must be one of: {', '.join(allowed)}")
return v
Por qué este fix es correcto:
- Se cambió
< 1a< 3y se agregó límite máximo de 100 caracteres (RF-05.1) - Se agregaron validadores para
priority(RF-05.2) ystatus(RF-05.3) - Los validadores usan sets para verificación rápida y mensajes de error claros
Decisión: Regenerar la clase completa. Se necesitaban 3 validadores nuevos además de corregir el existente. Regenerar era más limpio que añadir uno por uno.
Corrección: Edge Case — Paginación Acepta page=0
Archivo: routes/tasks.py
Problema: El parámetro page acepta 0 porque tiene ge=0 en vez de ge=1.
Requisito violado: RF-05.4
Ver solución
@router.get("/", response_model=TaskListResponse)
async def list_tasks(
status: Optional[str] = Query(default=None),
priority: Optional[str] = Query(default=None),
page: int = Query(default=1, ge=1),
size: int = Query(default=10, ge=1, le=100),
current_user: dict = Depends(get_current_user),
):
result = get_tasks(
user_id=current_user["user_id"],
status=status,
priority=priority,
page=page,
size=size,
)
return TaskListResponse(**result)
Por qué este fix es correcto:
- Se cambió
ge=0age=1para que page empiece en 1 (RF-05.4) - FastAPI validará automáticamente y retornará 422 si se envía page=0
Decisión: Editar. Cambio de un carácter: 0 → 1.
Cómo Verificar Cada Fix
Después de implementar cada corrección, verifica que funciona:
Verificación de fixes de seguridad
# 1. Verificar que la app no arranca sin JWT_SECRET_KEY
unset JWT_SECRET_KEY
uvicorn main:app --reload # Debe fallar con KeyError
# 2. Verificar que la contraseña se hashea
export JWT_SECRET_KEY="test-secret"
uvicorn main:app --reload
curl -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "verify@test.com", "name": "Verify", "password": "securepass123"}'
sqlite3 taskflow.db "SELECT password FROM users WHERE email='verify@test.com'"
# Debe mostrar un hash de bcrypt, no "securepass123"
# 3. Verificar que SQL injection no funciona
curl -s "http://localhost:8000/users/search?query=test'%20OR%20'1'='1" \
-H "Authorization: Bearer $TOKEN"
# No debe retornar todos los usuarios
# 4. Verificar que búsqueda requiere auth
curl -s "http://localhost:8000/users/search?query=test"
# Debe retornar 401, no resultados
Verificación de fixes de lógica
# 1. Verificar soft delete
curl -X DELETE http://localhost:8000/tasks/1 \
-H "Authorization: Bearer $TOKEN"
sqlite3 taskflow.db "SELECT id, status FROM tasks WHERE id = 1"
# Debe mostrar status = 'deleted', no estar vacío
# 2. Verificar autorización
# Con token de User 2, intentar ver tarea de User 1
curl -s http://localhost:8000/tasks/1 \
-H "Authorization: Bearer $TOKEN_USER2"
# Debe retornar 403, no la tarea
# 3. Verificar paginación
curl -s "http://localhost:8000/tasks/?page=1&size=5" \
-H "Authorization: Bearer $TOKEN"
# Debe retornar las primeras 5 tareas, no saltarse ninguna
Verificación de fixes de edge cases
# 1. Verificar estadísticas sin tareas
curl -s http://localhost:8000/users/stats \
-H "Authorization: Bearer $TOKEN_NEW_USER"
# Debe retornar {"total_tasks": 0, ...}, no error 500
# 2. Verificar estadísticas sin tareas eliminadas
# (después de soft delete)
curl -s http://localhost:8000/users/stats \
-H "Authorization: Bearer $TOKEN"
# No debe incluir tareas con status "deleted"
# 3. Verificar validaciones
curl -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "weak@test.com", "name": "Weak", "password": "1234"}'
# Debe retornar 422, no 200
curl -X POST http://localhost:8000/tasks/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title": "AB", "priority": "medium"}'
# Debe retornar 422 (título < 3 caracteres)
curl -X POST http://localhost:8000/tasks/ \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title": "Tarea válida", "priority": "urgente"}'
# Debe retornar 422 (prioridad inválida)
Formato de Documentación de Justificaciones
Para cada corrección, documenta usando este formato:
# Justificaciones de Correcciones — TaskFlow API
## Fix #1: [Título del finding]
**Finding:** #[número] — [descripción breve]
**Severidad:** [Critical/High/Medium/Low]
**Archivo:** [nombre del archivo]
**Líneas afectadas:** [rango de líneas]
### Qué cambié
[Descripción concisa del cambio — qué líneas cambiaron y cómo]
### Por qué el código original era incorrecto
[Explicación referenciando el requisito funcional violado.
Usar el ID del requisito: RF-XX.X]
### Por qué mi corrección es correcta
[Explicación de por qué el fix resuelve el problema.
Incluir cómo verificaste que funciona.]
### Decisión regenerar/editar
- **Decisión:** [Editar / Regenerar]
- **Justificación:** [Por qué elegiste esta opción]
### Verificación
- **Método:** [Cómo verificaste que el fix funciona]
- **Resultado:** [Qué observaste]
---
## Fix #2: [Título del finding]
[Mismo formato]
Errores Comunes en Esta Fase
Error 1: Corregir sin justificar
Cambiar una línea de código no demuestra conocimiento. Explicar por qué la cambias y por qué tu versión es correcta sí lo demuestra. Cada fix sin justificación es un fix incompleto.
Error 2: Corregir los Low antes que los Critical
Es tentador empezar por lo fácil (cambiar un número de 4 a 8). Pero en un entorno profesional, si solo tienes 30 minutos, los security holes son lo primero. Practica la priorización.
Error 3: No verificar que el fix funciona
"Cambié la línea, debe funcionar" es una suposición. Ejecuta el endpoint, verifica el resultado, confirma que el fix resuelve el problema sin introducir otros.
Error 4: Introducir nuevos bugs al corregir
Cada corrección tiene el potencial de romper algo más. Después de cada fix, ejecuta las pruebas básicas para confirmar que no rompiste nada:
# Smoke test después de cada fix
curl -s http://localhost:8000/health
curl -s -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "smoke@test.com", "name": "Smoke", "password": "smoketest123"}'
curl -s -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "smoke@test.com", "password": "smoketest123"}'
Error 5: Regenerar todo el codebase
Si le dices a Claude Code "regenera todo el codebase corregido", pierdes:
- La oportunidad de demostrar que entiendes cada problema
- La documentación de qué cambió y por qué
- La práctica de la habilidad real (corrección quirúrgica)
Regenera funciones específicas cuando el framework lo justifica. No regeneres archivos completos.
Resumen de Correcciones Esperadas
Al final de esta fase, deberías haber implementado correcciones para todos los problemas encontrados. Este es un mapa de las correcciones principales por archivo:
config.py
- ✅ JWT secret sin fallback hardcoded
- ✅ DEBUG no hardcoded como True
main.py
- ✅ Import inexistente eliminado
models.py
- ✅ Validación de password: 8 caracteres mínimo
- ✅ Validación de título: 3-100 caracteres
- ✅ Validación de prioridad: solo low/medium/high
- ✅ Validación de estado: solo pending/in_progress/completed
routes/auth.py
- ✅ Contraseña hasheada antes de almacenar
routes/tasks.py
- ✅ Paginación: page >= 1
routes/users.py
- ✅ SQL injection corregido en búsqueda
- ✅ Autenticación agregada en búsqueda
services/task_service.py
- ✅ SQL injection corregido en filtros
- ✅ Paginación offset corregido
- ✅ Soft delete implementado
- ✅ Verificación de propiedad en get_task_by_id
- ✅ Estadísticas excluyen tareas eliminadas
- ✅ División por cero manejada
Checkpoint de la Fase de Corrección
Antes de pasar a la fase de entrega y retrospectiva, verifica:
- ✅ Todos los findings de severidad Critical están corregidos
- ✅ Todos los findings de severidad High están corregidos
- ✅ La mayoría de findings Medium están corregidos
- ✅ Cada corrección tiene justificación escrita
- ✅ Cada corrección fue verificada ejecutando el endpoint
- ✅ Para cada fix, documentaste la decisión regenerar/editar
- ✅ No introdujiste nuevos bugs (smoke tests pasan)
- ✅ El codebase corregido se ejecuta sin errores
Siguiente cápsula: Entrega y Retrospectiva — El formato completo de entrega, la rúbrica de auto-evaluación, y las preguntas guía para la retrospectiva.
Debugging & Code Review with Claude Code — Módulo 8, Cápsula 04 Claude Code Agentic Development Path — Guía #6 de 11