🛡️ Manejo de Errores: Cómo Hacer que tu Código Sea Más Robusto

¡Hola desarrolladores junior! :waving_hand:

Uno de los aspectos que más diferencia a un desarrollador junior de uno más experimentado es cómo maneja los errores. Aprender a anticipar, capturar y manejar errores hará que tu código sea mucho más confiable y profesional.

:bullseye: ¿Por Qué es Importante Manejar Errores?

Imagina que tu aplicación se rompe cada vez que un usuario escribe un email inválido o cuando una API no responde. ¡Sería muy frustrante! El manejo adecuado de errores mejora la experiencia del usuario y facilita el debugging.

:police_car_light: Tipos Comunes de Errores

1. Errores de Sintaxis

// ❌ Error de sintaxis - falta cerrar paréntesis
function saludar(nombre {
    return `Hola ${nombre}`;
}

// ✅ Sintaxis correcta
function saludar(nombre) {
    return `Hola ${nombre}`;
}

2. Errores de Referencia

// ❌ Variable no definida
console.log(usuarioNombre); // ReferenceError

// ✅ Verificar antes de usar
const usuarioNombre = "Ana";
console.log(usuarioNombre);

3. Errores de Tipo

// ❌ Intentar llamar un método en null/undefined
const usuario = null;
const nombre = usuario.nombre; // TypeError

// ✅ Verificación defensiva
const nombre = usuario?.nombre || 'Usuario desconocido';

:hammer_and_wrench: Try-Catch: Tu Mejor Amigo

Uso Básico

try {
    // Código que podría fallar
    const datos = JSON.parse(jsonString);
    console.log('Datos parseados:', datos);
} catch (error) {
    // Qué hacer si algo sale mal
    console.error('Error al parsear JSON:', error.message);
    // Mostrar mensaje amigable al usuario
    mostrarMensaje('Hubo un problema al procesar los datos');
}

Con Finally

function cargarDatos() {
    mostrarIndicadorCarga(true);
    
    try {
        const datos = obtenerDatosDelServidor();
        mostrarDatos(datos);
    } catch (error) {
        mostrarMensajeError('No se pudieron cargar los datos');
    } finally {
        // Esto SIEMPRE se ejecuta
        mostrarIndicadorCarga(false);
    }
}

:globe_with_meridians: Manejo de Errores en APIs

Fetch con Manejo Completo

async function obtenerUsuario(id) {
    try {
        mostrarCargando(true);
        
        const response = await fetch(`/api/usuarios/${id}`);
        
        // Verificar si la respuesta es exitosa
        if (!response.ok) {
            if (response.status === 404) {
                throw new Error('Usuario no encontrado');
            } else if (response.status === 500) {
                throw new Error('Error del servidor');
            } else {
                throw new Error(`Error: ${response.status}`);
            }
        }
        
        const usuario = await response.json();
        return usuario;
        
    } catch (error) {
        console.error('Error al obtener usuario:', error);
        
        // Mostrar mensaje específico según el error
        if (error.message.includes('Failed to fetch')) {
            mostrarError('Sin conexión a internet');
        } else {
            mostrarError(error.message);
        }
        
        return null;
    } finally {
        mostrarCargando(false);
    }
}

Manejo de Timeouts

function fetchConTimeout(url, tiempoLimite = 5000) {
    return Promise.race([
        fetch(url),
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error('Timeout')), tiempoLimite)
        )
    ]);
}

// Uso
try {
    const response = await fetchConTimeout('/api/datos', 3000);
    const datos = await response.json();
} catch (error) {
    if (error.message === 'Timeout') {
        mostrarError('La solicitud tardó demasiado tiempo');
    } else {
        mostrarError('Error al cargar datos');
    }
}

:memo: Validación de Formularios

Validación Robusta

function validarFormularioRegistro(datos) {
    const errores = [];
    
    // Validar email
    if (!datos.email || !datos.email.trim()) {
        errores.push('El email es requerido');
    } else if (!esEmailValido(datos.email)) {
        errores.push('El email no tiene un formato válido');
    }
    
    // Validar contraseña
    if (!datos.password) {
        errores.push('La contraseña es requerida');
    } else if (datos.password.length < 6) {
        errores.push('La contraseña debe tener al menos 6 caracteres');
    }
    
    // Validar edad
    if (!datos.edad || isNaN(datos.edad)) {
        errores.push('La edad debe ser un número válido');
    } else if (datos.edad < 18) {
        errores.push('Debes ser mayor de 18 años');
    }
    
    return {
        esValido: errores.length === 0,
        errores: errores
    };
}

function esEmailValido(email) {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(email);
}

