Skip to content

Capítulo 4.4: Gestión de errores

Tiempo de estudio: 45 minutos


1. Gestión de errores: Protocolos de emergencia del Centro de Control de Misiones (CCM)

En el espacio, no todo puede salir según lo planeado: una llamarada solar puede interrumpir las comunicaciones, el ordenador de a bordo de la nave puede fallar y un comando desde la Tierra puede contener coordenadas incorrectas.

La gestión de errores en el frontend son los protocolos de emergencia de su CCM. Deben:

  • 🚨 Evitar que toda la interfaz "explote" debido a un solo comando fallido.
  • 📡 Comunicar claramente al operador (usuario) qué salió mal.
  • 🔧 Sugerir posibles acciones futuras.

💡 Analogía espacial:

Si la nave envía una señal 500 Internal Server Error, en la pantalla del CCM no debería aparecer "Error crítico de JavaScript en la línea 57". En su lugar, debería ser: "🚨 ¡Fallo a bordo de la nave! Los ingenieros ya han sido notificados. Intente repetir el comando más tarde."


2. Tipos de "anomalías cósmicas"

En el frontend, nos encontramos con tres tipos principales de errores al trabajar con la API:

  1. Errores de red: La conexión con el servidor no se ha establecido. La antena no funciona, el cable está cortado. fetch "caerá" en el bloque .catch().
  2. Errores del cliente (4xx): El comando desde la Tierra era incorrecto. ID inválido, error de validación. El servidor responde, pero con un estado 4xx.
  3. Errores del servidor (5xx): Fallo en la propia nave. Problema en el código de la API. El servidor responde, pero con un estado 500+.

Ya hemos empezado a gestionarlos con try...catch y la comprobación de response.ok. Ahora, hagámoslo de forma centralizada.


3. Función manejadora centralizada

Repetir el mismo código try...catch en cada función es una mala práctica. Crearemos un "envoltorio" universal para nuestras peticiones fetch.

Paso 1: Creamos api.js Cree un nuevo archivo api.js junto a app.js. En él, trasladaremos toda la lógica de interacción con la API.

// api.js

const API_BASE_URL = 'http://127.0.0.1:8000';

/**
 * Función universal para realizar peticiones a la API.
 * Gestiona errores y devuelve JSON.
 * @param {string} endpoint - Punto final de la API, por ejemplo, '/spaceships'
 * @param {object} options - Parámetros para fetch (method, headers, body)
 */
async function apiRequest(endpoint, options = {}) {
    const url = `${API_BASE_URL}${endpoint}`;

    try {
        const response = await fetch(url, options);

        // Si la respuesta no es JSON en absoluto, lanzamos un error inmediatamente
        const contentType = response.headers.get('content-type');
        if (!contentType || !contentType.includes('application/json')) {
            // Excepción para una petición DELETE exitosa que no tiene cuerpo
            if (response.status === 204) return null;

            throw new TypeError(`Respuesta no-JSON recibida del servidor: ${response.statusText}`);
        }

        const data = await response.json();

        if (!response.ok) {
            // Si el servidor devolvió un JSON con un error (por ejemplo, detalle de FastAPI)
            const errorMessage = data.detail || `¡Error HTTP! Estado: ${response.status}`;
            throw new Error(errorMessage);
        }

        return data;

    } catch (error) {
        console.error(`Error en la petición API a ${endpoint}:`, error);
        // "Relanzamos" el error para que pueda ser capturado en la UI
        throw error;
    }
}

Paso 2: Conectamos api.js en index.html Es importante conectarlo ANTES de app.js, ya que app.js utilizará sus funciones.

<!-- index.html -->
<body>
    <!-- ... -->
    <script src="api.js"></script>
    <script src="app.js"></script>
</body>

Paso 3: Refactorizamos app.js Ahora reescribiremos nuestras funciones usando el nuevo apiRequest.

// app.js

// const API_BASE_URL = ...; // Esta línea puede eliminarse, ahora está en api.js

// ...

