Módulo 8: Proyecto Integrador

Fase 2: Debugging — Confirmar y Diagnosticar los Problemas de Runtime

Fase 2: Debugging — Confirmar y Diagnosticar los Problemas de Runtime

Descripción de la cápsula

En la fase anterior hiciste code review estático: leíste el código, lo comparaste contra los requisitos, y documentaste findings. Algunos de esos findings son evidentes con solo leer el código (un secret hardcoded, un import que no existe). Pero otros necesitan confirmación ejecutando el código: ¿realmente crashea con una lista vacía? ¿El filtro realmente devuelve datos incorrectos? ¿El error de tipo realmente ocurre con ese input?

Esta cápsula te guía a través del proceso de debugging sistemático del Módulo 6, aplicado al codebase de TaskFlow API. Vas a configurar el proyecto localmente, ejecutar la API, y confirmar cada finding que requiera evidencia de runtime. También vas a buscar bugs que solo se manifiestan al ejecutar — los que no puedes encontrar solo leyendo.


Setup del Proyecto

Paso 1: Crear la estructura de archivos

Crea un directorio para el proyecto y replica la estructura del codebase de la Cápsula 02:

mkdir taskflow-api
cd taskflow-api

mkdir routes
mkdir services

touch main.py config.py database.py models.py requirements.txt
touch routes/__init__.py routes/auth.py routes/tasks.py routes/users.py
touch services/__init__.py services/auth_service.py services/task_service.py

Copia el contenido de cada archivo desde la Cápsula 02 en el archivo correspondiente.

Paso 2: Instalar dependencias

python -m venv venv
source venv/bin/activate  # En macOS/Linux
# venv\Scripts\activate   # En Windows

pip install -r requirements.txt

Paso 3: Configurar variables de entorno

export JWT_SECRET_KEY="test-secret-for-debugging"
export DATABASE_URL="sqlite:///./taskflow.db"

Paso 4: Intentar ejecutar la aplicación

uvicorn main:app --reload --port 8000

Presta atención a lo que pasa. Si la aplicación no arranca, el error que ves es tu primer runtime bug confirmado. Documéntalo.


Qué Esperar al Ejecutar

Al intentar ejecutar la aplicación, es posible que encuentres errores inmediatos. Estos son los tipos de errores de arranque que debes esperar:

Errores de Import

ModuleNotFoundError: No module named 'pydantic_settings'
ImportError: cannot import name 'verify_hash' from 'bcrypt'

Si el código importa algo que no existe en las dependencias instaladas, obtendrás un ModuleNotFoundError o ImportError al arrancar. Revisa también imports de funciones específicas: que un paquete exista no garantiza que todas las funciones importadas sean reales.

Qué hacer:

  1. Identifica qué import falla
  2. Verifica si el paquete está en requirements.txt
  3. Verifica si la clase/función importada existe en la versión instalada
  4. Documéntalo como un finding de tipo Hallucination
  5. Corrige temporalmente el import para poder seguir debuggeando el resto

Errores de configuración

Si la aplicación depende de variables de entorno que no configuraste, puede fallar al arrancar.

Qué hacer:

  1. Lee el error para entender qué variable falta
  2. Configura las variables mínimas necesarias
  3. Evalúa si la aplicación debería manejar mejor la ausencia de configuración

Proceso de Debugging Sistemático

El proceso de 5 pasos (Módulo 6)

Para cada bug que necesites confirmar, sigue este proceso:

1. REPRODUCIR  → Crear el request exacto que causa el problema
2. AISLAR      → Identificar la línea o función exacta
3. DIAGNOSTICAR → Entender POR QUÉ ocurre el error
4. FIX         → Implementar la corrección (en Fase 3)
5. VERIFICAR   → Confirmar que el fix funciona (en Fase 3)

En esta fase solo ejecutas los pasos 1-3. Los pasos 4-5 son para la Fase de Corrección (Cápsula 04).

Herramientas de debugging

Para debuggear la API necesitas poder hacer requests. Estas son tus opciones:

Opción 1: curl desde terminal

# Health check
curl http://localhost:8000/health

# Registrar usuario
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "name": "Test User", "password": "testpassword123"}'

# Login
curl -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "password": "testpassword123"}'

