# Regla: Hooks de Calidad — Rust

Checks automáticos que se ejecutan en distintos puntos del ciclo de desarrollo
para detectar problemas antes de que lleguen a revisión de código o a producción.
Ningún check es opcional — todos bloquean el flujo si detectan un problema.

---

## cargo fmt --check antes de commit

**Pre-commit hook** — bloquea el commit si el código no está formateado:

```bash
#!/usr/bin/env bash
# .git/hooks/pre-commit o configurado via pre-commit framework

set -e

echo "Verificando formato con rustfmt..."
cargo fmt --check

if [ $? -ne 0 ]; then
    echo "ERROR: El código no está formateado."
    echo "Ejecutar: cargo fmt"
    exit 1
fi
```

Con el framework `pre-commit` (recomendado para equipos):

```yaml
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: cargo-fmt
        name: cargo fmt
        language: system
        entry: cargo fmt --check
        pass_filenames: false
```

- Si el check falla: ejecutar `cargo fmt` (sin `--check`) y hacer el commit de nuevo.
- NUNCA commitear con `--no-verify` para saltarse el formato.

---

## cargo clippy -- -D warnings antes de commit

**Pre-commit hook** — bloquea el commit si hay warnings de Clippy:

```bash
echo "Ejecutando Clippy..."
cargo clippy --all-targets --all-features -- -D warnings

if [ $? -ne 0 ]; then
    echo "ERROR: Clippy encontró warnings."
    echo "Corregir los warnings antes de hacer commit."
    exit 1
fi
```

- `--all-targets` incluye tests, benchmarks y ejemplos — no solo el código de producción.
- `--all-features` activa todos los feature flags para detectar problemas en cualquier configuración.
- Si un lint específico de Clippy es un falso positivo para el caso en cuestión,
  suprimir con `#[allow(clippy::lint_name)]` en el sitio exacto, con comentario.
  NUNCA suprimir a nivel de crate.

---

## cargo test antes de merge

**CI — bloquea el merge si algún test falla**:

```yaml
# .github/workflows/ci.yml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2

      - name: Ejecutar tests
        run: cargo test --all-targets --all-features

      - name: Cobertura de código
        run: |
          cargo install cargo-llvm-cov --locked
          cargo llvm-cov --all-features --fail-under-lines 80
```

- `cargo test` se ejecuta en CI en cada PR — no solo antes de merge.
- Los tests deben pasar en el runner de CI, no solo en la máquina local.
- La cobertura se verifica como parte del mismo job de tests.

---

## cargo-audit en CI semanal

**Job separado — ejecuta semanalmente y en PRs que modifican Cargo.lock**:

```yaml
# .github/workflows/audit.yml
name: Security Audit

on:
  schedule:
    - cron: '0 6 * * 1'  # Lunes a las 6am UTC
  push:
    paths:
      - 'Cargo.lock'
      - 'Cargo.toml'

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: rustsec/audit-check@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
```

- El job de auditoría falla si encuentra vulnerabilidades HIGH o CRITICAL.
- Las alertas de `cargo audit` se notifican al canal de seguridad del equipo.
- Vulnerabilidades MEDIUM generan warning — deben resolverse en <2 semanas.

---

## Detección de println! en código no-test

**Custom Clippy lint via CI** — detecta prints de debug que no deben llegar a producción:

```bash
# En el job de CI, después de los tests
echo "Verificando que no hay println! en código de producción..."

# Buscar println! en src/ pero no en mod tests ni en main.rs
if grep -rn "println!" src/ --include="*.rs" | grep -v "#\[cfg(test)\]" | grep -v "fn main"; then
    echo "ERROR: println! encontrado en código de producción."
    echo "Usar tracing::info! o tracing::debug! en su lugar."
    exit 1
fi
```

Alternativamente, via Clippy con configuración en `clippy.toml`:

```toml
# clippy.toml
# Prohibir macros de debugging en código de producción
disallowed-macros = [
    { path = "std::println", reason = "Usar tracing::info! en código de producción" },
    { path = "std::eprintln", reason = "Usar tracing::error! en código de producción" },
    { path = "std::dbg", reason = "Macro de debugging — eliminar antes de merge" },
]
```

- `println!` y `eprintln!` están permitidos solo en:
  - `src/main.rs` para output esperado de CLIs
  - `#[cfg(test)] mod tests`
  - Ejemplos en `examples/`
- En el resto del código: usar `tracing::info!`, `tracing::debug!`, `tracing::error!`.

---

## Detección de unwrap() y expect() en src/

**Parte del hook de pre-commit** — detecta panics potenciales en código de producción:

```bash
echo "Verificando ausencia de unwrap()/expect() en código de producción..."

# Buscar en src/ excluyendo módulos de test
if grep -rn "\.unwrap()\|\.expect(" src/ --include="*.rs" \
   | grep -v "#\[cfg(test)\]" \
   | grep -v "// ALLOW-UNWRAP:"; then
    echo "ERROR: unwrap()/expect() encontrado en código de producción."
    echo "Usar el operador ? o manejo explícito de errores."
    echo "Si el unwrap() está justificado, agregar comentario: // ALLOW-UNWRAP: razón"
    exit 1
fi
```

- `unwrap()` y `expect()` están permitidos en:
  - `#[cfg(test)] mod tests`
  - `src/main.rs` para inicialización de la aplicación (con `expect()` descriptivo)
  - Código marcado con `// ALLOW-UNWRAP: <razón explícita>`
- Cuando se necesita `expect()` fuera de tests: el mensaje debe explicar por qué
  el `None`/`Err` es imposible en ese punto.

---

## Configuración completa recomendada

```yaml
# .pre-commit-config.yaml — instalar con: pre-commit install
repos:
  - repo: local
    hooks:
      - id: cargo-fmt
        name: Formato (rustfmt)
        language: system
        entry: cargo fmt --check
        pass_filenames: false

      - id: cargo-clippy
        name: Linter (clippy)
        language: system
        entry: cargo clippy --all-targets -- -D warnings
        pass_filenames: false

      - id: no-println
        name: Sin println! en producción
        language: pygrep
        entry: '\bprintln!\('
        files: '^src/(?!main\.rs).*\.rs$'
        exclude: '#\[cfg\(test\)\]'

      - id: no-unwrap
        name: Sin unwrap() en producción
        language: pygrep
        entry: '\.unwrap\(\)'
        files: '^src/.*\.rs$'
        exclude: '(#\[cfg\(test\)\]|ALLOW-UNWRAP)'
```

---

## Checklist de hooks antes de integrar al proyecto

- [ ] `pre-commit install` ejecutado en el repositorio del proyecto
- [ ] `cargo fmt --check` en el hook de pre-commit
- [ ] `cargo clippy -- -D warnings` en el hook de pre-commit
- [ ] `cargo test` en el pipeline de CI (cada PR)
- [ ] `cargo audit` en job semanal de CI
- [ ] Detección de `println!` en `src/` fuera de tests y main
- [ ] Detección de `unwrap()` en `src/` fuera de tests
- [ ] Cobertura mínima del 80% verificada en CI
