JavaScript

JavaScript

Tutorial JavaScript: Excepciones

Aprende a capturar y gestionar excepciones en JavaScript, incluyendo errores síncronos y asincrónicos con promesas y async/await.

Aprende JavaScript y certifícate

Fundamentos del manejo de excepciones: Captura y lanzamiento de errores en JavaScript

El manejo de excepciones es una técnica fundamental en la programación orientada a objetos que permite controlar situaciones inesperadas o erróneas durante la ejecución del código. En JavaScript, este mecanismo nos ayuda a identificar, capturar y responder a errores de forma estructurada, evitando que nuestra aplicación se detenga abruptamente.

Errores en JavaScript

JavaScript proporciona un sistema de errores basado en objetos que heredan de la clase Error. Cuando ocurre un problema durante la ejecución, el intérprete genera (o "lanza") un objeto de error que contiene información sobre lo sucedido.

Los tipos de errores nativos más comunes son:

  • Error: El tipo base para todos los errores
  • SyntaxError: Errores de sintaxis en el código
  • ReferenceError: Referencias a variables o funciones inexistentes
  • TypeError: Operaciones sobre tipos de datos incorrectos
  • RangeError: Valores fuera del rango permitido

Cada objeto de error contiene propiedades útiles:

try {
  nonExistentFunction();
} catch (error) {
  console.log(error.name);     // "ReferenceError"
  console.log(error.message);  // "nonExistentFunction is not defined"
  console.log(error.stack);    // Traza de la pila de llamadas
}

Estructura try-catch-finally

El bloque try-catch-finally es la estructura básica para manejar excepciones en JavaScript:

try {
  // Código que podría generar un error
} catch (error) {
  // Código que se ejecuta si ocurre un error
} finally {
  // Código que se ejecuta siempre, haya error o no
}

Cada parte cumple una función específica:

  • El bloque try contiene el código que queremos "proteger" y que podría generar errores.
  • El bloque catch se ejecuta solo si ocurre un error en el bloque try.
  • El bloque finally (opcional) se ejecuta siempre, independientemente de si hubo un error o no.

Un ejemplo práctico:

function divideNumbers(a, b) {
  try {
    if (b === 0) {
      throw new Error("Cannot divide by zero");
    }
    
    const result = a / b;
    return result;
  } catch (error) {
    console.error(`Error occurred: ${error.message}`);
    return null;
  } finally {
    console.log("Division operation attempted");
  }
}

console.log(divideNumbers(10, 2));  // 5
console.log(divideNumbers(10, 0));  // null (después de mostrar el error)

Lanzamiento de errores con throw

La palabra clave throw permite generar errores manualmente cuando detectamos condiciones inválidas en nuestro código:

function validateAge(age) {
  if (typeof age !== 'number') {
    throw new TypeError("Age must be a number");
  }
  
  if (age < 0 || age > 120) {
    throw new RangeError("Age must be between 0 and 120");
  }
  
  return true;
}

try {
  validateAge("twenty");  // Lanzará TypeError
} catch (error) {
  console.error(error.name + ": " + error.message);
}

Podemos lanzar cualquier tipo de error nativo o crear nuestros propios tipos personalizados utilizando la herencia de clases que ya conocemos:

// Creamos una clase de error personalizada extendiendo Error
class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

function processUserData(user) {
  if (!user.name) {
    throw new ValidationError("User name is required");
  }
  
  // Continuar procesando...
}

Este es un excelente ejemplo de cómo el sistema de excepciones se integra con la programación orientada a objetos que hemos estudiado anteriormente.

Captura selectiva de errores

Podemos discriminar entre diferentes tipos de errores para manejarlos de forma específica utilizando instanceof:

try {
  // Código que podría generar diferentes tipos de errores
  const data = JSON.parse(userInput);
  processData(data);
} catch (error) {
  if (error instanceof SyntaxError) {
    console.error("Invalid JSON format");
  } else if (error instanceof ReferenceError) {
    console.error("Missing reference in the data");
  } else {
    console.error("An unexpected error occurred:", error);
  }
}

Propagación de errores

A veces es mejor propagar un error hacia arriba en la pila de llamadas para que sea manejado por un nivel superior:

function validateData(data) {
  if (!data.id) {
    throw new Error("Missing ID field");
  }
  return true;
}

function processUserRecord(record) {
  try {
    validateData(record);
    // Continuar procesando...
  } catch (error) {
    // Añadir contexto al error y propagarlo
    throw new Error(`Error processing user record: ${error.message}`);
  }
}

try {
  processUserRecord({name: "John"});
} catch (error) {
  console.error("Application error:", error.message);
  // Mostrar mensaje amigable al usuario
}

Errores en funciones anidadas

Los errores se propagan automáticamente hacia arriba a través de las llamadas a funciones hasta encontrar un bloque catch:

function innerFunction() {
  throw new Error("Inner error");
}

function middleFunction() {
  innerFunction();  // El error se propaga hacia arriba
}

function outerFunction() {
  try {
    middleFunction();
  } catch (error) {
    console.error("Caught in outer function:", error.message);
  }
}

outerFunction();  // "Caught in outer function: Inner error"

Buenas prácticas básicas

  • Sé específico: Captura solo los errores que puedes manejar adecuadamente.
  • Proporciona mensajes claros: Los mensajes de error deben ser descriptivos y útiles.
  • No abuses de try-catch: Úsalo para condiciones excepcionales, no para el flujo normal del programa.
  • Considera el rendimiento: Los bloques try-catch tienen un pequeño impacto en el rendimiento.
// Enfoque recomendado
function getUserData(userId) {
  if (!userId) {
    throw new Error("User ID is required");
  }
  
  try {
    return fetchUserFromDatabase(userId);
  } catch (error) {
    console.error(`Failed to fetch user ${userId}:`, error.message);
    throw new Error(`Could not retrieve user data: ${error.message}`);
  }
}

Diseño de estrategias robustas para el control de errores: Patrones y mejores prácticas

El manejo de excepciones es más que simplemente usar try-catch. Al integrar estos mecanismos con los principios de POO que hemos aprendido, podemos crear estrategias más robustas para nuestras aplicaciones.

Jerarquía de errores personalizados

Aprovechando la herencia de clases que vimos en el módulo anterior, podemos crear una jerarquía de errores específicos para nuestra aplicación:

// Error base para toda la aplicación
class AppError extends Error {
  constructor(message, statusCode = 500) {
    super(message);
    this.name = this.constructor.name;
    this.statusCode = statusCode;
    Error.captureStackTrace(this, this.constructor);
  }
}

// Errores específicos por dominio
class ValidationError extends AppError {
  constructor(message) {
    super(message, 400);
    this.validationErrors = [];
  }
  
  addValidationError(field, message) {
    this.validationErrors.push({ field, message });
    return this;
  }
}

class AuthorizationError extends AppError {
  constructor(message) {
    super(message, 403);
  }
}

Esta estructura de clases de error permite un manejo contextual más efectivo:

function processUserInput(data) {
  try {
    if (!data.username) {
      const error = new ValidationError("Invalid user data")
        .addValidationError("username", "Username is required");
      throw error;
    }
    
    // Más validaciones...
    
  } catch (error) {
    if (error instanceof ValidationError) {
      // Mostrar errores de validación en la interfaz
      displayValidationErrors(error.validationErrors);
    } else if (error instanceof AuthorizationError) {
      // Redirigir al login
      redirectToLogin();
    } else {
      // Manejar otros tipos de errores
      showGenericError(error.message);
    }
  }
}

Patrón de envoltura de errores

La envoltura de errores preserva el contexto original mientras añade información valiosa en cada nivel:

function processPayment(paymentData) {
  try {
    validatePaymentData(paymentData);
    chargeCustomer(paymentData);
    sendReceipt(paymentData);
  } catch (error) {
    // Envolver el error con contexto adicional
    const wrappedError = new AppError(
      `Payment processing failed: ${error.message}`,
      error.statusCode || 500
    );
    
    // Preservar el error original y añadir contexto
    wrappedError.originalError = error;
    wrappedError.paymentReference = paymentData.reference;
    
    throw wrappedError;
  }
}

Este patrón se alinea bien con el principio de encapsulación que estudiamos en POO, ya que oculta los detalles de implementación mientras proporciona información útil.

Recuperación elegante