# Crear tarea (reemplazar TOKEN con el token del login)
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer TOKEN" \
  -d '{"title": "Mi primera tarea", "description": "Descripción", "priority": "high"}'

Opción 2: FastAPI Swagger UI

Si la aplicación arranca correctamente, puedes usar la documentación interactiva en:

http://localhost:8000/docs

Swagger UI te permite enviar requests directamente desde el navegador.

Opción 3: httpie (alternativa a curl)

pip install httpie

# Health check
http GET localhost:8000/health

# Registrar usuario
http POST localhost:8000/auth/register \
  email=test@example.com \
  name="Test User" \
  password=testpassword123

Escenarios de Debugging

Escenario 1: Verificar el flujo de registro

Objetivo: Confirmar que el registro almacena la contraseña correctamente.

# 1. REPRODUCIR: Registrar un usuario
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "debug@test.com", "name": "Debug User", "password": "securepass123"}'

Qué verificar después del registro:

Abre la base de datos SQLite y verifica cómo se almacenó la contraseña:

sqlite3 taskflow.db "SELECT email, password FROM users WHERE email = 'debug@test.com'"

Preguntas de diagnóstico:

  • ¿La contraseña está hasheada o en texto plano?
  • Si está en texto plano, ¿dónde se debería hashear?
  • ¿El endpoint de registro llama a hash_password() antes de insertar?

Sigue el flujo completo en el código:

routes/auth.py: register() 
  → ¿Llama a hash_password()?
  → ¿Qué pasa con user.password antes del INSERT?
  → ¿Se inserta directamente sin transformación?

Escenario 2: Verificar la paginación

Objetivo: Confirmar que la paginación funciona correctamente.

Primero, crea suficientes tareas para probar:

TOKEN="tu-token-aquí"

# Crear 15 tareas
for i in $(seq 1 15); do
  curl -s -X POST http://localhost:8000/tasks/ \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $TOKEN" \
    -d "{\"title\": \"Tarea $i\", \"priority\": \"medium\"}"
done

Ahora prueba la paginación:

# Página 1 (debería devolver tareas 1-10)
curl -s "http://localhost:8000/tasks/?page=1&size=10" \
  -H "Authorization: Bearer $TOKEN" | python -m json.tool

# Página 2 (debería devolver tareas 11-15)
curl -s "http://localhost:8000/tasks/?page=2&size=10" \
  -H "Authorization: Bearer $TOKEN" | python -m json.tool

Preguntas de diagnóstico:

  • ¿La página 1 devuelve las 10 primeras tareas?
  • ¿La página 2 devuelve las 5 restantes?
  • ¿O la página 1 está vacía y la página 2 tiene las primeras 10?
  • ¿Cuál es la fórmula de offset que usa el código?

Revisa el cálculo de offset en services/task_service.py:

offset = page * size

Para page=1, size=10: offset = 10
→ Se salta las primeras 10 tareas en la página 1

¿Es correcto? ¿Cuál debería ser la fórmula para que la primera página (page=1) empiece desde el offset 0?

Escenario 3: Verificar autorización entre usuarios

Objetivo: Confirmar que un usuario no puede acceder a las tareas de otro.

# Registrar dos usuarios
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "user1@test.com", "name": "User 1", "password": "password123"}'

curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "user2@test.com", "name": "User 2", "password": "password123"}'