// Uso en formulario
document.getElementById('formulario').addEventListener('submit', function(e) {
    e.preventDefault();
    
    const datos = {
        email: document.getElementById('email').value,
        password: document.getElementById('password').value,
        edad: parseInt(document.getElementById('edad').value)
    };
    
    const validacion = validarFormularioRegistro(datos);
    
    if (validacion.esValido) {
        enviarFormulario(datos);
    } else {
        mostrarErrores(validacion.errores);
    }
});

:artist_palette: Mostrar Errores de Forma Amigable

Funciones Utilitarias para UI

function mostrarError(mensaje, tipo = 'error') {
    const contenedor = document.getElementById('mensajes');
    
    const div = document.createElement('div');
    div.className = `mensaje ${tipo}`;
    div.textContent = mensaje;
    
    contenedor.appendChild(div);
    
    // Remover después de 5 segundos
    setTimeout(() => {
        div.remove();
    }, 5000);
}

function mostrarCargando(mostrar) {
    const indicador = document.getElementById('cargando');
    indicador.style.display = mostrar ? 'block' : 'none';
}

function limpiarErrores() {
    const errores = document.querySelectorAll('.mensaje.error');
    errores.forEach(error => error.remove());
}

CSS para Mensajes

.mensaje {
    padding: 12px;
    margin: 8px 0;
    border-radius: 4px;
    font-weight: 500;
}

.mensaje.error {
    background-color: #fee;
    color: #c53030;
    border: 1px solid #fed7d7;
}

.mensaje.exito {
    background-color: #f0fff4;
    color: #38a169;
    border: 1px solid #c6f6d5;
}

.mensaje.advertencia {
    background-color: #fffbf0;
    color: #d69e2e;
    border: 1px solid #feebc8;
}

:magnifying_glass_tilted_left: Logging para Debugging

Sistema de Logs Básico

const Logger = {
    info: (mensaje, datos = null) => {
        console.log(`ℹ️ [INFO] ${mensaje}`, datos);
    },
    
    error: (mensaje, error = null) => {
        console.error(`❌ [ERROR] ${mensaje}`, error);
        // En producción, enviar a servicio de logs
    },
    
    warn: (mensaje, datos = null) => {
        console.warn(`⚠️ [WARN] ${mensaje}`, datos);
    },
    
    debug: (mensaje, datos = null) => {
        if (window.location.hostname === 'localhost') {
            console.log(`🐛 [DEBUG] ${mensaje}`, datos);
        }
    }
};

// Uso
try {
    Logger.info('Iniciando carga de usuarios');
    const usuarios = await cargarUsuarios();
    Logger.info('Usuarios cargados exitosamente', { cantidad: usuarios.length });
} catch (error) {
    Logger.error('Error al cargar usuarios', error);
}

:light_bulb: Mejores Prácticas

1. Sé Específico con los Errores

// ❌ Error genérico
throw new Error('Algo salió mal');

// ✅ Error específico
throw new Error('El email ya está registrado en el sistema');

2. No Ignores los Errores

// ❌ Ignorar errores silenciosamente
try {
    operacionRiesgosa();
} catch (error) {
    // No hacer nada es malo
}

// ✅ Manejar apropiadamente
try {
    operacionRiesgosa();
} catch (error) {
    Logger.error('Error en operación riesgosa', error);
    mostrarMensajeAlUsuario('No se pudo completar la operación');
}

3. Usa Error Boundaries en React

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }
    
    static getDerivedStateFromError(error) {
        return { hasError: true };
    }
    
    componentDidCatch(error, errorInfo) {
        Logger.error('Error capturado por boundary', { error, errorInfo });
    }
    
    render() {
        if (this.state.hasError) {
            return <h2>Algo salió mal. Por favor, recarga la página.</h2>;
        }
        
        return this.props.children;
    }
}

:bullseye: Ejercicio Práctico

Crea una función que:

  1. Haga una petición a una API
  2. Maneje diferentes tipos de errores (red, 404, 500, etc.)
  3. Muestre mensajes apropiados al usuario
  4. Registre errores para debugging

¡Intenta implementarlo y comparte tu código en los comentarios!

:books: Resumen

  • Siempre anticipa qué puede salir mal
  • Usa try-catch para operaciones riesgosas
  • Valida datos antes de procesarlos
  • Muestra mensajes claros al usuario
  • Registra errores para debugging
  • No ignores errores nunca

El manejo de errores puede parecer tedioso al principio, pero es lo que separa aplicaciones amateur de aplicaciones profesionales. ¡Practiquen estas técnicas y verán la diferencia!

¿Qué tipo de errores les han causado más problemas? ¿Tienen alguna situación específica donde no saben cómo manejar un error? ¡Compartan sus experiencias!

juniordev #ErrorHandling bestpractices javascript webdev #LearningToCode