# Regla: Hooks de Desarrollo Go

Go tiene excelentes herramientas de análisis estático integradas o de la comunidad.
Automatizarlas como hooks garantiza que el código que llega al repositorio ya
superó las verificaciones básicas, reduciendo el ruido en code review.

---

## go vet obligatorio antes de commit

`go vet` examina el código fuente e identifica construcciones sospechosas que
el compilador no reporta como errores pero que probablemente son bugs.

```bash
# Verificación básica — debe pasar siempre
go vet ./...
```

Casos que `go vet` detecta:
- `Printf`-like calls con formato incorrecto (`fmt.Sprintf("%d", "string")`)
- Variables de loop capturadas incorrectamente en goroutines
- Llamadas a `sync.Mutex` después de copiar el struct
- `unreachable code` después de return/break/continue

Pre-commit hook:

```bash
#!/bin/sh
# .git/hooks/pre-commit
echo "Ejecutando go vet..."
if ! go vet ./...; then
    echo "ERROR: go vet encontró problemas. Corregir antes de hacer commit."
    exit 1
fi
echo "go vet: OK"
```

---

## staticcheck como linter principal

`staticcheck` es el analizador estático más completo para Go. Detecta bugs,
código ineficiente, código simplificable y APIs deprecadas.

```bash
# Instalar
go install honnef.co/go/tools/cmd/staticcheck@latest

# Ejecutar
staticcheck ./...
```

Verificaciones clave de staticcheck:
- SA: Análisis estático (bugs reales como nil dereference, locks mal usados)
- S: Código simplificable
- ST: Violaciones de estilo
- QF: Sugerencias de quickfix (código mejorable)
- Deprecations: uso de APIs deprecadas de la stdlib

Configuración en `staticcheck.conf` en la raíz del proyecto:

```ini
# staticcheck.conf
checks = ["all", "-ST1000", "-ST1003"]
# ST1000: comentario de paquete (puede ser ruidoso en paquetes internos)
# ST1003: naming de identificadores (demasiado estricto para nombres en español)
```

---

## golangci-lint con configuración del proyecto

`golangci-lint` agrega una capa de verificación coordinando múltiples linters.
La configuración en `.golangci.yml` garantiza que todos en el equipo usan los mismos checks.

```yaml
# .golangci.yml — configuración del proyecto
run:
  timeout: 5m
  go: "1.21"

linters:
  enable:
    - errcheck       # errores no verificados
    - gosimple       # simplificaciones de código
    - govet          # vet checks
    - ineffassign    # asignaciones sin uso
    - staticcheck    # análisis estático completo
    - unused         # código sin usar
    - goimports      # imports no organizados
    - misspell       # typos en comentarios
    - godot          # punto final en comentarios
    - gosec          # seguridad
    - bodyclose      # response body no cerrado
    - noctx          # HTTP requests sin context

linters-settings:
  errcheck:
    check-type-assertions: true
  govet:
    enable:
      - shadow        # detección de variable shadowing

issues:
  exclude-rules:
    # En archivos de test, errcheck es menos estricto
    - path: "_test\\.go"
      linters:
        - errcheck
```

```bash
# Ejecutar
golangci-lint run ./...

# En CI con reporte
golangci-lint run --out-format github-actions ./...
```

---

## Detección de fmt.Println en código no-test

`fmt.Println` y `fmt.Printf` en código de producción son logs no estructurados
que no se pueden filtrar, enrutar ni analizar. Usar `zap`, `slog` o el logger
del proyecto.

Hook de detección:

```bash
#!/bin/sh
# Detectar fmt.Println/Printf en src que no son test
ARCHIVOS=$(grep -r "fmt\.Print" --include="*.go" \
    --exclude="*_test.go" \
    --exclude-dir=vendor \
    -l . 2>/dev/null)

if [ -n "$ARCHIVOS" ]; then
    echo "ERROR: fmt.Print* detectado en código de producción."
    echo "Usar el logger del proyecto (zap/slog) en su lugar."
    echo "Archivos afectados:"
    echo "$ARCHIVOS"
    exit 1
fi
```

Con golangci-lint, agregar el linter `forbidigo`:

```yaml
# En .golangci.yml
linters:
  enable:
    - forbidigo

linters-settings:
  forbidigo:
    forbid:
      - pattern: "^fmt\\.Print"
        msg: "Usar el logger del proyecto en lugar de fmt.Print*"
```

---

## go mod tidy antes de commit

`go mod tidy` asegura que `go.mod` y `go.sum` están sincronizados con las
importaciones reales del proyecto. Sin esto, el CI puede fallar con módulos
que no están en el go.sum.

```bash
#!/bin/sh
# Verificar que go.mod y go.sum están actualizados
go mod tidy

# Si go mod tidy modificó archivos, el commit no debe proceder
if ! git diff --quiet go.mod go.sum; then
    echo "ERROR: go.mod o go.sum están desactualizados."
    echo "Ejecutar 'go mod tidy' y agregar los cambios al commit."
    exit 1
fi
```

---

## errcheck: verificación de errores no manejados

`errcheck` como parte de golangci-lint detecta errores ignorados sin el
`_ =` explícito, y también con él si `check-type-assertions: true`.

```go
// MAL — estos casos son detectados por errcheck
repositorio.Guardar(ctx, factura)      // error retornado e ignorado
archivo, _ := os.Open(ruta)            // error ignorado con blank identifier

// BIEN — error manejado explícitamente
if err := repositorio.Guardar(ctx, factura); err != nil {
    return fmt.Errorf("guardar factura: %w", err)
}

archivo, err := os.Open(ruta)
if err != nil {
    return fmt.Errorf("abrir archivo %s: %w", ruta, err)
}
```

---

## Pipeline CI completa de calidad Go

Orden de ejecución (cada fase bloquea las siguientes):

```bash
# 1. Formato
gofmt -l . && test -z "$(gofmt -l .)"

# 2. Imports
goimports -l . && test -z "$(goimports -l .)"

# 3. Módulos actualizados
go mod tidy && git diff --exit-code go.mod go.sum

# 4. Compilación
go build ./...

# 5. Tests con race detection
go test -race ./...

# 6. Análisis estático
golangci-lint run ./...

# 7. Seguridad
gosec -severity medium ./...

# 8. Cobertura
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out | grep total
```

---

## Checklist de hooks Go antes de hacer commit

- [ ] `go vet ./...` pasa sin errores
- [ ] `gofmt -l .` retorna vacío
- [ ] `go mod tidy` no modifica go.mod ni go.sum
- [ ] Sin `fmt.Println`/`fmt.Printf` en código fuera de tests
- [ ] `golangci-lint run ./...` pasa
- [ ] `gosec ./...` sin severidad media o alta
- [ ] `go test -race ./...` pasa sin data races