# Login con cada usuario
TOKEN1=$(curl -s -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user1@test.com", "password": "password123"}' | python -c "import sys, json; print(json.load(sys.stdin)['access_token'])")

TOKEN2=$(curl -s -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user2@test.com", "password": "password123"}' | python -c "import sys, json; print(json.load(sys.stdin)['access_token'])")

# User 1 crea una tarea
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Tarea secreta de User 1", "priority": "high"}'

Ahora la prueba clave:

# User 2 intenta acceder a la tarea de User 1
# Asumiendo que la tarea tiene id=1
curl -s http://localhost:8000/tasks/1 \
  -H "Authorization: Bearer $TOKEN2"

Preguntas de diagnóstico:

  • ¿User 2 puede ver la tarea de User 1?
  • ¿Qué código HTTP devuelve? ¿404 o 403?
  • ¿Dónde se verifica la propiedad en el código?
  • Según RF-05.6, ¿qué debería devolver?

Revisa services/task_service.py, función get_task_by_id():

  • ¿La query filtra por user_id?
  • ¿O devuelve la tarea sin importar quién la posee?

Escenario 4: Verificar las estadísticas

Objetivo: Confirmar que las estadísticas se calculan correctamente.

# Crear tareas con diferentes estados
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Tarea pending", "status": "pending"}'

curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Tarea completed", "status": "completed"}'

# Eliminar una tarea
curl -X DELETE http://localhost:8000/tasks/1 \
  -H "Authorization: Bearer $TOKEN1"

# Obtener estadísticas
curl -s http://localhost:8000/users/stats \
  -H "Authorization: Bearer $TOKEN1" | python -m json.tool

Preguntas de diagnóstico:

  • ¿Las estadísticas incluyen la tarea eliminada?
  • Según RF-04.2, ¿deberían excluir tareas con soft delete?
  • ¿Cómo implementa el delete? ¿Es soft delete (cambiar estado) o hard delete (eliminar fila)?
  • Si es hard delete, ¿viola RF-03.6?

Escenario 5: Verificar las estadísticas con cero tareas

Objetivo: Confirmar que las estadísticas funcionan con un usuario que no tiene tareas.

# Registrar un usuario nuevo sin tareas
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "empty@test.com", "name": "Empty User", "password": "password123"}'

TOKEN_EMPTY=$(curl -s -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "empty@test.com", "password": "password123"}' | python -c "import sys, json; print(json.load(sys.stdin)['access_token'])")

# Obtener estadísticas sin tener tareas
curl -s http://localhost:8000/users/stats \
  -H "Authorization: Bearer $TOKEN_EMPTY"

Preguntas de diagnóstico:

  • ¿Qué respuesta obtienes?
  • ¿Obtienes un error 500?
  • Si hay un error, ¿cuál es el stack trace?
  • ¿Dónde ocurre el error exactamente?

Pista: revisa la función get_user_stats() en task_service.py. ¿Qué pasa cuando total = 0?

Escenario 6: Probar el endpoint de búsqueda

Objetivo: Confirmar si hay vulnerabilidades en el endpoint de búsqueda de usuarios.

# Búsqueda normal
curl -s "http://localhost:8000/users/search?query=Test"

# Intento de SQL injection
curl -s "http://localhost:8000/users/search?query=test'%20OR%20'1'='1"

# Intento de extraer datos
curl -s "http://localhost:8000/users/search?query=test'%20UNION%20SELECT%20password,email,name%20FROM%20users--"

Preguntas de diagnóstico:

  • ¿El endpoint requiere autenticación?
  • ¿La búsqueda es vulnerable a SQL injection?
  • ¿Puede un atacante extraer contraseñas con UNION SELECT?
  • ¿Qué debería tener este endpoint: autenticación y parameterized queries?

Escenario 7: Probar validaciones de entrada

Objetivo: Confirmar que las validaciones coinciden con los requisitos.

# Intentar registrar con contraseña corta (RF-01.4 dice 8 caracteres mínimo)
curl -X POST http://localhost:8000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "weak@test.com", "name": "Weak", "password": "1234"}'

# Intentar crear tarea con título de 1 carácter (RF-05.1 dice mínimo 3)
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "A", "priority": "medium"}'

# Intentar crear tarea con prioridad inválida (RF-05.2)
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Tarea test", "priority": "urgente"}'

# Intentar paginación con page=0 (RF-05.4 dice page >= 1)
curl -s "http://localhost:8000/tasks/?page=0&size=10" \
  -H "Authorization: Bearer $TOKEN1"

Preguntas de diagnóstico:

  • ¿El sistema acepta una contraseña de 4 caracteres? ¿Debería?
  • ¿El sistema acepta un título de 1 carácter? ¿Debería?
  • ¿El sistema acepta una prioridad "urgente"? ¿Debería?
  • ¿El sistema acepta page=0? ¿Debería?

Escenario 8: Probar el soft delete

Objetivo: Confirmar que el delete funciona según los requisitos.

# Crear una tarea
curl -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Tarea para eliminar", "priority": "low"}'

# Anotar el ID de la tarea creada, por ejemplo id=5

# Eliminar la tarea
curl -X DELETE http://localhost:8000/tasks/5 \
  -H "Authorization: Bearer $TOKEN1"

# Verificar en la base de datos
sqlite3 taskflow.db "SELECT * FROM tasks WHERE id = 5"