La recuperación elegante permite que el sistema continúe funcionando incluso cuando ocurren errores:

function loadUserPreferences(userId) {
  try {
    return fetchUserPreferences(userId);
  } catch (error) {
    // Registrar el error
    console.warn(`Failed to load preferences for user ${userId}:`, error);
    
    // Proporcionar valores predeterminados como fallback
    return getDefaultPreferences();
  }
}

function getDefaultPreferences() {
  return {
    theme: 'light',
    notifications: true,
    language: 'en'
  };
}

Validación preventiva

La validación temprana puede evitar errores antes de que ocurran, siguiendo el principio de "fallar rápido":

function transferFunds(fromAccount, toAccount, amount) {
  // Validación preventiva
  if (typeof amount !== 'number' || amount <= 0) {
    throw new ValidationError("Amount must be a positive number");
  }
  
  if (!fromAccount || !toAccount) {
    throw new ValidationError("Both accounts are required");
  }
  
  // Verificar fondos suficientes antes de intentar la transferencia
  if (fromAccount.balance < amount) {
    throw new AppError("Insufficient funds", 400);
  }
  
  // Proceder con la transferencia...
  fromAccount.balance -= amount;
  toAccount.balance += amount;
  
  return {
    success: true,
    newBalance: fromAccount.balance
  };
}

Contratos y precondiciones

Establecer contratos claros mediante precondiciones ayuda a detectar errores temprano:

function calculateDiscount(product, user) {
  // Precondiciones
  assert(product, "Product is required");
  assert(user, "User is required");
  assert(typeof product.price === 'number', "Product price must be a number");
  
  // Lógica de negocio
  let discount = 0;
  
  if (user.isPremium) {
    discount = product.price * 0.1;
  }
  
  if (product.isOnSale) {
    discount += product.price * 0.05;
  }
  
  // Postcondición
  assert(discount >= 0, "Discount cannot be negative");
  
  return discount;
}

function assert(condition, message) {
  if (!condition) {
    throw new Error(`Assertion failed: ${message}`);
  }
}

Manejo de errores en un contexto orientado a objetos

Podemos integrar el manejo de excepciones directamente en nuestras clases, respetando los principios de POO:

class BankAccount {
  constructor(accountNumber, initialBalance = 0) {
    this.accountNumber = accountNumber;
    this.balance = initialBalance;
    this.transactions = [];
  }
  
  deposit(amount) {
    try {
      if (amount <= 0) {
        throw new ValidationError("Deposit amount must be positive");
      }
      
      this.balance += amount;
      this.recordTransaction("deposit", amount);
      
      return this.balance;
    } catch (error) {
      console.error(`Error in account ${this.accountNumber}:`, error);
      throw error; // Re-lanzar para manejo superior
    }
  }
  
  withdraw(amount) {
    try {
      if (amount <= 0) {
        throw new ValidationError("Withdrawal amount must be positive");
      }
      
      if (amount > this.balance) {
        throw new ValidationError("Insufficient funds");
      }
      
      this.balance -= amount;
      this.recordTransaction("withdrawal", amount);
      
      return this.balance;
    } catch (error) {
      console.error(`Error in account ${this.accountNumber}:`, error);
      throw error;
    }
  }
  
  recordTransaction(type, amount) {
    this.transactions.push({
      type,
      amount,
      date: new Date(),
      balance: this.balance
    });
  }
}

// Uso con manejo de excepciones
try {
  const account = new BankAccount("123456", 1000);
  account.deposit(500);
  account.withdraw(2000); // Esto generará un error
} catch (error) {
  if (error instanceof ValidationError) {
    console.log("Validation error:", error.message);
  } else {
    console.error("Unexpected error:", error);
  }
}

Manejo de excepciones en contextos asíncronos: Promesas, async/await y entornos distribuidos

El código asíncrono plantea desafíos adicionales para el manejo de excepciones. Si bien este tema se explorará en profundidad en el módulo de programación asíncrona, es importante entender los conceptos básicos y cómo se relacionan con lo que hemos aprendido hasta ahora.

Errores en promesas

En JavaScript moderno, las operaciones asíncronas a menudo utilizan promesas, que tienen su propio mecanismo para manejar errores mediante el método .catch():

