Español — Versión oficial en español de
bugfix-protocol.
Bugfix Protocol: Depuración Sistemática en 6 Fases
Enfoque estructurado para la resolución de errores — desde el análisis de síntomas hasta la verificación. Evita el ensayo y error sin rumbo y garantiza que las soluciones sean sostenibles.
Resumen y Propósito
| Fase | Nombre | Objetivo | Tiempo máx. |
|---|---|---|---|
| 1 | Verificaciones rápidas | Descartar causas evidentes | 2 min |
| 2 | Diagnóstico | Localizar la causa raíz | 10 min |
| 3 | Prueba aislada | Hacer que el error sea reproducible | 5 min |
| 4 | Corrección | Corrección mínima | 10 min |
| 5 | Verificación | Verificar la solución + comprobar efectos secundarios | 5 min |
| 6 | Documentación | Preservar el conocimiento | 2 min |
Regla de los 20 minutos: Si no hay avances tras 20 minutos, cambia de enfoque o busca ayuda.
Fase 1: Verificaciones rápidas (2 min)
Antes de profundizar, comprueba las causas más comunes:
Lista de verificación
- ¿Error de sintaxis? Lee atentamente el mensaje de error, comprueba la línea
- ¿Error de importación? ¿Módulo instalado? ¿Nombre correcto? ¿Importación circular?
- ¿Error tipográfico? ¿Nombres de variables/funciones correctos?
- ¿Tipo de datos incorrecto? ¿Cadena en lugar de int? ¿None donde se esperaba un objeto?
- ¿Caché desactualizada? Elimina
__pycache__, reinicia - ¿Entorno incorrecto? ¿venv correcto activo? ¿Versión de Python correcta?
- ¿Codificación? UTF-8 vs. cp1252 (Windows clásico)
Acciones rápidas
# Limpiar caché
find . -name "__pycache__" -type d -exec rm -rf {} + 2>&1
find . -name "*.pyc" -delete 2>&1
# Comprobar importaciones
python -c "import modulename"
# Comprobar sintaxis
python -m py_compile file.py
Fase 2: Diagnóstico (10 min)
Estrategia: De fuera hacia dentro
- Analizar mensaje de error — Lee la traza de pila (traceback) de abajo hacia arriba
- Comprobar cambios recientes —
git diff,git log --oneline -10 - Usar herramientas de diagnóstico — Utiliza herramientas específicas del proyecto
Herramientas de diagnóstico (Ejemplos)
Según el proyecto, las secuencias de comandos de diagnóstico especializadas pueden ser útiles:
| Herramienta | Propósito |
|---|---|
import_diagnose.py |
Analizar problemas de importación |
method_analyzer.py |
Comprobar firmas de métodos |
env_checker.py |
Validar variables de entorno/rutas |
Nota: Crea herramientas de diagnóstico específicas del proyecto o utiliza las existentes. Lo que importa es el enfoque sistemático, no la herramienta específica.
Técnicas de depuración
# 1. Depuración con print (rápida pero efectiva)
print(f"DEBUG: variable={variable!r}, type={type(variable)}")
# 2. Puntos de interrupción (interactivo)
breakpoint() # Python 3.7+
# 3. Traza de pila extendida
import traceback
traceback.print_exc()
# 4. Registro en lugar de print
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug(f"State: {state!r}")
Fase 3: Prueba aislada (5 min)
Ejemplo mínimo reproducible (MRE)
Objetivo: Reproducir el error con el código mínimo.
# test_bug.py — Prueba de reproducción mínima
"""
Bug: [Descripción corta]
Expected: [Lo que debería suceder]
Actual: [Lo que sucede en su lugar]
"""
# Configuración mínima
# ... solo lo esencial
# Desencadenante del error
# ... código exacto que desencadena el error
# Resultado esperado
# assert result == expected, f"Got {result}"
Estrategias de aislamiento
- Nuevo archivo: Reproduce el error en un archivo separado
- Eliminar dependencias: Una por una, hasta que el error desaparezca
- Búsqueda binaria: Divide el bloque de código a la mitad, comprueba qué mitad contiene el error
- Git bisect:
git bisect start,git bisect bad,git bisect good <commit>
Fase 4: Corrección (10 min)
Principios
- Mínimo: Cambia lo menos posible
- Comprender: Nunca corrijas a ciegas — comprende POR QUÉ está roto
- Una cosa: Una corrección por commit, no soluciones múltiples problemas a la vez
- Compatible hacia atrás: No rompas la funcionalidad existente
Patrones de corrección
# MAL: Tratar el síntoma
try:
result = broken_function()
except: # Ignorar todo
result = default_value
# BIEN: Corregir la causa raíz
def broken_function():
if input_data is None: # Causa real: falta comprobación de None
return default_value
return process(input_data)
Categorías comunes de corrección
| Categoría | Corrección típica |
|---|---|
| None/Null | Cláusula de guarda: if x is None: return default |
| Error de índice | Comprobación de límites: if i < len(lst) |
| Error de tipo | Conversión explícita: str(x), int(x) |
| Error de importación | Corregir ruta, instalar paquete |
| Codificación | Especificar UTF-8 explícitamente: encoding='utf-8' |
| Condición de carrera | Bloqueo/Mutex, o cambiar el orden |
| Error de estado | Comprobar inicialización, añadir restablecimiento |
Fase 5: Verificación (5 min)
Lista de verificación
- Error corregido: El problema original ya no ocurre
- MRE supera: La prueba aislada se ejecuta correctamente
- Sin regresión: Las pruebas existentes siguen pasando
- Casos límite: Entrada vacía, None, datos grandes probados
- Herramientas del proyecto: Comprueba el directorio de herramientas del proyecto para herramientas de prueba/validación relevantes
Comandos de prueba
# Pruebas unitarias
python -m pytest tests/ -v
# Solo pruebas afectadas
python -m pytest tests/test_module.py -v -k "test_name"
# Verificación de tipos
python -m mypy file.py
# Linter
python -m flake8 file.py
Fase 6: Documentación (2 min)
Plantilla de informe de errores
## Bug Report: [Título corto]
**Date:** YYYY-MM-DD
**Severity:** critical / high / medium / low
**Component:** [Módulo/Archivo]
### Symptom
[Lo que ve el usuario / mensaje de error]
### Root Cause
[Causa raíz técnica]
### Fix
[Qué se cambió + por qué]
### Affected Files
- `file1.py` — [Cambio]
- `file2.py` — [Cambio]
### Prevention
[¿Cómo se puede prevenir este tipo de error en el futuro?]
Formato del mensaje de commit
fix: [Descripción corta de la corrección]
Cause: [Causa raíz en una frase]
Fix: [Qué se cambió]
Test: [Cómo se verificó]
PyQt6 / Depuración de GUI — Trampas comunes
Esta sección es relevante para proyectos de GUI de escritorio con PyQt6/PySide6.
Las 5 principales trampas de PyQt6
| Trampa | Problema | Solución |
|---|---|---|
| Desconexión Signal-Slot | Señal conectada pero el manejador no se ejecuta | print en manejador, comprobar firma |
| Seguridad de hilos (Thread Safety) | Actualización de GUI desde hilo de trabajo | QMetaObject.invokeMethod o usar señal |
| Cascada de diseño (Layout) | Widget invisible/desplazado | widget.show(), comprobar jerarquía de layout |
| Bloqueo del bucle de eventos | La GUI se congela | Mover operaciones largas a QThread |
| Recolección de basura | El widget desaparece repentinamente | Mantener referencia como self.widget |
Ayudantes de depuración de PyQt6
# Imprimir jerarquía de widgets
def dump_widget_tree(widget, indent=0):
print(" " * indent + f"{widget.__class__.__name__}: {widget.objectName()}")
for child in widget.findChildren(QWidget):
if child.parent() == widget:
dump_widget_tree(child, indent + 2)
# Depuración de señales
from PyQt6.QtCore import QObject
original_connect = QObject.connect
def debug_connect(self, *args, **kwargs):
print(f"CONNECT: {self.__class__.__name__} -> {args}")
return original_connect(self, *args, **kwargs)
Referencia rápida
¿ERROR ENCONTRADO?
|
v
[Fase 1: Verificaciones rápidas] ── ¿Evidente? -> CORREGIR
|
v
[Fase 2: Diagnóstico] ──────────── ¿Causa clara? -> Fase 4
|
v
[Fase 3: Prueba aislada] ──────── ¿Reproducible? -> Fase 4
| |
| ¿No reproducible?
| |
| Añadir registros,
| esperar recurrencia
v
[Fase 4: Corrección] ───────────── Mínima + comprendida
|
v
[Fase 5: Verificación] ────────── ¿Pruebas en verde? -> Fase 6
| |
| ¿Pruebas en rojo? -> Volver a Fase 4
v
[Fase 6: Documentación] ───────── Informe de error + commit
Regla de los 20 minutos
Si estás atascado tras 20 minutos:
- Cambiar de enfoque — Prueba una técnica de depuración diferente
- Pato de goma (Rubber duck) — Explica el problema en voz alta (o escríbelo)
- Toma un descanso — Aléjate durante 5 minutos, regresa con mente fresca
- Busca ayuda — Pregunta a un colega, Stack Overflow, documentación
- Restablecer —
git stash, empieza completamente de nuevo