Saltar al contenido principal

7.2.6 Manejo de Errores

Cuando trabajamos con ficheros, el error no es una excepción rara: es parte del flujo normal. Un archivo puede no existir, tener formato inválido, estar bloqueado o tener permisos insuficientes.

Por eso, manejar errores no es "poner un try"; es diseñar cómo reacciona el sistema sin perder datos ni romper la ejecución.


1️⃣ Enfoque Python: EAFP vs LBYL​

En Python existen dos estilos para prevenir fallos:

  • LBYL (Look Before You Leap): comprobar antes de actuar.
  • EAFP (Easier to Ask Forgiveness than Permission): actuar y capturar excepciones si algo falla.

🟩 Ejemplo de LBYL​

import os

if os.path.exists("datos.txt"):
with open("datos.txt", "r", encoding="utf-8") as f:
contenido = f.read()

🟦 Ejemplo de EAFP (recomendado)​

try:
with open("datos.txt", "r", encoding="utf-8") as f:
contenido = f.read()
except FileNotFoundError:
contenido = ""

🟨 Comparativa (LBYL vs EAFP)​

CriterioLBYLEAFP
Estilo en PythonMenos habitualMás habitual
Riesgo por carrera (TOCTOU)Más altoMás bajo
Claridad de errores realesMediaAlta
Recomendado para ficherosNo como base principalSí
Regla práctica

En Python, usa EAFP como estrategia principal y captura excepciones concretas.


2️⃣ Excepciones comunes de E/S​

No todos los fallos de ficheros son iguales. Tratar todo con except Exception dificulta diagnóstico y mantenimiento.

🟩 Captura específica de errores​

Captura granular
try:
with open("datos_criticos.dat", "r", encoding="utf-8") as f:
datos = f.read()
except FileNotFoundError:
print("No existe el archivo.")
except PermissionError:
print("No hay permisos suficientes.")
except IsADirectoryError:
print("La ruta es una carpeta, no un fichero.")
except UnicodeDecodeError:
print("Problema de codificación al leer el archivo.")
except OSError as e:
print(f"Error de sistema: {e}")

🟦 Cuándo usar except Exception​

Solo en capas superiores (por ejemplo, punto de entrada de app) para evitar caída total y registrar fallo inesperado.

🟨 Comparativa de captura​

Tipo de exceptVentajaRiesgo
Excepciones específicasDiagnóstico claroMás líneas de código
except Exception globalContiene fallos inesperadosOculta errores de lógica si se abusa

3️⃣ Logging: registrar, no improvisar​

print() sirve en aprendizaje, pero en entornos reales necesitas trazabilidad persistente.

🟩 Configuración base de logging​

Logging a fichero
import logging

logging.basicConfig(
filename="app_errors.log",
level=logging.ERROR,
format="%(asctime)s | %(levelname)s | %(message)s",
)

🟦 Registrar errores con contexto​

try:
with open("config.json", "r", encoding="utf-8") as f:
config = f.read()
except Exception as e:
logging.exception("Fallo al cargar configuración")

logging.exception(...) incluye traza completa, muy útil para depurar.

🟨 Niveles típicos​

  • DEBUG: detalle técnico interno.
  • INFO: eventos normales.
  • WARNING: situación anómala recuperable.
  • ERROR: fallo funcional.
  • CRITICAL: fallo grave del sistema.

4️⃣ Escritura segura y recuperación ante fallos​

Escribir directamente con modo w puede dejar un archivo corrupto si la app se interrumpe a mitad.

🟩 Patrón seguro (tmp + reemplazo)​

Escritura atómica básica
import os

ruta_final = "datos.json"
ruta_tmp = "datos.tmp"

with open(ruta_tmp, "w", encoding="utf-8") as f:
f.write('{"estado": "ok"}')

os.replace(ruta_tmp, ruta_final)

🟦 Backup antes de sobrescribir​

Copia de seguridad mínima
import shutil

shutil.copy2("datos.json", "datos.backup.json")

🟨 Comparativa rápida​

TécnicaQué protegeCoste
tmp + replaceCorrupción parcial por interrupciónBajo
Backup previoErrores lógicos o humanosMedio (espacio y gestión)

5️⃣ Limpieza garantizada con finally​

with cierra archivos automáticamente, pero a veces debes limpiar recursos extra (temporales, locks, sockets).

🟩 Ejemplo con limpieza obligatoria​

finally para liberar recursos
import os

ruta_tmp = "proceso.tmp"

try:
with open(ruta_tmp, "w", encoding="utf-8") as f:
f.write("estado temporal")
# Aquí podría fallar cualquier paso posterior
raise RuntimeError("fallo simulado")
except RuntimeError as e:
print(f"Error controlado: {e}")
finally:
if os.path.exists(ruta_tmp):
os.remove(ruta_tmp)

