# Regla: Hooks de Calidad para Swift

Esta regla define las verificaciones automáticas obligatorias que corren en cada
commit y merge para código Swift. SwiftFormat y SwiftLint detectan en segundos
errores que de otro modo llegan a code review o a producción.

---

## SwiftFormat antes de commit (pre-commit hook)

SwiftFormat aplica formato de manera determinista. El hook corre en modo de formato
y falla si el archivo queda con cambios sin stagear (significa que había diferencias).

```bash
#!/bin/sh
# .git/hooks/pre-commit

SWIFT_FILES=$(git diff --cached --name-only | grep "\.swift$")

if [ -z "$SWIFT_FILES" ]; then
    exit 0
fi

echo "Ejecutando SwiftFormat..."
echo "$SWIFT_FILES" | xargs swiftformat --config .swiftformat

# Si SwiftFormat modificó archivos, abortar para que el dev revise
if ! git diff --quiet; then
    echo "SwiftFormat modificó archivos. Revisa los cambios y vuelve a hacer commit:"
    git diff --name-only
    exit 1
fi
echo "SwiftFormat: OK"
```

Configuración base `.swiftformat` del proyecto:
```
--indent 4
--linebreaks lf
--maxwidth 120
--wraparguments before-first
--closingparen balanced
--semicolons never
--trailingCommas always
--importgrouping testable-bottom
```

---

## SwiftLint --strict antes de commit

```bash
# Pre-commit — continua del hook anterior
echo "Ejecutando SwiftLint..."
echo "$SWIFT_FILES" | xargs swiftlint lint --strict --config .swiftlint.yml

if [ $? -ne 0 ]; then
    echo "SwiftLint encontro violaciones. Corregir antes de hacer commit."
    exit 1
fi
echo "SwiftLint: OK"
```

Configuración crítica en `.swiftlint.yml` que NUNCA debe desactivarse:
```yaml
opt_in_rules:
  - force_unwrapping          # detecta ! en producción
  - force_try                 # detecta try! en producción
  - force_cast                # detecta as! en producción
  - implicitly_unwrapped_optional
  - discouraged_optional_boolean

custom_rules:
  no_print:
    name: "No print en producción"
    regex: '^\s*print\('
    message: "Usar Logger del proyecto en lugar de print()"
    severity: error
    excluded: ".*Tests.*\\.swift"
```

---

## swift build antes de merge (CI obligatorio)

El build completo con tests corre en CI antes de aprobar cualquier merge.
El pipeline bloqueante en GitHub Actions:

```yaml
# .github/workflows/ci.yml
name: CI Swift

on: [push, pull_request]

jobs:
  build-and-test:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4

      - name: SwiftLint
        run: swiftlint lint --strict

      - name: SwiftFormat (verificación)
        run: swiftformat --lint .

      - name: Build
        run: xcodebuild build-for-testing
          -scheme MiApp
          -destination 'platform=iOS Simulator,name=iPhone 15'

      - name: Tests
        run: xcodebuild test-without-building
          -scheme MiApp
          -destination 'platform=iOS Simulator,name=iPhone 15'
          -enableCodeCoverage YES

      - name: Verificar cobertura
        run: |
          COBERTURA=$(xcrun xccov view --report DerivedData/Logs/Test/*.xcresult --json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['lineCoverage']*100)")
          echo "Cobertura: $COBERTURA%"
          python3 -c "import sys; sys.exit(0 if float('$COBERTURA') >= 80 else 1)"
```

---

## Detección de print() en código no-test

`print()` en producción contamina la consola y puede exponer datos sensibles en logs.
Solo se permite en archivos que contienen `Tests` en su nombre o ruta.

```bash
# Script de detección — integrado en pre-commit
SWIFT_MAIN=$(git diff --cached --name-only | grep "\.swift$" | grep -v -i "test")

if [ -n "$SWIFT_MAIN" ]; then
    MATCHES=$(echo "$SWIFT_MAIN" | xargs grep -En "^\s*print\(" 2>/dev/null)
    if [ -n "$MATCHES" ]; then
        echo "ERROR: print() encontrado en código de producción:"
        echo "$MATCHES"
        echo "Usar Logger o os.log en lugar de print()."
        exit 1
    fi
fi
```

