# Regla: Patrones Go Idiomáticos

Los patrones aquí listados son soluciones probadas para problemas recurrentes
en Go de producción. El lenguaje tiene sus propios modismos — aplicar patrones
de otros lenguajes sin adaptarlos genera código que "funciona pero no es Go".

---

## Functional options para configuración

El patrón de opciones funcionales permite API de construcción extensible sin
romper compatibilidad al agregar nuevas opciones.

```go
// MAL — constructor con muchos parámetros booleanos/opcionales
func NuevoCliente(host string, port int, timeout time.Duration, tls bool, maxConn int) *Cliente { ... }

// BIEN — functional options: cada opción es explícita y opcional
type ClienteOption func(*cliente)

func ConTimeout(d time.Duration) ClienteOption {
    return func(c *cliente) { c.timeout = d }
}

func ConTLS(certPath string) ClienteOption {
    return func(c *cliente) {
        c.tls = true
        c.certPath = certPath
    }
}

func NuevoCliente(host string, opts ...ClienteOption) *Cliente {
    c := &cliente{
        host:    host,
        timeout: 30 * time.Second,  // valores por defecto razonables
        maxConn: 10,
    }
    for _, opt := range opts {
        opt(c)
    }
    return c
}

// Consumo — solo las opciones necesarias
cliente := NuevoCliente("api.ejemplo.com",
    ConTimeout(5*time.Second),
    ConTLS("/certs/client.pem"),
)
```

---

## Interface satisfaction implícita

Go no usa `implements`. Un tipo satisface una interfaz automáticamente si tiene
los métodos requeridos. Verificar en compile time con asignación a blank identifier.

```go
// Verificación en compile time — falla si *FacturaService no implementa la interfaz
var _ FacturaRepository = (*FacturaRepositoryPostgres)(nil)

// Inyección de dependencias: el servicio depende de la interfaz, no del concreto
type FacturaService struct {
    repo   FacturaRepository  // interface
    logger *zap.Logger
}

func NuevoFacturaService(repo FacturaRepository, logger *zap.Logger) *FacturaService {
    return &FacturaService{repo: repo, logger: logger}
}
```

---

## Context propagation: context.Context como primer parámetro

`context.Context` es el primer parámetro de toda función que hace I/O, llama
a servicios externos o puede cancelarse/timeout.

```go
// MAL — sin context: no hay forma de cancelar ni hacer timeout
func (s *FacturaService) Crear(req CrearRequest) (*Factura, error) { ... }

// MAL — context en el struct (compartido entre llamadas)
type FacturaService struct {
    ctx context.Context  // NUNCA almacenar context en struct
}

// BIEN — context como primer parámetro
func (s *FacturaService) Crear(ctx context.Context, req CrearRequest) (*Factura, error) {
    // Propagar el context a llamadas downstream
    if err := s.validar(ctx, req); err != nil {
        return nil, err
    }
    factura, err := s.repo.Guardar(ctx, req.toFactura())
    if err != nil {
        return nil, fmt.Errorf("guardar factura: %w", err)
    }
    return factura, nil
}
```

---

## Error wrapping con fmt.Errorf y %w

Envolver errores agrega contexto sin perder el tipo original para `errors.Is`/`errors.As`.

```go
// MAL — error devuelto sin contexto
if err != nil {
    return err  // el llamador no sabe en qué operación falló
}

// MAL — error formateado como string (pierde el tipo)
if err != nil {
    return fmt.Errorf("error al guardar: " + err.Error())
}

// BIEN — wrapping con %w preserva el tipo para unwrapping
if err != nil {
    return fmt.Errorf("guardar factura %s: %w", factura.ID, err)
}

// Errores sentinel para tipos conocidos
var (
    ErrFacturaNoEncontrada  = errors.New("factura no encontrada")
    ErrClienteInactivo      = errors.New("cliente inactivo")
    ErrSaldoInsuficiente    = errors.New("saldo insuficiente")
)

// Verificación con errors.Is (funciona aunque el error esté envuelto)
if errors.Is(err, ErrFacturaNoEncontrada) {
    return c.JSON(http.StatusNotFound, errorResponse(err))
}
```

---

## Goroutines con errgroup

Usar `golang.org/x/sync/errgroup` para goroutines coordinadas. Propaga errores
y cancelación correctamente.

```go
// MAL — goroutines sin manejo de errores ni cancelación
var wg sync.WaitGroup
for _, id := range ids {
    wg.Add(1)
    go func(id uuid.UUID) {
        defer wg.Done()
        procesarFactura(id)  // ¿cómo se maneja el error?
    }(id)
}
wg.Wait()

// BIEN — errgroup maneja errores y cancela el resto si uno falla
g, ctx := errgroup.WithContext(ctx)
for _, id := range ids {
    id := id  // capturar para la goroutine (Go < 1.22)
    g.Go(func() error {
        return procesarFactura(ctx, id)
    })
}
if err := g.Wait(); err != nil {
    return fmt.Errorf("procesar facturas: %w", err)
}
```

---

## Table-driven tests como patrón por defecto

Ver la regla de testing para el ejemplo completo. Los table-driven tests son
el patrón idiomatic en Go para múltiples casos de prueba del mismo comportamiento.

---

## Middleware pattern para HTTP

```go
// Tipo de middleware
type Middleware func(http.Handler) http.Handler

// Implementación de middleware de autenticación
func RequiereAutenticacion(validador TokenValidador) Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            token := r.Header.Get("Authorization")
            claims, err := validador.Validar(token)
            if err != nil {
                http.Error(w, "No autorizado", http.StatusUnauthorized)
                return
            }
            // Propagar claims via context
            ctx := context.WithValue(r.Context(), claimsKey, claims)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// Composición de middlewares
handler := RequiereAutenticacion(validador)(
    RequiereRol("ADMIN")(
        manejadorFacturas,
    ),
)
```

---

## Preferir comunicación sobre mutex

```go
// MAL — mutex para estado compartido (propenso a deadlocks)
type Contador struct {
    mu    sync.Mutex
    valor int
}

func (c *Contador) Incrementar() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.valor++
}

// BIEN — channel para comunicación entre goroutines (cuando aplica)
func NuevoContador() *Contador {
    c := &Contador{ch: make(chan int, 1)}
    c.ch <- 0  // valor inicial
    go c.ejecutar()
    return c
}

// Nota: mutex es apropiado para estado simple; channels para orquestación
// La regla es: "do not communicate by sharing memory; share memory by communicating"
```

---

## Checklist de patrones Go antes de abrir PR

- [ ] Funciones con múltiples parámetros opcionales usan functional options
- [ ] context.Context es el primer parámetro en toda función con I/O
- [ ] Errores envueltos con fmt.Errorf y %w (no descartados ni concatenados)
- [ ] Goroutines coordinadas usan errgroup, no WaitGroup manual
- [ ] Interfaces verificadas en compile time con `var _ Interface = (*Tipo)(nil)`
- [ ] Sin context.Context almacenado en structs
- [ ] Errores sentinel definidos para tipos de error conocidos
