Tema
Errores y depuración
Todo programador escribe bugs; los buenos programadores los encuentran rápido y escriben código que falla con elegancia. En esta lección aprenderás a manejar errores con try/catch, crear tus propios tipos de error, capturar fallos asíncronos y usar las DevTools del navegador para depurar como profesional, en lugar de llenar tu código de console.log.
Concepto
Un error es como la luz de advertencia del tablero de un auto: ignorarla no la hace desaparecer. Tienes dos trabajos:
- Manejar los errores esperables (la red falla, el usuario escribe mal un dato, la API responde 404) para que la app siga funcionando y el usuario sepa qué pasó.
- Encontrar y corregir los errores de programación (bugs) con herramientas de depuración.
Cuando ocurre un error que nadie captura, JavaScript detiene la ejecución de ese bloque de código y lo muestra en rojo en la consola.
Leer un mensaje de error
txt
Uncaught TypeError: Cannot read properties of undefined (reading 'nombre')
at mostrarUsuario (app.js:12:24)
at HTMLButtonElement.<anonymous> (app.js:30:3)- Tipo:
TypeError. - Mensaje: intentaste leer
nombrede algo que esundefined. - Stack trace (pila): dónde ocurrió (
app.js, línea 12, columna 24) y quién llamó a esa función. Haz clic en el enlace de la consola para ir directo a la línea.
Buena práctica
Lee el error completo antes de buscar en Google. El 80 % de las veces el mensaje y la línea te dicen exactamente qué pasa.
Tipos de errores nativos
| Tipo | Cuándo ocurre | Ejemplo |
|---|---|---|
SyntaxError | Código mal escrito (no se ejecuta nada) | Falta un paréntesis, JSON.parse('{mal}') |
ReferenceError | Variable que no existe | console.log(totl) |
TypeError | Operación sobre un tipo incorrecto | undefined.nombre, null() |
RangeError | Valor fuera de rango | new Array(-1), recursión infinita |
URIError | URI mal formada | decodeURIComponent('%') |
try, catch y finally
js
try {
// Código que puede fallar
const datos = JSON.parse(textoDelServidor);
procesar(datos);
} catch (error) {
// Se ejecuta SOLO si algo falló en el try
console.error('No se pudieron procesar los datos:', error.message);
mostrarMensaje('Hubo un problema al cargar la información.');
} finally {
// Se ejecuta SIEMPRE (haya error o no): limpieza
ocultarSpinner();
}El objeto error tiene:
js
error.name; // 'SyntaxError'
error.message; // 'Unexpected token m in JSON at position 1'
error.stack; // la pila completa como texto
error.cause; // la causa original (si se indicó)Si no necesitas el error, puedes omitir el parámetro: catch { ... }.
Error común: tragarse los errores
js
try {
guardarPedido(pedido);
} catch (e) {
// vacío 🙈
}Un catch vacío oculta los bugs: la app falla en silencio y nadie sabe por qué. Como mínimo, registra el error con console.error y avisa al usuario.
Lanzar errores con throw
Tú también puedes lanzar errores cuando algo no es válido. Así detienes el flujo en cuanto detectas un problema:
js
function calcularDescuento(precio, porcentaje) {
if (typeof precio !== 'number' || precio < 0) {
throw new Error('El precio debe ser un número positivo');
}
if (porcentaje < 0 || porcentaje > 100) {
throw new RangeError(`Porcentaje inválido: ${porcentaje}`);
}
return precio * (1 - porcentaje / 100);
}
try {
calcularDescuento(500, 150);
} catch (error) {
console.log(error.name, error.message); // 'RangeError' 'Porcentaje inválido: 150'
}Error común
Lanza siempre objetos Error (o subclases), no strings: throw 'falló' no tiene stack ni name, y dificulta la depuración. Usa throw new Error('falló').
Encadenar la causa
js
async function cargarPerfil(id) {
try {
return await obtenerUsuario(id);
} catch (error) {
// Añade contexto sin perder el error original
throw new Error(`No se pudo cargar el perfil ${id}`, { cause: error });
}
}Errores personalizados
Crear tus propias clases de error te permite distinguir qué tipo de problema ocurrió y reaccionar diferente a cada uno:
js
class AppError extends Error {
constructor(mensaje, opciones) {
super(mensaje, opciones);
this.name = this.constructor.name;
}
}
class ValidacionError extends AppError {
constructor(campo, mensaje) {
super(mensaje);
this.campo = campo;
}
}
class NoEncontradoError extends AppError {}
class RedError extends AppError {}
// Uso
function validarProducto(p) {
if (!p.nombre) throw new ValidacionError('nombre', 'El nombre es obligatorio');
if (p.precio <= 0) throw new ValidacionError('precio', 'El precio debe ser mayor a 0');
}
try {
validarProducto({ nombre: 'Mouse', precio: 0 });
} catch (error) {
if (error instanceof ValidacionError) {
marcarCampoConError(error.campo, error.message);
} else if (error instanceof RedError) {
mostrarToast('Revisa tu conexión a internet');
} else {
throw error; // un error que no sé manejar: lo dejo subir
}
}Buena práctica
Captura sólo los errores que sabes manejar y relanza el resto. Un catch que lo atrapa todo y sigue como si nada puede esconder bugs graves.
Errores en código asíncrono
js
// Con async/await: try/catch normal
async function cargar() {
try {
const r = await fetch('/api/productos');
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return await r.json();
} catch (error) {
console.error(error);
return [];
}
}
// Con promesas: .catch()
fetch('/api/productos')
.then((r) => r.json())
.catch((error) => console.error(error));Error común: try/catch no atrapa callbacks
js
try {
setTimeout(() => {
throw new Error('¡Boom!'); // NO lo atrapa el catch de afuera
}, 1000);
} catch (e) {
console.log('Nunca llega aquí');
}Cuando el callback se ejecuta, el try ya terminó hace rato. El manejo de errores debe ir dentro del callback, o usar promesas con await.
También debes await-ear las promesas dentro del try:
js
async function guardar() {
try {
return enviarDatos(); // ❌ sin await: si la promesa falla, el catch no se entera
} catch (e) { /* ... */ }
}Red de seguridad global
Para errores que se escapan de todos tus try/catch, registra manejadores globales. En producción se usan para enviar los errores a servicios como Sentry:
js
// Errores síncronos no capturados
window.addEventListener('error', (evento) => {
console.error('Error global:', evento.message, evento.filename, evento.lineno);
// enviarAlServidor({ mensaje: evento.message, stack: evento.error?.stack });
});
// Promesas rechazadas sin .catch()
window.addEventListener('unhandledrejection', (evento) => {
console.error('Promesa sin manejar:', evento.reason);
});Depuración con la consola
js
console.log('Valor:', valor);
console.info('Información');
console.warn('Advertencia'); // amarillo
console.error('Error'); // rojo, con stack
console.table(productos); // arrays de objetos como tabla
console.dir(document.body); // ver un elemento como objeto
console.log({ usuario, total }); // truco: muestra nombre y valor
console.group('Carrito'); // agrupar mensajes
console.log('Items:', items.length);
console.log('Total:', total);
console.groupEnd();
console.time('render'); // medir rendimiento
renderizarLista();
console.timeEnd('render'); // render: 12.4 ms
console.count('clic'); // cuántas veces pasó por aquí
console.assert(total >= 0, 'El total no puede ser negativo'); // sólo imprime si falla
console.trace('¿Quién llamó a esta función?');Depuración con breakpoints
Los console.log sirven, pero un breakpoint es mucho más poderoso: pausa el programa en una línea y te deja inspeccionar todas las variables en ese momento.
Paso a paso en Chrome / Edge / Firefox
- Abre DevTools (
F12oCtrl + Shift + I; en MacCmd + Option + I). - Ve a la pestaña Sources (Fuentes) y abre tu archivo con
Ctrl + P. - Haz clic en el número de línea: aparece un marcador azul (breakpoint).
- Provoca la acción (clic en el botón). El código se pausa en esa línea.
- Observa:
- Scope: todas las variables y sus valores actuales.
- Call Stack: qué funciones llamaron a la actual.
- Watch: expresiones que quieras vigilar (
total * 1.16). - Pasa el mouse sobre cualquier variable del código para ver su valor.
- Controla la ejecución:
- Resume (
F8): continuar hasta el siguiente breakpoint. - Step over (
F10): ejecutar la línea actual y pasar a la siguiente. - Step into (
F11): entrar a la función que se está llamando. - Step out (
Shift + F11): terminar la función actual y volver a quien la llamó.
- Resume (
La palabra clave debugger
Escribir debugger; en tu código actúa como un breakpoint cuando las DevTools están abiertas:
js
function calcularTotal(items) {
let total = 0;
for (const item of items) {
debugger; // se pausa aquí en cada vuelta
total += item.precio * item.cantidad;
}
return total;
}Importante
Nunca subas a producción código con debugger ni con console.log de depuración. Configura ESLint para que te avise (no-debugger, no-console).
Breakpoints especiales
- Condicionales: clic derecho en la línea → Add conditional breakpoint →
item.precio < 0. Sólo se pausa cuando la condición es verdadera. - Logpoints: clic derecho → Add logpoint: imprime en consola sin modificar tu código.
- En eventos: panel Event Listener Breakpoints →
Mouse > click. Se pausa en cualquier clic. - En cambios del DOM: en la pestaña Elements, clic derecho en un nodo → Break on → subtree modifications. Útil para descubrir "¿qué código está cambiando este elemento?".
- En excepciones: activa Pause on exceptions para detenerte justo donde se lanza un error.
La pestaña Network
Indispensable cuando trabajas con APIs:
- Lista cada petición: URL, método, código de estado, tamaño y tiempo.
- Haz clic en una para ver Headers (lo que enviaste), Payload (el cuerpo), Preview/Response (lo que respondió el servidor).
- Filtra por Fetch/XHR para ver sólo las llamadas a APIs.
- Usa Throttling (Slow 3G) para probar cómo se ve tu estado de "cargando" con una conexión lenta.
- Marca Offline para probar tu manejo de errores de red.
- Clic derecho → Copy as fetch o Copy as cURL para reproducir la petición.
Método para cazar bugs
- Reprodúcelo: encuentra los pasos exactos que lo provocan.
- Lee el error y ve a la línea indicada.
- Formula una hipótesis: "creo que
usuarioesundefinedporque la API tarda". - Comprueba con un breakpoint o un
console.logestratégico. - Aísla: comenta código o reduce el caso al mínimo hasta encontrar la causa.
- Corrige la causa, no el síntoma. Y prueba de nuevo.
- Explícalo en voz alta (la técnica del "pato de hule"): describir el código línea por línea suele revelar el error.
Ejercicios
- Escribe
parsearJSON(texto)que devuelva{ ok: true, datos }o{ ok: false, error: mensaje }sin lanzar nunca. - Crea una función
dividir(a, b)que lance unRangeErrorsibes 0 y unTypeErrorsi alguno no es número. Pruébala contry/catchmostrandoerror.name. - Crea la clase
SaldoInsuficienteErrorcon propiedadessaldoymonto, y úsala en una funciónretirar. - Encuentra el bug (pista: pon un breakpoint dentro del bucle):js
function promedio(nums) { let suma = 0; for (let i = 0; i <= nums.length; i++) suma += nums[i]; return suma / nums.length; } promedio([8, 9, 10]); // NaN - Registra un manejador global que muestre un aviso discreto en pantalla cuando haya una promesa rechazada sin manejar.
Ver solución
js
// 1
function parsearJSON(texto) {
try {
return { ok: true, datos: JSON.parse(texto) };
} catch (error) {
return { ok: false, error: error.message };
}
}
// 2
function dividir(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') throw new TypeError('Ambos deben ser números');
if (b === 0) throw new RangeError('No se puede dividir entre 0');
return a / b;
}
try {
dividir(10, 0);
} catch (error) {
console.log(error.name); // 'RangeError'
}
// 3
class SaldoInsuficienteError extends Error {
constructor(saldo, monto) {
super(`Saldo insuficiente: tienes $${saldo} y quieres retirar $${monto}`);
this.name = 'SaldoInsuficienteError';
this.saldo = saldo;
this.monto = monto;
}
}
function retirar(saldo, monto) {
if (monto > saldo) throw new SaldoInsuficienteError(saldo, monto);
return saldo - monto;
}
// 4: el bucle usa <= y en la última vuelta nums[3] es undefined → 27 + undefined = NaN.
function promedio(nums) {
let suma = 0;
for (let i = 0; i < nums.length; i++) suma += nums[i];
return suma / nums.length;
}
// 5
window.addEventListener('unhandledrejection', (e) => {
const aviso = document.createElement('div');
aviso.textContent = 'Algo salió mal. Intenta de nuevo.';
aviso.style.cssText = 'position:fixed;bottom:16px;right:16px;background:#991b1b;color:#fff;padding:10px 14px;border-radius:8px';
document.body.append(aviso);
setTimeout(() => aviso.remove(), 4000);
console.error(e.reason);
});Resumen
- Lee el error completo: tipo, mensaje y stack trace te dicen qué y dónde.
try/catch/finallymaneja errores;throw new Error()los lanza. Nunca dejes uncatchvacío.- Crea errores personalizados con
extends Errorpara reaccionar según el tipo; relanza lo que no sepas manejar. - En código asíncrono usa
awaitdentro deltryo.catch(); registraerroryunhandledrejectioncomo red de seguridad. - Depura con breakpoints,
debugger, la consola avanzada y la pestaña Network.
Siguiente: Buenas prácticas