Preguntas de diagnóstico:

  • ¿La tarea sigue existiendo en la base de datos con estado "deleted"?
  • ¿O fue eliminada completamente (hard delete)?
  • Según RF-03.6, ¿qué debería pasar?

Usando Claude Code para Debugging

Cuándo usar Claude Code

Claude Code es útil durante el debugging para:

1. Interpretar stack traces

Si obtienes un error 500, copia el stack trace y pásalo a Claude Code:

Estoy debuggeando esta API FastAPI. Al hacer GET /users/stats
con un usuario sin tareas, obtengo este error:

[pegar stack trace completo]

¿Qué está causando este error?

Claude Code puede identificar rápidamente la línea exacta y el tipo de error. Pero recuerda: verifica su diagnóstico mirando el código tú mismo.

2. Verificar si un import/API existe

¿La clase BaseSettings existe en el paquete pydantic_settings?
¿O está en pydantic directamente? Estoy usando pydantic 2.9.0.

3. Entender comportamiento de SQLite

En SQLite, si hago dict() sobre un sqlite3.Row, 
¿obtengo un diccionario con las columnas como keys?

Cuándo NO usar Claude Code

  • ❌ "Debuggea esta API por mí" — el debugging es tu ejercicio
  • ❌ "¿Hay bugs en este código?" — eso es lo que estás determinando
  • ❌ Aceptar un diagnóstico sin verificar tú mismo que es correcto

Cómo documentar el uso de Claude Code

Para cada vez que uses Claude Code durante el debugging, documenta:

## Uso de Claude Code #1

**Pregunta:** [Qué le pregunté]
**Respuesta:** [Resumen de su respuesta]
**Verificación:** [Cómo verifiqué que la respuesta es correcta]
**Resultado:** [Correcto / Parcialmente correcto / Incorrecto]

Esta documentación es parte de tu proceso y se evalúa en la sección de "Proceso" (20% de la evaluación).


Template de Documentación de Debugging

Para cada bug de runtime que confirmes, documenta el proceso completo:

# Debugging Log — TaskFlow API

## Bug #1: [Título descriptivo]

### 1. Reproducir
- **Request:** [curl command o descripción del request]
- **Response esperada:** [Qué debería pasar según los requisitos]
- **Response actual:** [Qué pasó realmente]
- **Error/Output:** [Stack trace o respuesta incorrecta]

### 2. Aislar
- **Archivo:** [Dónde está el problema]
- **Función:** [Nombre de la función]
- **Línea(s):** [Número(s) de línea]
- **Flujo de ejecución:** [Cómo llega la ejecución a este punto]

### 3. Diagnosticar
- **Causa raíz:** [Por qué ocurre el error]
- **Por qué no se detectó en code review estático:** [Explicación]
- **Categoría:** [Runtime / Edge Case / Logic / etc.]

### 4. Fix propuesto
[Se implementa en la Fase de Corrección — Cápsula 04]

---

## Bug #2: [Título descriptivo]
[Mismo formato]

Debugging Manual vs Claude Code

Cuándo el debugging manual es necesario

Hay situaciones donde Claude Code no puede ayudar efectivamente:

1. Estado de la base de datos

Claude Code no puede ver tu base de datos. Tú necesitas inspeccionar directamente:

# Ver la estructura de las tablas
sqlite3 taskflow.db ".schema"

# Ver los datos almacenados
sqlite3 taskflow.db "SELECT * FROM users"
sqlite3 taskflow.db "SELECT * FROM tasks"

# Verificar si las contraseñas están hasheadas
sqlite3 taskflow.db "SELECT email, password FROM users"

# Verificar si el delete es soft o hard
sqlite3 taskflow.db "SELECT * FROM tasks WHERE status = 'deleted'"

2. Comportamiento del endpoint en vivo

Claude Code no puede ejecutar tu API. Tú necesitas:

  • Hacer el request real
  • Observar la respuesta real
  • Comparar contra lo esperado

3. Interacción entre componentes

Algunos bugs solo se manifiestan cuando los componentes interactúan. Por ejemplo, el endpoint de tasks llama al service que llama a la database. Un bug puede ser que la query es correcta pero el service interpreta mal el resultado. Esto requiere tracing manual.

Cuándo Claude Code ayuda más

