---
name: go-patrones
version: "1.0.0"
description: >
  Patrones idiomáticos de Go: functional options, interfaces pequeñas, error
  wrapping, middleware chain, repository pattern y generics básicos (Go 1.18+).
  Cargar cuando se diseñe arquitectura Go o se revise código existente.
herramientasPermitidas: [Read, Glob, Grep]
exclusiones:
  - "No cargar para implementar handlers HTTP o lógica concurrente en Go — para implementación concreta cargar `go-experto`; este skill cubre diseño de arquitectura de paquetes."
  - "No cargar para escribir tests en Go — para testing cargar `go-testing`."
  - "No cargar para errores de compilación Go — para build errors cargar `build-errors-go`."
  - "No cargar para patrones de diseño en otros lenguajes — las interfaces pequeñas y functional options son patrones específicos del idioma Go."
evolvable: true  # default para skill estandar
---
# Go Patrones — Diseño Idiomático Go

Cubre los patrones que producen código Go mantenible y testeable: functional
options para configuración, interfaces de 1-3 métodos, error handling avanzado,
middleware chain, repository pattern y generics básicos.

## Cuándo cargar este skill

Invoca `Skill("go-patrones")` cuando:

- Se diseñe una nueva API pública de un paquete Go
- Se refactorice código con structs de configuración complejos
- Se implemente un middleware chain HTTP
- Se diseñe la capa de acceso a datos (repository pattern)
- Se evalúen los anti-patrones de global state o init() abuse

## Cuándo NO cargar

- La pregunta es sobre implementar handlers HTTP concretos o lógica concurrente — para implementación cargar `go-experto`; este skill cubre decisiones de diseño de paquetes.
- La tarea es escribir tests unitarios o de integración en Go — cargar `go-testing`.
- La pregunta es sobre errores de compilación Go — cargar `build-errors-go`.
- Los patrones buscados son de otro lenguaje (Java, Python) — las interfaces pequeñas y functional options son patrones idiomáticos de Go, no directamente aplicables en otros lenguajes.

## Conceptos clave

### Interfaces pequeñas — cuanto más chica, más reutilizable

La interfaz de Go se satisface implícitamente. Una interfaz de 1-2 métodos
puede ser satisfecha por muchas más implementaciones y es más fácil de testear.
La regla: aceptar interfaces, retornar tipos concretos.

### Functional Options Pattern

Permite configuración opcional con valores default sin romper la firma del
constructor cuando se agregan opciones nuevas. Elimina structs Config con
campos opcionales difíciles de distinguir.

### Zero Value Util

El zero value de un tipo debe ser útil o al menos seguro de usar. Un struct
con zero value inválido es un bug en espera.

## Reglas obligatorias

### Interfaces de máximo 3 métodos — dividir si crece más

Una interfaz con 10 métodos raramente puede ser implementada limpiamente.
Si crece, dividirla en interfaces específicas que se componen.

```go
// MAL — difícil de mockear, acopla demasiado
type PedidoStore interface {
    Crear(ctx, req) (*Pedido, error)
    Actualizar(ctx, id, req) (*Pedido, error)
    Eliminar(ctx, id) error
    Buscar(ctx, filtro) ([]*Pedido, error)
    BuscarPorCliente(ctx, clienteID) ([]*Pedido, error)
    Contar(ctx, filtro) (int, error)
}

// BIEN — interfaces por responsabilidad
type PedidoWriter interface {
    Crear(ctx context.Context, req CrearPedidoRequest) (*Pedido, error)
    Actualizar(ctx context.Context, id uuid.UUID, req ActualizarRequest) (*Pedido, error)
}
type PedidoReader interface {
    Buscar(ctx context.Context, filtro Filtro) ([]*Pedido, error)
}
```

### NUNCA usar init() para logica de negocio

`init()` se ejecuta antes de main, en orden no garantizado entre paquetes.
Solo para registro de drivers o validación de configuración obligatoria.

### NUNCA usar variables globales mutables en paquetes de biblioteca

El estado global impide tests paralelos y hace el comportamiento dependiente
del orden de inicialización. Usar inyección de dependencias.

## Patrones recomendados

### Functional options

```go
type Server struct {
    addr    string
    timeout time.Duration
    maxConn int
}

type Option func(*Server)

func ConAddr(addr string) Option {
    return func(s *Server) { s.addr = addr }
}
func ConTimeout(d time.Duration) Option {
    return func(s *Server) { s.timeout = d }
}
func ConMaxConn(n int) Option {
    return func(s *Server) { s.maxConn = n }
}

func NuevoServer(opts ...Option) *Server {
    s := &Server{          // defaults seguros
        addr:    ":8080",
        timeout: 30 * time.Second,
        maxConn: 100,
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Uso — solo se pasan las opciones que cambian
srv := NuevoServer(
    ConAddr(":9090"),
    ConTimeout(60 * time.Second),
)
```

### Middleware chain

```go
type Middleware func(http.Handler) http.Handler

func Cadena(h http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        h = middlewares[i](h)
    }
    return h
}

func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        slog.InfoContext(r.Context(), "request",
            "method", r.Method,
            "path", r.URL.Path,
            "duracion", time.Since(start),
        )
    })
}
```

### Repository pattern