fetchUserData(userId)
  .then(user => {
    console.log('User data:', user);
  })
  .catch(error => {
    console.error('Failed to fetch user data:', error.message);
  });

Un aspecto importante de las promesas es que los errores se propagan automáticamente a través de la cadena de .then() hasta encontrar un .catch():

fetchUserData(userId)
  .then(user => processUser(user))       // Si fetchUserData falla, este paso se omite
  .then(result => saveToDatabase(result)) // Este también se omite
  .catch(error => {
    // Captura errores de cualquier paso anterior
    console.error('Operation failed:', error);
  });

Introducción a async/await con excepciones

La sintaxis async/await simplifica el manejo de errores asíncronos permitiendo usar bloques try-catch tradicionales:

async function getUserData(userId) {
  try {
    const response = await fetchData(`/users/${userId}`);
    return response;
  } catch (error) {
    console.error(`Failed to fetch user ${userId}:`, error);
    throw new Error(`Could not retrieve user data: ${error.message}`);
  }
}

Es importante recordar que una función async siempre devuelve una promesa, por lo que los errores deben manejarse adecuadamente cuando se llama a estas funciones:

// Opción 1: Usando .catch()
getUserData(123)
  .then(user => displayUserProfile(user))
  .catch(error => showErrorMessage(error.message));

// Opción 2: Usando async/await con try-catch
async function displayUser(userId) {
  try {
    const user = await getUserData(userId);
    displayUserProfile(user);
  } catch (error) {
    showErrorMessage(error.message);
  }
}

Errores comunes en código asíncrono

Uno de los errores más frecuentes es olvidar manejar las promesas rechazadas:

// Incorrecto: error no manejado
function fetchAndProcess(url) {
  fetch(url).then(response => response.json());
  // Si la promesa se rechaza, el error no se maneja
}

// Correcto: con manejo de errores
function fetchAndProcess(url) {
  fetch(url)
    .then(response => response.json())
    .catch(error => console.error("Failed to fetch:", error));
}

Integración de excepciones síncronas y asíncronas

Un desafío común es integrar manejo de errores en código que mezcla operaciones síncronas y asíncronas:

class UserService {
  constructor(apiClient) {
    this.apiClient = apiClient;
    this.cache = new Map();
  }
  
  async getUser(userId) {
    // Verificación síncrona
    if (!userId) {
      throw new ValidationError("User ID is required");
    }
    
    // Verificar caché (síncrono)
    if (this.cache.has(userId)) {
      return this.cache.get(userId);
    }
    
    // Operación asíncrona
    try {
      const user = await this.apiClient.fetchUser(userId);
      
      // Más validaciones síncronas
      if (!user.name) {
        throw new ValidationError("Invalid user data received");
      }
      
      // Guardar en caché
      this.cache.set(userId, user);
      return user;
    } catch (error) {
      // Transformar errores específicos de la API
      if (error.status === 404) {
        throw new NotFoundError(`User ${userId} not found`);
      }
      // Re-lanzar otros errores
      throw error;
    }
  }
}

Un vistazo a patrones asíncronos más avanzados

En el módulo de programación asíncrona exploraremos estos conceptos con mayor profundidad, incluyendo:

  • Manejo de múltiples operaciones asíncronas con Promise.all() y Promise.allSettled()
  • Implementación de timeouts para operaciones asíncronas
  • Estrategias de reintentos para operaciones que pueden fallar temporalmente
  • Manejo de errores en APIs basadas en eventos
  • Patrones de recuperación en sistemas distribuidos

Por ahora, es importante entender que los principios fundamentales de manejo de excepciones se aplican tanto en código síncrono como asíncrono, aunque con algunas consideraciones adicionales.

Aprende JavaScript online

Otros ejercicios de programación de JavaScript

Evalúa tus conocimientos de esta lección Excepciones con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Clases y objetos

JavaScript
Código

Uso de operadores

JavaScript
Puzzle

Uso de operadores

JavaScript
Test

Estructuras de control

JavaScript
Test

Proyecto Manipulación DOM

JavaScript
Proyecto

Excepciones

JavaScript
Test

Transformación con map()

JavaScript
Código

Arrays y Métodos

JavaScript
Código

