# Regla: Patrones de Diseño — Rust

Patrones idiomáticos de Rust para código de producción. El objetivo es aprovechar
el sistema de tipos y el ownership model para escribir código correcto por
construcción, no solo por convención.

---

## Error handling: thiserror y anyhow

**thiserror** para librerías (errores tipados que el llamador puede inspeccionar):

```rust
use thiserror::Error;

#[derive(Debug, Error)]
pub enum FacturaError {
    #[error("Factura no encontrada: {id}")]
    NoEncontrada { id: u64 },

    #[error("Total inválido: {total} (debe ser > 0)")]
    TotalInvalido { total: f64 },

    #[error("Error de base de datos")]
    BaseDatos(#[from] sqlx::Error),
}
```

**anyhow** para aplicaciones (propagación de errores con contexto):

```rust
use anyhow::{Context, Result};

async fn cargar_config() -> Result<Config> {
    let ruta = env::var("CONFIG_PATH")
        .context("Variable CONFIG_PATH no definida")?;

    let contenido = fs::read_to_string(&ruta)
        .with_context(|| format!("No se pudo leer el archivo: {ruta}"))?;

    toml::from_str(&contenido)
        .context("Formato de configuración inválido")
}
```

- NUNCA mezclar: `thiserror` en librerías, `anyhow` en binarios/aplicaciones.
- NUNCA retornar `Box<dyn Error>` — usar `anyhow::Error` o un tipo específico.

---

## Builder pattern para structs complejos

Cuando un struct tiene más de 4 campos o campos opcionales, usar builder:

```rust
// MAL — constructor con muchos parámetros, difícil de leer
let factura = Factura::new(123, "cliente@email.com", 1500.0, None, true, "MXN");

// BIEN — builder con nombres explícitos
let factura = Factura::builder()
    .id(123)
    .cliente("cliente@email.com")
    .total(1500.0)
    .moneda("MXN")
    .build()?;
```

- El método `build()` retorna `Result<T, Error>` para validar invariantes.
- Usar el crate `derive_builder` para casos simples.
- Para builders complejos con validación, implementar manualmente.

---

## Newtype pattern para type safety

Envolver tipos primitivos para que el compilador detecte usos incorrectos:

```rust
// MAL — dos u64 intercambiables, el compilador no detecta el error
fn transferir(origen: u64, destino: u64, monto: f64) { ... }
transferir(cuenta_destino, cuenta_origen, 100.0); // invertidos, sin error

// BIEN — tipos distintos que el compilador distingue
struct CuentaId(u64);
struct Monto(f64);

fn transferir(origen: CuentaId, destino: CuentaId, monto: Monto) { ... }
// transferir(cuenta_destino, cuenta_origen, monto); // ERROR DE COMPILACIÓN
```

- Implementar `Display`, `Debug`, `From<u64>` y `Into<u64>` según necesidad.
- Implementar `Deref` solo cuando el newtype ES el tipo interno (no solo lo contiene).

---

## Trait objects vs generics

| Situación | Usar | Razón |
|-----------|------|-------|
| Tipo conocido en compilación, performance crítica | `impl Trait` / genéricos | Monomorphization, sin overhead |
| Colección heterogénea de implementadores | `Box<dyn Trait>` | Dispatch dinámico necesario |
| Trait object en función | `&dyn Trait` | Sin heap allocation |
| Tamaño del binario importa | Genéricos con cuidado | Monomorphization puede inflarlo |

```rust
// Genéricos — dispatch estático, cero overhead
fn procesar<T: Pagable>(item: &T) -> Resultado { ... }

// Trait object — dispatch dinámico, cuando el tipo varía en runtime
fn registrar_handler(handler: Box<dyn EventHandler>) { ... }
```

---

## From/Into para conversiones idiomáticas

Implementar `From<T>` automáticamente proporciona `Into<T>`:

```rust
impl From<FacturaDb> for Factura {
    fn from(db: FacturaDb) -> Self {
        Factura {
            id: FacturaId(db.id),
            total: Monto(db.total),
            estatus: db.estatus.parse().unwrap_or_default(),
        }
    }
}

// El llamador puede usar From o Into indistintamente
let factura = Factura::from(fila_db);
let factura: Factura = fila_db.into();
```