🟦 Cuándo usar finally​

  • Limpieza de archivos temporales.
  • Liberación de bloqueos.
  • Restauración de estado parcial.

🟨 Diferencia clave​

  • with: garantiza cierre del recurso gestionado por contexto.
  • finally: garantiza ejecutar una acción final, ocurra lo que ocurra.

6️⃣ Diseño de errores en capas (recomendado)​

Un sistema mantenible separa responsabilidades también en el manejo de errores.

🟩 Capa de infraestructura​

Captura excepciones técnicas (FileNotFoundError, JSONDecodeError, etc.) y las traduce.

🟦 Capa de negocio​

Decide qué hacer (continuar, reintentar, abortar, avisar usuario).

🟨 Patrón simple​

Traducir error técnico a error de dominio
class ConfigError(Exception):
pass

def cargar_config(ruta):
import json
try:
with open(ruta, "r", encoding="utf-8") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError) as e:
raise ConfigError("No se pudo cargar configuración") from e

Este enfoque evita acoplar toda la aplicación a detalles de infraestructura.


7️⃣ Ejemplo completo: lectura robusta + guardado seguro​

En este ejemplo se combinan tres ideas clave: lectura defensiva, valor por defecto recuperable y escritura segura.

Pipeline robusto
import json
import os


def cargar_eventos(ruta):
try:
with open(ruta, "r", encoding="utf-8") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return []


def guardar_resumen_seguro(ruta, resumen):
ruta_tmp = ruta + ".tmp"
with open(ruta_tmp, "w", encoding="utf-8") as f:
json.dump(resumen, f, indent=4, ensure_ascii=False)
os.replace(ruta_tmp, ruta)


eventos = cargar_eventos("eventos.json")
resumen = {"total": len(eventos)}
guardar_resumen_seguro("resumen.json", resumen)

Flujo recomendado: intentar -> capturar error específico -> registrar -> recuperar o abortar de forma controlada.


✅ Buenas prácticas recomendadas​

  • Captura excepciones específicas antes que genéricas.
  • Usa EAFP como patrón base en Python.
  • Registra errores con logging, no solo con print.
  • Usa escritura segura (tmp + os.replace).
  • Limpia recursos auxiliares con finally.
  • Separa error técnico (infraestructura) de decisión de negocio.

🧪 Ejercicios prácticos – Escudo Sentinel​

El sistema de persistencia está operativo, pero ahora debes endurecerlo frente a fallos reales. Tu misión es detectar errores, registrar incidencias y garantizar consistencia de datos.

Dispones de estos ficheros: 👉 Descárgalos aquí

  • config_valida.json -> configuración correcta
  • config_corrupta.json -> JSON inválido
  • eventos_incompletos.json -> eventos con campos faltantes
  • inventario_operativo.json -> base de datos operativa

🟢 Fase 1 – Diagnóstico de errores​

🟩 Ejercicio 1 – Carga defensiva de configuración​

Implementa cargar_config_segura(ruta) que:

  • Devuelva el diccionario si es válido.
  • Devuelva None si no existe o si está corrupto.
  • Muestre un mensaje distinto para cada caso.

🟦 Ejercicio 2 – Registro de incidencias con logging​

Configura logging a fichero y registra:

  • Error al abrir config_corrupta.json.
  • Warning por evento sin tipo en eventos_incompletos.json.

Objetivo: dejar trazabilidad técnica del proceso.

🟡 Fase 2 – Recuperación segura​

🟨 Ejercicio 3 – Guardado atómico de resumen​

Genera resumen_eventos.json con total de eventos válidos usando patrón tmp + replace.

Condición:

  • Si ocurre error durante escritura, el archivo final previo no debe dañarse.

🟧 Ejercicio 4 – Backup previo del inventario​

Antes de escribir cambios en inventario_operativo.json:

  • Crea inventario_operativo.backup.json.
  • Luego guarda la nueva versión.

Objetivo: minimizar riesgo de pérdida irreversible.

🔵 Fase 3 – Limpieza y robustez​

🟥 Ejercicio 5 – Limpieza con finally​

Simula un proceso que crea proceso.tmp y fuerza un error. Asegura que el archivo temporal se elimina siempre.

🟫 Ejercicio 6 – Traducción de errores a dominio​

Crea una excepción PersistenciaError y úsala para encapsular fallos técnicos de lectura/escritura.

Objetivo: desacoplar infraestructura de negocio.

🔴 Fase Final – Informe de resiliencia​

🟪 Ejercicio 7 – Informe final (informe_resiliencia.txt)​

Genera un informe con:

  • Número de errores capturados.
  • Número de warnings registrados.
  • Número de archivos recuperados correctamente.
  • Confirmación de que no quedan temporales (*.tmp).

Formato sugerido:

Errores capturados: X
Warnings: X
Archivos recuperados: X
Temporales pendientes: 0