1. Interpretar errores crípticos

# Este error no es obvio:
# TypeError: 'NoneType' object is not subscriptable

# Claude Code puede explicar que estás intentando 
# acceder a un índice o key de un valor None

2. Verificar APIs y signatures

"¿jwt.encode() en PyJWT 2.9 retorna str o bytes?"
"¿bcrypt.hashpw() requiere bytes o acepta str?"

3. Sugerir hipótesis de diagnóstico

Cuando tienes un error pero no sabes por dónde empezar, Claude Code puede sugerir las 3 causas más probables. Pero verifica cada una tú mismo.


Checklist de Debugging Completo

Antes de pasar a la Fase de Corrección, verifica que has ejecutado estos escenarios:

Flujo de autenticación

  • Registrar un usuario y verificar cómo se almacena la contraseña
  • Login con credenciales correctas
  • Login con credenciales incorrectas
  • Acceder a endpoint protegido con token válido
  • Acceder a endpoint protegido sin token

Flujo de tareas

  • Crear una tarea con datos válidos
  • Crear una tarea con título vacío o muy corto
  • Crear una tarea con prioridad inválida
  • Listar tareas con paginación (page=1, page=2)
  • Obtener detalle de tarea propia
  • Intentar obtener detalle de tarea de otro usuario
  • Actualizar tarea propia
  • Eliminar tarea y verificar si es soft o hard delete

Estadísticas

  • Obtener estadísticas con varias tareas
  • Obtener estadísticas con cero tareas
  • Verificar que las estadísticas excluyen tareas eliminadas

Seguridad

  • Probar SQL injection en búsqueda de usuarios
  • Verificar que el endpoint de búsqueda requiere auth
  • Probar SQL injection en filtros de tareas

Validaciones

  • Contraseña menor a 8 caracteres
  • Título menor a 3 caracteres
  • Página 0 o negativa
  • Prioridad o estado inválido

Escenario 9: Verificar el Flujo Completo de Actualización

Objetivo: Confirmar que la actualización de tareas funciona correctamente con todos los campos.

