Depurar código es el proceso de encontrar y corregir errores en un programa. Es una habilidad que todo programador usa a diario, y que muchos aprenden de forma intuitiva sin desarrollarla de manera sistemática. El resultado es pasar horas buscando un bug que con la técnica correcta se encontraría en minutos.
En esta guía aprenderás los tipos de errores que existen, las técnicas más efectivas para encontrarlos y las herramientas que los profesionales usan en Python y JavaScript.
Tipos de errores
Antes de aprender a depurar, conviene entender con qué tipo de error estás lidiando. No todos se encuentran de la misma manera.
Errores de sintaxis
El lenguaje no puede interpretar el código porque está mal escrito. Son los más fáciles de encontrar: el intérprete o compilador te dice exactamente en qué línea está el problema antes de ejecutar nada.
# Python: error de sintaxis
def saludar(nombre) # ← falta los dos puntos
print(f"Hola, {nombre}")
# SyntaxError: expected ':'
# Más ejemplos de errores de sintaxis:
if edad > 18 # falta los dos puntos
print("adulto")
print("hola" # falta el paréntesis de cierre
// JavaScript: errores de sintaxis
function saludar(nombre { // ← falta el paréntesis de cierre
console.log(`Hola, ${nombre}`);
}
// SyntaxError: Unexpected token '{'
Errores de ejecución (runtime errors)
El código tiene sintaxis correcta pero algo falla cuando se ejecuta. Se producen en una línea específica y el programa se detiene con un mensaje de error.
# Python: errores de ejecución comunes
# TypeError: operar con tipos incompatibles
edad = "25"
print(edad + 1) # TypeError: can only concatenate str (not "int") to str
# ZeroDivisionError: dividir entre cero
resultado = 10 / 0 # ZeroDivisionError: division by zero
# IndexError: acceder a un índice que no existe
lista = [1, 2, 3]
print(lista[5]) # IndexError: list index out of range
# KeyError: buscar una clave que no existe en un diccionario
usuario = {"nombre": "Ana"}
print(usuario["email"]) # KeyError: 'email'
# AttributeError: usar un método que no existe
numero = 42
numero.upper() # AttributeError: 'int' object has no attribute 'upper'
# NameError: usar una variable que no ha sido definida
print(resultado) # NameError: name 'resultado' is not defined
Errores de lógica
Son los más difíciles de encontrar: el código se ejecuta sin errores pero produce resultados incorrectos. El programa hace algo diferente a lo que el programador tenía en mente.
# Python: errores de lógica
# Calcular el promedio incorrectamente
def promedio(numeros):
total = 0
for n in numeros:
total += n
return total / len(numeros) + 1 # ← el +1 no debería estar aquí
print(promedio([10, 20, 30])) # 21.0 en lugar de 20.0
# No hay ningún mensaje de error. El resultado simplemente está mal.
# Condición invertida
def es_adulto(edad):
if edad < 18: # ← debería ser >=
return True
return False
print(es_adulto(20)) # False ← incorrecto
print(es_adulto(15)) # True ← incorrecto
Técnica 1: leer el mensaje de error con atención
La habilidad más básica y más subestimada de la depuración. Cuando un programa falla con un error de ejecución, el mensaje contiene exactamente la información que necesitas: el tipo de error, la descripción del problema y el traceback (la pila de llamadas que llevó al error).
# Python: ejemplo de traceback completo
# El traceback se lee de abajo hacia arriba
def calcular_precio(precio, descuento):
return precio - precio * (descuento / 100)
def procesar_pedido(pedido):
precio_final = calcular_precio(pedido["precio"], pedido["descuento"])
return precio_final
pedido = {"precio": 100} # falta la clave "descuento"
print(procesar_pedido(pedido))
# Traceback que produce:
# Traceback (most recent call last): ← léelo de abajo hacia arriba
# File "tienda.py", line 10, in <module>
# print(procesar_pedido(pedido)) ← aquí se llamó a la función
# File "tienda.py", line 7, in procesar_pedido
# precio_final = calcular_precio(...) ← aquí se llamó a calcular_precio
# File "tienda.py", line 2, in calcular_precio
# return precio - precio * (descuento / 100) ← aquí ocurrió el error
# KeyError: 'descuento' ← tipo de error + causa exacta
# La última línea te dice QUÉ salió mal.
# Las líneas del traceback te dicen DÓNDE y CÓMO llegó el programa hasta ahí.
Muchos principiantes ven el traceback y se abruman por su longitud. El truco es ir directamente a la última línea (el tipo de error) y luego buscar la primera línea del traceback que corresponde a tu código, no a una librería externa.
Técnica 2: print debugging
El método más simple y más universalmente usado: añadir instrucciones print() o console.log() para ver el estado del programa en puntos específicos. No es glamuroso, pero funciona y no requiere ninguna herramienta especial.
# Python: print debugging
def buscar_maximo(numeros):
print(f"[DEBUG] Entrada: {numeros}") # ver qué recibe la función
if not numeros:
print("[DEBUG] Lista vacía, devolviendo None")
return None
maximo = numeros[0]
print(f"[DEBUG] Máximo inicial: {maximo}")
for i, n in enumerate(numeros):
print(f"[DEBUG] Comparando {n} con el máximo actual {maximo}")
if n > maximo:
maximo = n
print(f"[DEBUG] Nuevo máximo: {maximo}")
print(f"[DEBUG] Resultado final: {maximo}")
return maximo
buscar_maximo([34, 7, 89, 12])
# [DEBUG] Entrada: [34, 7, 89, 12]
# [DEBUG] Máximo inicial: 34
# [DEBUG] Comparando 34 con el máximo actual 34
# [DEBUG] Comparando 7 con el máximo actual 34
# [DEBUG] Comparando 89 con el máximo actual 34
# [DEBUG] Nuevo máximo: 89
# [DEBUG] Comparando 12 con el máximo actual 89
# [DEBUG] Resultado final: 89
# Buenas prácticas para el print debugging:
# 1. Añade una etiqueta clara para distinguir los prints de depuración
print(f"[DEBUG] valor de x: {x}") # fácil de buscar y eliminar después
# 2. Imprime el tipo además del valor cuando no estés seguro
print(f"[DEBUG] tipo={type(x)}, valor={x}")
# 3. Imprime antes y después de la operación sospechosa
print(f"[DEBUG] antes de calcular: total={total}")
total = calcular_algo(datos)
print(f"[DEBUG] después de calcular: total={total}")
# 4. Usa pprint para estructuras complejas (listas y diccionarios grandes)
from pprint import pprint
pprint(datos_complejos) # más legible que print() para estructuras anidadas
// JavaScript: console debugging
function procesarUsuarios(usuarios) {
console.log('[DEBUG] Usuarios recibidos:', usuarios);
const activos = usuarios.filter(u => u.activo);
console.log('[DEBUG] Activos:', activos.length);
const resultado = activos.map(u => {
console.log('[DEBUG] Procesando:', u.nombre);
return { ...u, procesado: true };
});
console.log('[DEBUG] Resultado final:', resultado);
return resultado;
}
// Herramientas adicionales de console en JavaScript:
console.table(usuarios); // muestra arrays de objetos como tabla
console.group('Procesamiento'); // agrupa los logs siguientes
console.log('detalle 1');
console.log('detalle 2');
console.groupEnd();
console.time('operacion'); // medir cuánto tarda algo
// ... código a medir ...
console.timeEnd('operacion'); // imprime el tiempo transcurrido
console.warn('¡Esto es una advertencia!');
console.error('¡Esto es un error!');
Técnica 3: el depurador (debugger)
Un depurador es una herramienta que te permite pausar la ejecución de tu programa en cualquier punto, ver el estado de todas las variables en ese momento, y avanzar el programa instrucción a instrucción. Es mucho más potente que el print debugging para errores complejos.
El depurador de Python en VS Code
VS Code tiene un depurador integrado para Python. Para usarlo:
- Abre tu archivo Python en VS Code.
- Haz clic en el número de línea donde quieres pausar la ejecución. Aparece un punto rojo: ese es el breakpoint.
- Pulsa F5 o ve a Run → Start Debugging.
- El programa se ejecuta y se pausa en el breakpoint.
Una vez pausado, tienes estas acciones:
- Step Over (F10): ejecuta la línea actual y pasa a la siguiente, sin entrar en funciones.
- Step Into (F11): ejecuta la línea actual y entra dentro de la función que se llama.
- Step Out (Shift+F11): sale de la función actual y vuelve al código que la llamó.
- Continue (F5): continúa la ejecución hasta el siguiente breakpoint.
En el panel de Variables (izquierda) ves el valor de todas las variables locales y globales en ese momento. Puedes pasar el cursor por encima de cualquier variable en el código para ver su valor actual.
pdb: el depurador de Python en la terminal
# Insertar un breakpoint directamente en el código
def calcular_descuento(precio, porcentaje):
breakpoint() # la ejecución se pausa aquí (Python 3.7+)
descuento = precio * porcentaje / 100
return precio - descuento
calcular_descuento(100, 20)
# Cuando el programa llega a breakpoint(), se abre el prompt de pdb:
# > archivo.py(3)calcular_descuento()
# -> descuento = precio * porcentaje / 100
# (Pdb)
# Comandos de pdb más útiles:
# n (next): ejecutar la línea actual y pasar a la siguiente
# s (step): entrar en la función que se llama
# c (continue): continuar hasta el siguiente breakpoint
# p expresion: imprimir el valor de una expresión
# pp expresion: imprimir con pprint (más legible)
# l (list): mostrar el código alrededor de la línea actual
# q (quit): salir del depurador
# h (help): mostrar ayuda
# where: mostrar la pila de llamadas actual
# En el prompt de pdb puedes ejecutar cualquier expresión Python:
# (Pdb) p precio → 100
# (Pdb) p porcentaje → 20
# (Pdb) p precio * porcentaje / 100 → 20.0
El depurador en el navegador (JavaScript)
// Insertar un breakpoint en JavaScript
function calcularDescuento(precio, porcentaje) {
debugger; // el navegador pausa la ejecución aquí si DevTools está abierto
const descuento = precio * porcentaje / 100;
return precio - descuento;
}
Para usar el depurador del navegador:
- Abre Chrome DevTools con F12.
- Ve a la pestaña Sources.
- Encuentra tu archivo JavaScript y haz clic en el número de línea para añadir un breakpoint.
- Recarga la página o ejecuta la acción que llama a ese código.
- El navegador pausa la ejecución en el breakpoint y puedes usar los controles de Step Over, Step Into, etc.
Técnica 4: reducir el problema
Cuando tienes un bug en un programa grande, el primer paso es aislar el problema: encontrar el mínimo código que reproduce el error. Esto tiene dos ventajas: te ayuda a entender el problema y hace mucho más fácil buscar ayuda online o pedir ayuda a alguien.
# Supongamos que tienes 200 líneas de código y un bug en algún lugar.
# Estrategia de búsqueda binaria:
# 1. Pon un print/breakpoint en el punto medio del código
# 2. ¿El error ocurre antes o después de ese punto?
# 3. Descarta la mitad que no tiene el bug
# 4. Repite con la mitad restante
# En pocos pasos localizas la zona exacta, aunque el código sea muy largo.
# Ejemplo: proceso de pago que falla en algún punto
def procesar_pago(pedido):
pedido = validar_pedido(pedido)
# ← ¿el error es antes o después de aquí? → print(pedido)
pedido = calcular_impuestos(pedido)
# ← ¿el error es antes o después de aquí? → print(pedido)
resultado = cobrar_tarjeta(pedido)
# ← ¿el error es antes o después de aquí? → print(resultado)
enviar_confirmacion(resultado)
return resultado
Técnica 5: el método del pato de goma
Explica tu código en voz alta, línea a línea, a un objeto inanimado (un pato de goma, una planta, una taza). Suena ridículo pero funciona. El proceso de verbalizar lo que hace el código te obliga a ser explícito sobre cada suposición que estás haciendo, y a menudo ves el error en el momento en que lo describes.
"Esta función recibe una lista de usuarios... itera sobre cada uno... ah, espera, aquí estoy asumiendo que todos tienen el campo 'email' pero los usuarios sin cuenta no lo tienen."
Si no tienes un pato a mano, escribe la explicación. El efecto es el mismo.
Técnica 6: leer el código hacia atrás
Cuando tienes un resultado incorrecto, parte del resultado y trabaja hacia atrás. En lugar de seguir el flujo del código de principio a fin, pregúntate: "¿Qué operación produjo este valor? ¿Qué operación produjo el valor que entró a esa operación?" y así sucesivamente hasta encontrar dónde el valor empezó a ser incorrecto.
# El total final es 0 pero debería ser 150. ¿Por qué?
total = calcular_total(pedido) # → 0
# ¿Por qué es 0? Veamos calcular_total...
def calcular_total(pedido):
subtotal = sum(p["precio"] for p in pedido["productos"]) # → 150
descuento = calcular_descuento(subtotal, pedido["descuento"]) # → ?
return subtotal - descuento # → 0, lo que significa que descuento = 150
# ¿Por qué descuento es 150? Veamos calcular_descuento...
def calcular_descuento(subtotal, porcentaje):
return subtotal * porcentaje # ← AHÍ ESTÁ: falta dividir entre 100
# debería ser: return subtotal * porcentaje / 100
Herramientas de depuración en Python
# 1. logging: mejor que print() para proyectos reales
import logging
# Configurar el nivel de logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def calcular_precio(precio, descuento):
logging.debug(f"Calculando precio: precio={precio}, descuento={descuento}")
if descuento < 0 or descuento > 100:
logging.error(f"Descuento inválido: {descuento}")
raise ValueError(f"El descuento debe estar entre 0 y 100")
resultado = precio * (1 - descuento / 100)
logging.info(f"Precio calculado: {resultado}")
return resultado
# Ventajas sobre print():
# - Niveles de severidad: DEBUG, INFO, WARNING, ERROR, CRITICAL
# - Puedes activar/desactivar sin borrar el código
# - Puedes escribir a archivo en producción
# - Incluye fecha/hora automáticamente
# En producción, cambias el nivel a WARNING y solo ves los errores importantes
logging.basicConfig(level=logging.WARNING)
# 2. assert: verificar suposiciones en el código
def calcular_promedio(numeros):
assert len(numeros) > 0, "La lista no puede estar vacía"
assert all(isinstance(n, (int, float)) for n in numeros), "Todos deben ser números"
return sum(numeros) / len(numeros)
# Si la suposición es falsa, AssertionError te dice exactamente qué esperabas:
calcular_promedio([])
# AssertionError: La lista no puede estar vacía
calcular_promedio([1, 2, "tres"])
# AssertionError: Todos deben ser números
# 3. traceback: obtener información detallada de excepciones
import traceback
try:
resultado = 10 / 0
except ZeroDivisionError:
# Imprimir el traceback completo aunque estés en un except
traceback.print_exc()
# O capturarlo como string
error_detallado = traceback.format_exc()
logging.error(f"Error inesperado:\n{error_detallado}")
Errores comunes y cómo encontrarlos rápido
Off-by-one error: el error de "uno de más o uno de menos"
# Muy común en bucles y con índices
# Imprimir los primeros 5 elementos de una lista
numeros = [10, 20, 30, 40, 50, 60, 70]
# ❌ Se imprime el índice 5 también (6 elementos, no 5)
for i in range(0, 6):
print(numeros[i])
# ✅ range(0, 5) genera: 0, 1, 2, 3, 4
for i in range(0, 5):
print(numeros[i])
# O más idiomático en Python:
for n in numeros[:5]:
print(n)
Variable no inicializada o con valor incorrecto
# El acumulador se reinicializa dentro del bucle (error clásico)
numeros = [1, 2, 3, 4, 5]
# ❌ total se reinicia en cada iteración
for n in numeros:
total = 0 # ← esto está mal: debería estar FUERA del bucle
total += n
print(total) # 5 (solo el último)
# ✅ total se inicializa una vez antes del bucle
total = 0
for n in numeros:
total += n
print(total) # 15
Modificar una lista mientras la recorres
# ❌ Modificar la lista que estás iterando causa comportamiento inesperado
numeros = [1, 2, 3, 4, 5, 6]
for n in numeros:
if n % 2 == 0:
numeros.remove(n) # ← peligroso: se saltan elementos
print(numeros) # [1, 3, 5] en Python... a veces. Comportamiento indefinido.
# ✅ Crear una nueva lista con los elementos que quieres conservar
numeros = [1, 2, 3, 4, 5, 6]
impares = [n for n in numeros if n % 2 != 0]
print(impares) # [1, 3, 5] siempre correcto
Confundir == con is en Python
# == compara el VALOR
# is compara la IDENTIDAD (si son el mismo objeto en memoria)
lista1 = [1, 2, 3]
lista2 = [1, 2, 3]
print(lista1 == lista2) # True ← mismo valor
print(lista1 is lista2) # False ← no son el mismo objeto en memoria
# El error clásico: usar 'is' para comparar strings o números
nombre = "ana"
if nombre is "ana": # ← puede funcionar a veces por optimización interna de Python
print("correcto") # pero NO es fiable. Usa == para comparar valores.
# Cuándo usar 'is':
# Solo para comparar con None, True y False
if resultado is None: # ✅ correcto
print("sin resultado")
if resultado == None: # ❌ funciona pero no es la forma idiomática en Python
print("sin resultado")
El proceso de depuración: paso a paso
Cuando te encuentras un bug, seguir este proceso evita perder tiempo dando vueltas:
- Reproduce el error de forma consistente. Asegúrate de que puedes provocar el error a voluntad. Un error que no se reproduce de forma fiable es imposible de depurar.
- Lee el mensaje de error completo. No lo ignores por miedo. La última línea te dice qué falló. El traceback te dice dónde.
- Entiende qué debería ocurrir. Antes de buscar el error, tenlo claro: ¿qué resultado esperas? ¿Qué hace el programa en su lugar?
- Localiza la zona del código. Usa print debugging o el depurador para encontrar en qué función o bloque empieza el comportamiento incorrecto.
- Formula una hipótesis. "Creo que el error está aquí porque esta variable tiene el valor X cuando debería tener Y."
- Verifica la hipótesis. Añade un print o breakpoint para confirmar si tu hipótesis es correcta.
- Corrige el error y verifica la corrección. Una vez corregido, ejecuta el caso que producía el error y también otros casos para asegurarte de no haber roto algo más.
Resumen
- Hay tres tipos de errores: sintaxis (el código está mal escrito), ejecución (falla al correr) y lógica (funciona pero da resultados incorrectos). Los de lógica son los más difíciles.
- Lee el traceback de abajo hacia arriba: la última línea dice qué falló, las anteriores muestran cómo llegó el programa hasta ahí.
- El print debugging es la técnica más rápida para problemas simples: añade prints con etiquetas claras y elimínalos cuando termines.
- El depurador (VS Code, pdb, Chrome DevTools) es la herramienta correcta para problemas complejos: pausa la ejecución, inspecciona variables y avanza instrucción a instrucción.
- Para localizar un bug en código largo, usa búsqueda binaria: descarta la mitad del código en cada paso.
- El método del pato de goma funciona: explica el código en voz alta y a menudo encuentras el error al verbalizarlo.
- Usa
loggingen lugar deprint()en proyectos reales: puedes activar y desactivar los mensajes sin borrar código y añade información de contexto automáticamente.
Kal An Zans
2 months ago