Nadie escribe código sin bugs, ni siquiera con años de experiencia. Lo que distingue a un programador con soltura de uno que se atasca no es cometer menos errores, es lo rápido que los localiza y entiende. Esa velocidad no es intuición: es un puñado de técnicas concretas que se aprenden y se entrenan, como cualquier otra habilidad técnica.
Este artículo recoge las que más se usan en el día a día, de la más simple a la más sistemática. No hace falta aplicarlas todas a la vez en cada bug; la habilidad está en saber cuál encaja con el problema que tienes delante.
Antes de depurar, reproduce el error de forma fiable
El primer paso, y el que más se salta la gente con prisa, es conseguir que el error ocurra de forma consistente. Un bug que "a veces pasa" es mucho más difícil de arreglar que uno que ocurre siempre bajo las mismas condiciones. Antes de tocar una sola línea de código, identifica los pasos exactos que provocan el fallo: qué datos de entrada, en qué orden, en qué estado estaba la aplicación. Sin esto, cualquier "arreglo" que hagas es en realidad una suposición sin verificar.
Cinco técnicas que se usan de verdad
Con el error reproducido, toca investigar la causa. Estas son las técnicas más comunes, cada una útil en un contexto distinto.
Poner console.log por todo el archivo sin criterio es la forma más lenta de depurar, aunque sea la más habitual al empezar. La versión con criterio consiste en identificar los puntos donde el flujo de datos podría estar rompiéndose y loguear ahí, con contexto suficiente para entender qué estás viendo sin tener que adivinarlo.
function calcularDescuento(precio, porcentaje) {
console.log('entrada:', { precio, porcentaje });
const descuento = precio * (porcentaje / 100);
console.log('descuento calculado:', descuento);
return precio - descuento;
}
Usar objetos como { precio, porcentaje } en lugar de valores sueltos etiqueta automáticamente cada dato en la consola, así que no tienes que contar comas para saber cuál es cuál. Cuando termines, borra estos logs o cámbialos por una librería de logging de verdad; no deben llegar a producción sueltos por el código.
Un breakpoint pausa la ejecución del programa en una línea concreta, dejándote inspeccionar el valor exacto de cada variable en ese momento, en lugar de tener que imprimirlas una a una. En Chrome DevTools, abre la pestaña "Sources", localiza tu archivo y haz clic en el número de línea donde quieres pausar. En VS Code, basta con hacer clic a la izquierda del número de línea para poner un punto rojo y lanzar el depurador integrado.
function procesarPedido(pedido) {
const total = calcularTotal(pedido.items);
debugger; // la ejecución se pausa aquí si las DevTools están abiertas
return aplicarDescuentos(total, pedido.cupon);
}
La palabra clave debugger hace exactamente lo mismo que un breakpoint puesto a mano, pero directamente desde el código: si las herramientas de desarrollador están abiertas, la ejecución se detiene ahí y puedes inspeccionar todo el estado del programa en ese instante exacto.
Cuando un programa lanza una excepción, casi siempre viene acompañada de un stack trace: la lista de llamadas a función que llevaron hasta el error, de la más reciente a la más antigua. Mucha gente lee solo la primera línea y se pierde información clave que está más abajo.
TypeError: Cannot read properties of undefined (reading 'email')
at obtenerEmailUsuario (usuarios.js:14:23)
at procesarPedido (pedidos.js:32:18)
at manejarPeticion (rutas.js:9:5)
Esta traza dice que el error ocurre en la línea 14 de usuarios.js, dentro de obtenerEmailUsuario, y que se llegó ahí desde procesarPedido en pedidos.js, a su vez llamado desde manejarPeticion. Lo importante no es solo dónde explota (línea 14), sino el camino completo: puede que el bug real esté en cómo procesarPedido le pasa datos a obtenerEmailUsuario, no en esa función en sí.
Cuando no tienes ni idea de dónde puede estar el bug en un bloque grande de código, la bisección es la técnica más eficiente: comenta o deshabilita la mitad del código sospechoso y comprueba si el error persiste. Si desaparece, el problema está en la mitad que quitaste; si sigue, está en la otra mitad. Repite el proceso sobre la mitad culpable hasta acorralar el bug en unas pocas líneas.
Es exactamente la misma lógica que git bisect aplica para encontrar qué commit introdujo una regresión: en vez de revisar commit por commit, prueba el punto medio del historial y descarta la mitad correcta o incorrecta en cada paso, algo especialmente útil si ya usas Git en tu flujo de trabajo con un historial de commits razonable.
El nombre viene de la costumbre (real, documentada) de algunos programadores de tener un patito de goma en el escritorio y explicarle el código línea por línea en voz alta cuando se atascan. Suena absurdo, pero funciona: el acto de verbalizar, despacio y con detalle, qué se supone que hace cada línea, obliga a tu cerebro a procesar el código de una forma distinta a cuando solo lo lees por encima, y las asunciones erróneas suelen saltar a la vista solas.
No necesitas un patito literal. Explicárselo a un compañero, escribirlo en un documento, o incluso grabarte hablando funciona igual de bien. Lo importante es la verbalización explícita, paso a paso, sin saltarte "obviedades": normalmente el bug está escondido justo en el paso que dabas por hecho.
Cuándo usar cada técnica
No hay una jerarquía fija, pero como guía general: usa console.log para bugs simples y rápidos de aislar, breakpoints cuando necesitas inspeccionar el estado completo del programa en un punto concreto, el stack trace como primer paso siempre que tengas una excepción explícita, la bisección cuando no tienes ni idea de por dónde empezar en un bloque grande, y el rubber duck cuando llevas más de veinte minutos atascado sin avanzar. Ese último punto merece mención aparte: si un bug te lleva mucho más tiempo del esperado, alejarte cinco minutos suele ser más productivo que seguir mirando la misma pantalla.
Un buen manejo de errores facilita todo esto
Depurar un programa que captura y describe bien sus errores es mucho más rápido que depurar uno que los deja fallar en silencio o con mensajes genéricos. Si trabajas en JavaScript, invertir tiempo en entender cómo usar try/catch correctamente reduce de forma directa cuánto tiempo vas a pasar luego con estas técnicas, porque los errores llegan ya acompañados de contexto útil en lugar de un fallo mudo tres funciones más abajo.