```go
// Interfaz en el paquete de dominio
type PedidoRepository interface {
    Obtener(ctx context.Context, id uuid.UUID) (*Pedido, error)
    Guardar(ctx context.Context, p *Pedido) error
}

// Implementación en paquete de infraestructura
type postgresPedidoRepo struct {
    db *pgxpool.Pool
}

func NuevoPedidoRepo(db *pgxpool.Pool) PedidoRepository {
    return &postgresPedidoRepo{db: db}
}

func (r *postgresPedidoRepo) Obtener(ctx context.Context, id uuid.UUID) (*Pedido, error) {
    var p Pedido
    err := r.db.QueryRow(ctx,
        "SELECT id, cliente_id, total FROM pedidos WHERE id = $1", id,
    ).Scan(&p.ID, &p.ClienteID, &p.Total)
    if errors.Is(err, pgx.ErrNoRows) {
        return nil, ErrNotFound
    }
    return &p, err
}
```

### Generics para contenedores tipados (Go 1.18+)

```go
// Mapa con default value
func ObtenerODefault[K comparable, V any](m map[K]V, key K, def V) V {
    if v, ok := m[key]; ok {
        return v
    }
    return def
}

// Filter generico
func Filtrar[T any](slice []T, pred func(T) bool) []T {
    resultado := make([]T, 0)
    for _, v := range slice {
        if pred(v) {
            resultado = append(resultado, v)
        }
    }
    return resultado
}
```

### Embed para recursos estaticos

```go
//go:embed templates/*.html
var templateFS embed.FS

//go:embed migrations/*.sql
var migrationsFS embed.FS

// Uso en producción y tests — sin path absolutos
tmpl, err := template.ParseFS(templateFS, "templates/email.html")
```

## Anti-patrones conocidos

### Global state mutable

```go
// MAL — estado global que rompe tests paralelos
var dbConnection *sql.DB
func init() {
    dbConnection, _ = sql.Open("postgres", os.Getenv("DB_URL"))
}

// BIEN — inyección por constructor
type App struct { db *sql.DB }
func NewApp(db *sql.DB) *App { return &App{db: db} }
```

### Interfaz definida por el productor, no el consumidor

```go
// MAL — el paquete "pedidos" define una interfaz grande que
// obliga a los consumidores a implementar todo
package pedidos
type Repositorio interface { /* 8 métodos */ }

// BIEN — cada consumidor define la interfaz mínima que necesita
package service
type pedidoLector interface {  // solo lo que este servicio usa
    Obtener(ctx context.Context, id uuid.UUID) (*Pedido, error)
}
```

### Panic para control de flujo normal

```go
// MAL
func obtenerConfig(key string) string {
    v, ok := configs[key]
    if !ok { panic("config no encontrada: " + key) }
    return v
}

// BIEN
func obtenerConfig(key string) (string, error) {
    v, ok := configs[key]
    if !ok { return "", fmt.Errorf("config no encontrada: %s", key) }
    return v, nil
}
```

## Checklist de verificación

- [ ] Interfaces de máximo 3 métodos; interfaces más grandes divididas
- [ ] Interfaces definidas en el paquete consumidor, no en el productor
- [ ] Functional options para structs con más de 3 campos opcionales
- [ ] Sin variables globales mutables en paquetes de biblioteca
- [ ] init() solo para registro de drivers, no lógica de negocio
- [ ] Repository pattern separa interfaz (dominio) de implementación (infra)
- [ ] Generics usados para contenedores, no para simular herencia

## Gotchas / Errores comunes no obvios

**Functional options que modifican el struct después de la inicialización no tienen efecto si el struct fue copiado**: si `WithTimeout(5 * time.Second)` modifica un campo del struct por puntero pero la función `New()` retorna el struct por valor (no `*MiStruct`), la opción modifica una copia temporal. Fix: siempre retornar puntero `*T` en funciones `New()` que aceptan functional options, y asegurarse que las opciones reciben `*T`, no `T`.

**Interface implementada en puntero receptor pero usada con valor receiver causa error de compilación difícil de leer**: si `func (s *Servidor) ServeHTTP(...)` implementa `http.Handler`, pasar `Servidor{}` (valor) donde se espera `http.Handler` falla con "does not implement interface". El mensaje de error no indica el puntero claramente. Fix: siempre usar `&Servidor{}` cuando el método de interfaz está en el puntero receptor, o definir los métodos en value receiver si el tipo no necesita mutación.

**Embebido de interface en struct para "implementación parcial" causa panic en runtime**: embeber una interfaz en un struct para satisfacer el type system sin implementar todos los métodos `type MockRepo struct { RepositorioInterface }` compila pero llama nil en runtime si se invoca el método no implementado. Fix: implementar todos los métodos de la interfaz explícitamente, o usar un framework de mocking que genere el mock completo.

**`init()` con efectos secundarios globales rompe la testabilidad del paquete**: usar `init()` para registrar handlers, conectar a BD o inicializar loggers globales hace que importar el paquete en tests ejecute esos efectos. Fix: exponer una función `Setup()` o `New()` explícita que el llamador invoca cuando quiere los efectos secundarios; reservar `init()` solo para registro de drivers (`sql.Register`).

---
*Skill creado con swl:crear-skill el 2026-03-31. Versión 1.0.0.*