# Crear una tarea
TASK=$(curl -s -X POST http://localhost:8000/tasks/ \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Tarea original", "priority": "low", "status": "pending"}')

echo $TASK
TASK_ID=$(echo $TASK | python -c "import sys, json; print(json.load(sys.stdin)['id'])")

# Actualizar solo el título
curl -s -X PUT http://localhost:8000/tasks/$TASK_ID \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"title": "Tarea actualizada"}'

# Actualizar solo la prioridad
curl -s -X PUT http://localhost:8000/tasks/$TASK_ID \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN1" \
  -d '{"priority": "high"}'

# Verificar que se actualizó updated_at
curl -s http://localhost:8000/tasks/$TASK_ID \
  -H "Authorization: Bearer $TOKEN1" | python -m json.tool

Preguntas de diagnóstico:

  • ¿Se actualiza solo el campo enviado o se sobrescriben todos?
  • ¿Se actualiza el campo updated_at?
  • ¿Qué pasa si envías un campo con valor inválido (e.g., prioridad "urgente")?

Escenario 10: Verificar el Import de pydantic_settings

Objetivo: Confirmar si el import de pydantic_settings causa un error de arranque.

# Intenta importar directamente para verificar
python -c "from pydantic_settings import BaseSettings"

Preguntas de diagnóstico:

  • ¿El import falla con ModuleNotFoundError?
  • ¿El paquete pydantic-settings está en requirements.txt?
  • ¿La aplicación logra arrancar a pesar de este import?
  • ¿Se usa BaseSettings en algún lugar del archivo main.py?

Si el import falla, el error aparecerá al intentar ejecutar uvicorn main:app. Este es un finding que se confirma en segundos pero que muchos pasan por alto en el code review estático.

Escenario 11: Verificar Import de verify_hash en bcrypt

Objetivo: Confirmar si verify_hash existe en el paquete bcrypt.

python -c "from bcrypt import verify_hash"

Preguntas de diagnóstico:

  • ¿El import falla con ImportError?
  • ¿La función verify_hash aparece en la documentación de bcrypt?
  • ¿Se usa verify_hash en alguna parte del código o solo se importa?
  • ¿Qué función de bcrypt cumple realmente esa función? (checkpw)

Este es un tipo de hallucination más sutil que el de pydantic_settings: el paquete bcrypt sí existe y está instalado, pero la función verify_hash no existe en su API. Claude Code a veces "inventa" funciones dentro de paquetes reales.

Escenario 12: Verificar cursor.fetchall(as_dict=True)

Objetivo: Confirmar si fetchall() acepta el parámetro as_dict.

import sqlite3
conn = sqlite3.connect(":memory:")
cursor = conn.cursor()
cursor.execute("SELECT 1")
cursor.fetchall(as_dict=True)  # ¿TypeError?

Preguntas de diagnóstico:

  • ¿fetchall() de sqlite3.Cursor acepta parámetros?
  • ¿Qué TypeError obtienes al pasar as_dict=True?
  • ¿Cómo se obtienen resultados como diccionarios en sqlite3? (conn.row_factory = sqlite3.Row)

Este hallucination es particularmente peligroso porque no falla al arrancar — falla solo cuando la función get_tasks() se ejecuta con resultados reales.

Escenario 13: Verificar los Filtros de Tareas con SQL Injection

Objetivo: Confirmar si los filtros de tareas son vulnerables a SQL injection.

# Filtro de estado normal
curl -s "http://localhost:8000/tasks/?status=pending" \
  -H "Authorization: Bearer $TOKEN1"

# Intento de SQL injection via status
curl -s "http://localhost:8000/tasks/?status=pending'%20OR%20'1'='1" \
  -H "Authorization: Bearer $TOKEN1"

# Intento de SQL injection via priority
curl -s "http://localhost:8000/tasks/?priority=high'%20UNION%20SELECT%20*%20FROM%20users--" \
  -H "Authorization: Bearer $TOKEN1"

Preguntas de diagnóstico:

  • ¿El filtro con SQL injection devuelve más resultados que el filtro normal?
  • ¿Los parámetros status y priority se insertan con parámetros ? o con f-strings?
  • ¿Es este un hallazgo diferente al SQL injection en la búsqueda de usuarios?

Cómo Interpretar los Resultados

El request devuelve 200 pero datos incorrectos

Este es el tipo de bug más difícil de encontrar. El endpoint no crashea, no devuelve un error — simplemente devuelve datos que no son los correctos. Ejemplos:

  • La paginación devuelve las tareas equivocadas en cada página
  • Las estadísticas incluyen tareas que no deberían incluir
  • Un usuario puede ver tareas de otro usuario

Para detectar estos bugs, necesitas saber cuál es la respuesta correcta y compararla contra la respuesta actual. Por eso los requisitos funcionales son tu herramienta principal.

El request devuelve 500 (Internal Server Error)

Un error 500 indica un crash en el servidor. El stack trace aparecerá en la consola donde ejecutaste uvicorn. Copia el stack trace completo — contiene:

  1. La línea exacta donde ocurrió el error
  2. El tipo de excepción (TypeError, ZeroDivisionError, etc.)
  3. El traceback completo mostrando cómo se llegó a esa línea
# Para ver el stack trace, mira la terminal donde corre uvicorn
# Los errores 500 se imprimen automáticamente en la consola

El request devuelve 422 (Validation Error)

Un 422 indica que FastAPI rechazó el input porque no cumple con los modelos Pydantic. Esto puede ser correcto (validación funcionando) o incorrecto (validación demasiado estricta o demasiado laxa).

Verifica: ¿debería el input ser rechazado según los requisitos? Si sí, la validación funciona. Si no, la validación tiene un problema.

El request devuelve 401 o no devuelve 401 cuando debería

Endpoints que devuelven 401 sin token: correcto. Endpoints que devuelven datos sin token: incorrecto si deberían requerir autenticación.


Preguntas de Auto-evaluación

Antes de avanzar a la Fase de Corrección, responde estas preguntas:

  1. ¿Cuántos bugs encontraste solo con code review estático (Fase 1)?
  2. ¿Cuántos bugs adicionales encontraste al ejecutar el código (Fase 2)?
  3. ¿Hay findings de la Fase 1 que resultaron ser falsos positivos al probar?
  4. ¿Hay bugs de runtime que no habías detectado en el code review?
  5. ¿Cuántas veces usaste Claude Code? ¿Fue útil? ¿Verificaste sus respuestas?
  6. ¿Qué escenario de debugging fue el más revelador? ¿Por qué?
  7. ¿Hubo algún bug que te sorprendió al confirmarlo?

Estas preguntas alimentarán tu retrospectiva final (Cápsula 05).


Errores Comunes en Esta Fase

Error 1: No ejecutar realmente el código

Leer los escenarios y "deducir" qué pasaría no es lo mismo que ejecutar y observar. La sorpresa que te llevas al ver un error que no esperabas es parte del aprendizaje.

Error 2: Solo probar el happy path

Si solo pruebas con inputs válidos, confirmarás que "funciona" — pero no encontrarás los edge cases. Prueba con inputs vacíos, negativos, excesivos, y maliciosos.

Error 3: No documentar mientras debuggeas

Es tentador debuggear todo y documentar al final. Pero al final olvidas detalles: qué request exacto causó el error, cuál era el stack trace, qué verificaste. Documenta en tiempo real.

Error 4: Corregir bugs antes de documentar todos

Resiste la tentación de corregir cada bug apenas lo encuentras. Documenta todos primero (esta fase), prioriza (fase siguiente), y luego corrige en orden.

Error 5: No reiniciar la base de datos entre pruebas

Si corres muchas pruebas, los datos se acumulan y pueden interferir. Para cada escenario limpio:

rm taskflow.db
# Reiniciar la aplicación para recrear la DB

Error 6: No guardar los comandos ejecutados

Los comandos curl que usaste para reproducir cada bug son evidencia de tu proceso. Guárdalos en tu debugging log. Cuando escribas las justificaciones, necesitarás recordar exactamente qué request hiciste y qué respuesta obtuviste.

Error 7: Ignorar warnings del servidor

Además de errores 500, uvicorn puede imprimir warnings que no causan crash pero indican problemas. DeprecationWarning, por ejemplo, puede señalar uso de APIs obsoletas. Lee la consola del servidor, no solo las respuestas HTTP.


Debugging con Prints vs Debugging con Claude Code

Cuándo usar print statements

A veces la forma más rápida de entender qué pasa es agregar un print temporal:

def get_tasks(user_id, status=None, priority=None, page=1, size=10):
    # Debugging temporal
    print(f"DEBUG get_tasks: page={page}, size={size}")
    
    offset = page * size
    print(f"DEBUG offset calculado: {offset}")
    
    # ... resto del código

Los prints son útiles cuando:

  • Necesitas ver el valor de una variable en un punto específico
  • Claude Code no puede ver el estado de tu aplicación en runtime
  • Quieres confirmar qué rama de un if/else se ejecuta

Recuerda eliminar los prints cuando termines de debuggear.

Cuándo usar Claude Code

Claude Code es útil cuando:

  • Tienes un stack trace largo y confuso
  • No sabes por dónde empezar a buscar
  • Necesitas verificar si una API o import existe
  • Quieres una segunda opinión sobre una hipótesis

Ejemplo de pregunta efectiva a Claude Code:

Al ejecutar GET /users/stats para un usuario sin tareas,
obtengo ZeroDivisionError en services/task_service.py línea 118.

La línea es:
completion_percentage = by_status.get("completed", 0) / total * 100

¿Por qué falla y cuál sería el fix correcto?

Ejemplo de pregunta inefectiva:

Mi API no funciona, arréglala.

La diferencia: la primera da contexto específico, la segunda delega el trabajo completo.


Resumen de la Fase de Debugging

Al final de esta fase deberías tener:

  1. Findings document actualizado — con los findings del code review confirmados y bugs de runtime adicionales
  2. Debugging log — documentación del proceso de debugging para cada bug de runtime
  3. Lista priorizada — findings ordenados por severidad, listos para la fase de corrección
  4. Notas sobre uso de Claude Code — cuándo lo usaste, qué preguntaste, si fue útil

Tu findings document debería tener ahora entre 12 y 18 findings. Si tienes menos de 12, hay áreas que no exploraste suficientemente. Revisa el checklist de debugging y ejecuta los escenarios que te faltan.


Siguiente cápsula: Fase de Corrección — Cómo priorizar, implementar, y justificar cada fix, decidiendo cuándo regenerar con Claude Code vs editar manualmente.


Debugging & Code Review with Claude Code — Módulo 8, Cápsula 03 Claude Code Agentic Development Path — Guía #6 de 11