- NUNCA implementar `Into<T>` directamente — implementar `From<T>` y dejar que
  el compilador derive `Into<T>` automáticamente.
- Implementar `TryFrom<T>` cuando la conversión puede fallar.

---

## Option combinators

```rust
// MAL — if-let verboso para transformaciones simples
let nombre_upper = if let Some(n) = usuario.nombre {
    Some(n.to_uppercase())
} else {
    None
};

// BIEN — combinators
let nombre_upper = usuario.nombre.map(|n| n.to_uppercase());

// MAL — unwrap con default innecesariamente verboso
let total = factura.total.unwrap_or(0.0);

// BIEN — unwrap_or_default cuando el default del tipo es correcto
let total = factura.total.unwrap_or_default(); // 0.0 para f64

// Cadena de operaciones con Option
let descuento = usuario
    .membresia
    .filter(|m| m.esta_activa())
    .map(|m| m.porcentaje_descuento())
    .unwrap_or(0.0);
```

- `map` para transformar el valor interno.
- `and_then` para operaciones que pueden retornar `None`.
- `or_else` para proveer un alternativo si el valor es `None`.
- `unwrap_or_default` cuando el default del tipo es el valor correcto.
- `ok_or_else` para convertir `Option<T>` en `Result<T, E>`.

---

## Async con Tokio

```rust
use tokio::{join, select, spawn};

// Ejecutar tareas independientes en paralelo
async fn cargar_datos(id: u64) -> Result<DatoCompleto> {
    let (usuario, facturas) = join!(
        repo.obtener_usuario(id),
        repo.listar_facturas(id)
    )?;
    Ok(DatoCompleto { usuario, facturas })
}

// Spawn para background tasks
spawn(async move {
    if let Err(e) = enviar_notificacion(evento).await {
        tracing::error!("Error enviando notificación: {e}");
    }
});

// Select para timeout o cancelación
select! {
    resultado = operacion_lenta() => manejar(resultado),
    _ = tokio::time::sleep(Duration::from_secs(30)) => {
        return Err(Error::Timeout);
    }
}
```

- NUNCA usar `std::thread::sleep` en código async — usar `tokio::time::sleep`.
- NUNCA bloquear el thread de Tokio con operaciones sincrónicas largas.
  Usar `tokio::task::spawn_blocking` para código CPU-intensivo o IO síncrono.
- `#[tokio::main]` solo en `main()`. El resto son funciones `async fn` normales.

---

## RAII: Drop trait para cleanup

```rust
struct ConexionBd {
    inner: pg::Connection,
}

impl Drop for ConexionBd {
    fn drop(&mut self) {
        // Se ejecuta automáticamente al salir del scope
        if let Err(e) = self.inner.cerrar() {
            tracing::warn!("Error cerrando conexión: {e}");
        }
    }
}

// El cleanup es automático — no hay finally ni try/catch necesario
{
    let conn = ConexionBd::conectar(&url)?;
    conn.ejecutar(query)?;
} // conn.drop() se llama aquí automáticamente
```

- Usar RAII para recursos que necesiten cleanup garantizado.
- Los guardas de Mutex (`MutexGuard`) son RAII — liberan el lock al salir del scope.

---

## Checklist de patrones antes de hacer merge

- [ ] Errores de librería usan `thiserror`, errores de aplicación usan `anyhow`
- [ ] Sin `Box<dyn Error>` como tipo de retorno
- [ ] Structs complejos (>4 campos opcionales) usan builder pattern
- [ ] IDs y valores de dominio usan newtype pattern
- [ ] Sin `unwrap()` en `src/` fuera de tests (ver regla de estilo)
- [ ] Option manejado con combinators, no con if-let cuando es una transformación
- [ ] Tareas async independientes usand `join!` para ejecución paralela
- [ ] Sin bloqueo del runtime async con operaciones síncronas largas