Reto Métodos de Strings

JavaScript
Código

Transformación con map()

JavaScript
Puzzle

Funciones flecha

JavaScript
Test

Async / Await

JavaScript
Código

Selección de elementos DOM

JavaScript
Puzzle

API Fetch

JavaScript
Código

Encapsulación

JavaScript
Test

Mapas con Map

JavaScript
Código

Creación y uso de variables

JavaScript
Puzzle

Polimorfismo

JavaScript
Puzzle

Reto Funciones flecha

JavaScript
Código

Tipos de datos

JavaScript
Puzzle

Reto Operadores avanzados

JavaScript
Código

Promises

JavaScript
Código

Reto Estructuras de control

JavaScript
Código

Estructuras de control

JavaScript
Puzzle

Pruebas unitarias

JavaScript
Proyecto

Inmutabilidad y programación funcional pura

JavaScript
Código

Funciones flecha

JavaScript
Puzzle

Polimorfismo

JavaScript
Test

Reto Polimorfismo

JavaScript
Código

Array

JavaScript
Código

Transformación con map()

JavaScript
Test

Reto Variables

JavaScript
Código

Gestor de tareas con JavaScript

JavaScript
Proyecto

Proyecto Modificación de elementos DOM

JavaScript
Proyecto

Manipulación DOM

JavaScript
Test

Funciones

JavaScript
Test

Conjuntos con Set

JavaScript
Código

Reto Prototipos y cadena de prototipos

JavaScript
Código

Reto Encapsulación

JavaScript
Código

Funciones flecha

JavaScript
Código

Async / Await

JavaScript
Código

Reto Excepciones

JavaScript
Código

Reto Filtrado con filter() y find()

JavaScript
Código

Creación y uso de variables

JavaScript
Test

Excepciones

JavaScript
Puzzle

Promises

JavaScript
Código

Funciones cierre (closure)

JavaScript
Test

Reto Herencia

JavaScript
Código

Herencia

JavaScript
Puzzle

Proyecto Eventos del DOM

JavaScript
Proyecto

Herencia

JavaScript
Test

Selección de elementos DOM

JavaScript
Test

Modificación de elementos DOM

JavaScript
Test

Reto Clases y objetos

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Test

Funciones cierre (closure)

JavaScript
Puzzle

Reto Destructuring de objetos y arrays

JavaScript
Código

Callbacks

JavaScript
Código

Funciones

JavaScript
Puzzle

Mapas con Map

JavaScript
Test

Reducción con reduce()

JavaScript
Test

Callbacks

JavaScript
Puzzle

Manipulación DOM

JavaScript
Puzzle

Introducción al DOM

JavaScript
Proyecto

Reto Funciones

JavaScript
Código

Reto Funciones cierre (closure)

JavaScript
Código

Promises

JavaScript
Test

Reto Reducción con reduce()

JavaScript
Código

Async / Await

JavaScript
Test

Reto Estructuras de control

JavaScript
Código

Eventos del DOM

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Puzzle

Async / Await

JavaScript
Puzzle

Promises

JavaScript
Puzzle

Selección de elementos DOM

JavaScript
Proyecto

Filtrado con filter() y find()

JavaScript
Código

Callbacks

JavaScript
Test

Creación de clases y objetos Restaurante

JavaScript
Código

Reducción con reduce()

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Puzzle

Reducción con reduce()

JavaScript
Puzzle

Conjuntos con Set

JavaScript
Puzzle

Herencia de clases

JavaScript
Código

Eventos del DOM

JavaScript
Test

Clases y objetos

JavaScript
Puzzle

Modificación de elementos DOM

JavaScript
Puzzle

Mapas con Map

JavaScript
Puzzle

Proyecto carrito compra agoodshop

JavaScript
Proyecto

Introducción a JavaScript

JavaScript
Test

Reto Mapas con Map

JavaScript
Código

Funciones

JavaScript
Código

Proyecto administrador de contactos

JavaScript
Proyecto

Reto Expresiones regulares

JavaScript
Código

Tipos de datos

JavaScript
Test

Clases y objetos

JavaScript
Test

Array

JavaScript
Test

Conjuntos con Set

JavaScript
Test

Array

JavaScript
Puzzle

Encapsulación