Alternativa recomendada al `print()`:
```swift
import os.log

private let logger = Logger(subsystem: "com.miapp.pedidos", category: "PedidoViewModel")

// En lugar de print("Pedido cargado: \(pedido.id)")
logger.info("Pedido cargado: \(pedido.id, privacy: .public)")
```

---

## Detección de force unwrap (!) en src/

El force unwrap `!` en producción es equivalente al `!!` de Kotlin: una excepción
garantizada en el peor momento posible.

```bash
# Detección de ! en archivos de producción (excluir tests y comentarios)
SWIFT_MAIN=$(git diff --cached --name-only | grep "\.swift$" | grep -v -i "test")

if [ -n "$SWIFT_MAIN" ]; then
    # Detectar: variable!, as!, try! — excluir !=, !important, comentarios
    MATCHES=$(echo "$SWIFT_MAIN" | xargs grep -En "[a-zA-Z0-9_\)]\![^=]|as\!|try\!" 2>/dev/null \
        | grep -v "//.*!" \
        | grep -v "^\s*//" )
    if [ -n "$MATCHES" ]; then
        echo "ERROR: Force unwrap (!) encontrado en código de producción:"
        echo "$MATCHES"
        echo "Usar guard let, if let, o try? con manejo explícito del nil/error."
        exit 1
    fi
fi
```

SwiftLint con `force_unwrapping`, `force_try` y `force_cast` en `opt_in_rules`
también detecta estos casos.

---

## Detección de fatalError en código no-test

`fatalError()` termina la app inmediatamente. Solo se acepta en:
- Tests (para indicar setup incorrecto)
- Casos que son literalmente imposibles y son errores de programación (no errores de runtime)

```bash
# Detección de fatalError en producción
SWIFT_MAIN=$(git diff --cached --name-only | grep "\.swift$" | grep -v -i "test")

if [ -n "$SWIFT_MAIN" ]; then
    MATCHES=$(echo "$SWIFT_MAIN" | xargs grep -En "fatalError\(" 2>/dev/null | grep -v "//.*fatalError")
    if [ -n "$MATCHES" ]; then
        echo "ADVERTENCIA: fatalError() encontrado en código de producción:"
        echo "$MATCHES"
        echo "Considerar lanzar un error tipado o usar preconditionFailure() con justificación."
        # exit 1 — cambiar a error si el proyecto tiene política estricta
    fi
fi
```

---

## Configuración de pre-commit con pre-commit framework

Para equipos que prefieren una herramienta declarativa:

```yaml
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: swiftformat
        name: SwiftFormat
        entry: swiftformat
        language: system
        types: [swift]
        args: [--config, .swiftformat]

      - id: swiftlint
        name: SwiftLint
        entry: swiftlint
        language: system
        types: [swift]
        args: [lint, --strict, --config, .swiftlint.yml]

      - id: no-print
        name: No print() en producción
        entry: bash -c 'grep -En "^\s*print\(" "$@" | grep -v "test" && exit 1 || exit 0'
        language: system
        types: [swift]

      - id: no-force-unwrap
        name: No force unwrap en producción
        entry: bash -c 'grep -En "[a-zA-Z0-9_\)]\![^=]" "$@" | grep -v "test" | grep -v "//.*!" && exit 1 || exit 0'
        language: system
        types: [swift]
```

---

## Checklist de hooks Swift

- [ ] SwiftFormat configurado y corriendo en pre-commit
- [ ] SwiftLint con `--strict` en pre-commit
- [ ] `force_unwrapping`, `force_try`, `force_cast` en `opt_in_rules` de SwiftLint
- [ ] swift build y tests corren en CI antes de aprobar merge
- [ ] Detección de `print()` activa para `src/` (excluye tests)
- [ ] Detección de `!` (force unwrap) activa para `src/`
- [ ] Detección de `fatalError()` activa (al menos como advertencia)
- [ ] pre-commit o lefthook instalado y versionado en el repositorio