async function fetchAndDisplayFleet() {
    try {
        fleetList.innerHTML = '<li>Cargando telemetría...</li>';
        const ships = await apiRequest('/spaceships'); // <-- ¡Usamos nuestro envoltorio!

        fleetList.innerHTML = '';
        if (ships.length === 0) {
            fleetList.innerHTML = '<li>No hay ninguna nave en el registro.</li>';
            return;
        }

        ships.forEach(ship => { /* ... el resto del código de visualización ... */ });
    } catch (error) {
        fleetList.innerHTML = `<li>🔴 Error al cargar la flota: ${error.message}</li>`;
    }
}

async function createShip(event) {
    event.preventDefault();
    const shipData = { /* ... recolección de datos del formulario ... */ };

    try {
        createStatusMessage.textContent = 'Enviando comando de lanzamiento...';
        const options = {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(shipData)
        };
        const newShip = await apiRequest('/spaceships', options); // <-- ¡Usamos nuestro envoltorio!

        createStatusMessage.textContent = `🚀 ¡Lanzamiento exitoso! ID asignado a la nave: ${newShip.id}`;
        createShipForm.reset();
        fetchAndDisplayFleet();
    } catch (error) {
        createStatusMessage.textContent = `🔴 Error: ${error.message}`;
    }
}

// ¡Reescriba el resto de las funciones (fetchShipById, deleteShip) de manera análoga!
Ahora toda la lógica para gestionar errores de red, verificar response.ok y parsear JSON se encuentra en un solo lugar, y el código en app.js se ha vuelto mucho más limpio y legible.


4. Mostrar errores al usuario

Una buena interfaz no solo debe escribir el error en la consola, sino mostrarlo al usuario de forma comprensible.

Ejemplo: Mejora de createShip Nuestro código ya lo hace: createStatusMessage.textContent = .... Pero podemos mejorarlo aún más creando una función universal para mostrar notificaciones.

Añadimos a app.js:

// app.js
function showNotification(message, isError = false) {
    const notificationArea = document.getElementById('create-status-message'); // o otro elemento
    notificationArea.textContent = message;
    notificationArea.style.color = isError ? 'red' : 'green';
}

// Usar en createShip:
async function createShip(event) {
    // ...
    try {
        // ...
        const newShip = await apiRequest('/spaceships', options);
        showNotification(`🚀 ¡Lanzamiento exitoso! ID: ${newShip.id}`);
        // ...
    } catch (error) {
        showNotification(`🔴 Error: ${error.message}`, true);
    }
}
Ahora tenemos un mecanismo unificado para mostrar tanto mensajes de éxito como errores.


Cuestionario de repaso

1. El bloque `.catch()` en la promesa `fetch` se activará si...

2. ¿Por qué es necesaria una función manejadora centralizada para las peticiones de la API?

3. `response.headers.get('content-type')` se usa para...

4. `throw new Error(...)` dentro de `try...catch` o `.then()` se usa para...

5. ¿Por qué es importante mostrar los errores al usuario y no solo en la consola?


🚀 Resumen del capítulo:

Usted ha fortalecido su MCC (Centro de Control de Misión) creando protocolos de emergencia fiables.

  • 🛡️ Usted comprende la diferencia entre errores de red, de cliente y de servidor.
  • ⚙️ Usted ha creado una función apiRequest centralizada para manejar todas las solicitudes, evitando la duplicación de código.
  • 📡 Su interfaz ahora es capaz de informar correctamente al usuario sobre los errores, haciéndola más amigable y confiable.

¡Escudos de emergencia levantados! Pero, ¿qué es mejor: las cadenas .then() o el moderno async/await? En el próximo capítulo, analizaremos ambos enfoques y entenderemos cuándo usar cada uno.

📌 Verificación:

  • Verifique que su código en app.js haya sido refactorizado con éxito y utilice la nueva función apiRequest.
  • Intente detener el servidor FastAPI y haga clic en el botón "Solicitar datos". Debería ver un error de conexión en la página.
  • Intente crear una nave con datos inválidos. Debería ver un mensaje de error de validación que proviene de FastAPI.

⚠️ Si hay errores:

  • apiRequest is not defined: Asegúrese de haber conectado api.js en index.html antes de app.js.
  • Verifique la consola del navegador en busca de otros errores de sintaxis en JavaScript.