JavaScript
Puzzle

Todas las lecciones de JavaScript

Accede a todas las lecciones de JavaScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Javascript

JavaScript

Introducción Y Entorno

Tipos De Datos

JavaScript

Sintaxis

Variables

JavaScript

Sintaxis

Operadores

JavaScript

Sintaxis

Estructuras De Control

JavaScript

Sintaxis

Funciones

JavaScript

Sintaxis

Funciones Cierre (Closure)

JavaScript

Sintaxis

Métodos De Strings

JavaScript

Sintaxis

Funciones Cierre (Closure)

JavaScript

Sintaxis

Operadores Avanzados

JavaScript

Sintaxis

Funciones

JavaScript

Sintaxis

Expresiones Regulares

JavaScript

Sintaxis

Estructuras De Control

JavaScript

Sintaxis

Arrays Y Métodos

JavaScript

Estructuras De Datos

Conjuntos Con Set

JavaScript

Estructuras De Datos

Mapas Con Map

JavaScript

Estructuras De Datos

Conjuntos Con Set

JavaScript

Estructuras De Datos

Funciones Flecha

JavaScript

Programación Funcional

Filtrado Con Filter() Y Find()

JavaScript

Programación Funcional

Transformación Con Map()

JavaScript

Programación Funcional

Reducción Con Reduce()

JavaScript

Programación Funcional

Funciones Flecha

JavaScript

Programación Funcional

Transformación Con Map()

JavaScript

Programación Funcional

Inmutabilidad Y Programación Funcional Pura

JavaScript

Programación Funcional

Clases Y Objetos

JavaScript

Programación Orientada A Objetos

Excepciones

JavaScript

Programación Orientada A Objetos

Encapsulación

JavaScript

Programación Orientada A Objetos

Herencia

JavaScript

Programación Orientada A Objetos

Polimorfismo

JavaScript

Programación Orientada A Objetos

This Y Contexto

JavaScript

Programación Orientada A Objetos

Patrón De Módulos Y Namespace

JavaScript

Programación Orientada A Objetos

Prototipos Y Cadena De Prototipos

JavaScript

Programación Orientada A Objetos

Destructuring De Objetos Y Arrays

JavaScript

Programación Orientada A Objetos

Manipulación Dom

JavaScript

Dom

Selección De Elementos Dom

JavaScript

Dom

Modificación De Elementos Dom

JavaScript

Dom

Eventos Del Dom

JavaScript

Dom

Localstorage Y Sessionstorage

JavaScript

Dom

Bom (Browser Object Model)

JavaScript

Dom

Callbacks

JavaScript

Programación Asíncrona

Promises

JavaScript

Programación Asíncrona

Async / Await

JavaScript

Programación Asíncrona

Promises

JavaScript

Programación Asíncrona

Api Fetch

JavaScript

Programación Asíncrona

Async / Await

JavaScript

Programación Asíncrona

Naturaleza De Js Y Event Loop

JavaScript

Programación Asíncrona

Callbacks

JavaScript

Programación Asíncrona

Websockets

JavaScript

Programación Asíncrona

Módulos En Es6

JavaScript

Construcción

Configuración De Bundlers Como Vite

JavaScript

Construcción

Eslint Y Calidad De Código

JavaScript

Construcción

Npm Y Dependencias

JavaScript

Construcción

Introducción A Pruebas En Js

JavaScript

Testing

Pruebas Unitarias

JavaScript

Testing

Accede GRATIS a JavaScript y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender qué son las excepciones y por qué son importantes en JavaScript.
  • Conocer la estructura try-catch-finally y cómo se utiliza para manejar excepciones.
  • Entender la función de cada bloque (try, catch, finally) en el manejo de excepciones.
  • Aprender a lanzar y capturar excepciones usando la palabra clave throw.
  • Saber cómo utilizar múltiples bloques catch para manejar diferentes tipos de excepciones.
  • Comprender la utilidad del bloque finally para ejecutar código que debe ejecutarse siempre, independientemente de si ocurrió una excepción o no.
  • Aprender a personalizar los mensajes de error en las excepciones para brindar información más específica.
  • Entender cómo el manejo adecuado de excepciones mejora la robustez y la legibilidad del código.