# Manejo de Errores — Implementaciones Completas

## Python — Circuit Breaker

```python
# Circuit breaker manual para servicios externos críticos
import time
from enum import Enum
from threading import Lock
from dataclasses import dataclass, field


class EstadoCircuito(Enum):
    CERRADO = "CERRADO"       # Normal: deja pasar llamadas
    ABIERTO = "ABIERTO"       # Fallo: bloquea llamadas
    SEMI_ABIERTO = "SEMI_ABIERTO"  # Prueba: permite una llamada


@dataclass
class CircuitBreaker:
    nombre: str
    umbral_fallos: int = 5
    ventana_segundos: float = 60.0
    tiempo_recuperacion: float = 30.0

    _estado: EstadoCircuito = field(default=EstadoCircuito.CERRADO, init=False)
    _conteo_fallos: int = field(default=0, init=False)
    _ultimo_fallo: float = field(default=0.0, init=False)
    _lock: Lock = field(default_factory=Lock, init=False)

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            with self._lock:
                if self._estado == EstadoCircuito.ABIERTO:
                    if time.time() - self._ultimo_fallo > self.tiempo_recuperacion:
                        self._estado = EstadoCircuito.SEMI_ABIERTO
                    else:
                        raise ErrorExterno(
                            f"Circuito {self.nombre!r} abierto — servicio no disponible",
                            {"circuito": self.nombre, "estado": self._estado.value},
                        )

            try:
                resultado = func(*args, **kwargs)
                with self._lock:
                    self._conteo_fallos = 0
                    self._estado = EstadoCircuito.CERRADO
                return resultado

            except ErrorExterno:
                with self._lock:
                    self._conteo_fallos += 1
                    self._ultimo_fallo = time.time()
                    if self._conteo_fallos >= self.umbral_fallos:
                        self._estado = EstadoCircuito.ABIERTO
                        logger.warning(
                            "circuito_abierto",
                            nombre=self.nombre,
                            fallos=self._conteo_fallos,
                        )
                raise

        return wrapper


circuito_pago = CircuitBreaker(nombre="servicio-pago", umbral_fallos=3)


@circuito_pago
def cobrar_tarjeta(monto: float, token: str) -> dict:
    return cliente_pago.post("/cobrar", json={"monto": monto, "token": token})
```

---

## TypeScript — Discriminated Unions para Resultados

```typescript
// Result type explícito — errores tipados, sin try/catch en el caller
type Resultado<T, E = ErrorAplicacion> =
  | { ok: true; valor: T }
  | { ok: false; error: E };

function exito<T>(valor: T): Resultado<T> {
  return { ok: true, valor };
}

function fallo<E extends ErrorAplicacion>(error: E): Resultado<never, E> {
  return { ok: false, error };
}

interface ErrorAplicacion {
  codigo: string;
  mensaje: string;
  detalle?: Record<string, unknown>;
}

async function obtenerUsuario(
  id: string
): Promise<Resultado<Usuario, ErrorAplicacion>> {
  const usuario = await db.findById(id);
  if (!usuario) {
    return fallo({ codigo: "NOT_FOUND", mensaje: `Usuario ${id} no encontrado` });
  }
  return exito(usuario);
}

// Uso
const resultado = await obtenerUsuario("abc");
if (!resultado.ok) {
  // TypeScript sabe que resultado.error es ErrorAplicacion
  console.error(resultado.error.codigo);
  return;
}
// TypeScript sabe que resultado.valor es Usuario
console.log(resultado.valor.nombre);
```

---

## Angular — Error Boundaries con Componentes de Error

```typescript
// error-boundary.component.ts
import { Component, ErrorHandler, Injectable, signal } from '@angular/core';

@Injectable()
export class ManejadorErroresGlobal implements ErrorHandler {
  readonly ultimoError = signal<Error | null>(null);

  handleError(error: Error): void {
    console.error('[ERROR GLOBAL]', error);
    this.ultimoError.set(error);
    // Enviar a Sentry/DataDog aquí
  }
}

// Uso en componente
@Component({
  standalone: true,
  template: `
    @if (manejador.ultimoError()) {
      <div class="error-boundary bg-red-50 border border-red-200 rounded p-4">
        <p class="font-semibold text-red-700">Algo salió mal</p>
        <p class="text-red-600 text-sm">{{ manejador.ultimoError()?.message }}</p>
        <button (click)="reintentar()" class="mt-2 px-3 py-1 bg-red-600 text-white rounded text-sm">
          Reintentar
        </button>
      </div>
    } @else {
      <ng-content />
    }
  `,
})
export class ErrorBoundaryComponent {
  constructor(protected manejador: ManejadorErroresGlobal) {}

  reintentar(): void {
    this.manejador.ultimoError.set(null);
  }
}
```

---

## Angular — Interceptor HTTP con Manejo Centralizado

```typescript
// error.interceptor.ts
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, throwError } from 'rxjs';
import { Router } from '@angular/router';
import { NotificacionService } from './notificacion.service';

export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);
  const notificaciones = inject(NotificacionService);

  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      switch (error.status) {
        case 401:
          router.navigate(['/login']);
          break;
        case 403:
          notificaciones.error('Sin permisos para realizar esta acción');
          break;
        case 404:
          notificaciones.advertencia('Recurso no encontrado');
          break;
        case 422:
          // Errores de validación — el componente los maneja directamente
          break;
        case 429:
          notificaciones.advertencia('Demasiadas solicitudes. Espere un momento.');
          break;
        case 0:
          notificaciones.error('Sin conexión a la red');
          break;
        default:
          if (error.status >= 500) {
            notificaciones.error('Error interno del servidor. Intente más tarde.');
          }
      }
      return throwError(() => error);
    })
  );
};
```

---

## Python — Degradación Elegante (Graceful Degradation)

```python
# Función con fallback explícito
from functools import wraps
from typing import TypeVar, Callable, Any

T = TypeVar("T")


def con_fallback(valor_default: T, *, loguear: bool = True):
    """Decorador que devuelve un valor por defecto ante cualquier excepción."""
    def decorador(func: Callable[..., T]) -> Callable[..., T]:
        @wraps(func)
        def wrapper(*args, **kwargs) -> T:
            try:
                return func(*args, **kwargs)
            except Exception as exc:
                if loguear:
                    logger.warning(
                        "usando_fallback",
                        funcion=func.__name__,
                        excepcion=type(exc).__name__,
                        mensaje=str(exc),
                    )
                return valor_default
        return wrapper
    return decorador


@con_fallback(valor_default=[], loguear=True)
def obtener_recomendaciones(usuario_id: str) -> list[Producto]:
    """Si falla el motor de recomendaciones, devuelve lista vacía."""
    return motor_recomendaciones.calcular(usuario_id)
```
