# Regla: Patrones Java Idiomáticos

Los patrones aquí listados no son opcionales: representan las soluciones probadas
para problemas recurrentes en código Java de producción. Ignorarlos genera deuda
técnica predecible y bugs conocidos.

---

## Inyección de dependencias vía constructor (nunca field injection)

La inyección por campo (`@Autowired` en el campo) oculta dependencias, impide
tests sin contenedor IoC y viola el principio de inmutabilidad.

```java
// MAL — field injection: dependencias ocultas, imposible testear sin Spring
@Service
public class FacturaService {
    @Autowired
    private FacturaRepository repositorio;  // no se ve en el constructor
    @Autowired
    private EventoPublicador publicador;
}

// BIEN — constructor injection: dependencias explícitas, testeable con mocks simples
@Service
public class FacturaService {
    private final FacturaRepository repositorio;
    private final EventoPublicador publicador;

    public FacturaService(FacturaRepository repositorio, EventoPublicador publicador) {
        this.repositorio = repositorio;
        this.publicador = publicador;
    }
}
```

Con Lombok: `@RequiredArgsConstructor` en la clase + campos `final` genera el
constructor automáticamente sin renunciar a la inyección por constructor.

---

## Repository pattern con interfaces

Toda interacción con persistencia pasa por una interfaz de repositorio.
La lógica de negocio depende de la interfaz, no de la implementación.

```java
// Interfaz — define el contrato, no el cómo
public interface FacturaRepository {
    Optional<Factura> buscarPorId(UUID id);
    List<Factura> listarPorCliente(UUID clienteId, Pageable pageable);
    Factura guardar(Factura factura);
}

// Implementación — detalle de persistencia (JPA, JDBC, etc.)
@Repository
public class FacturaRepositoryJpa implements FacturaRepository {
    private final JpaFacturaRepository jpa;
    // ...
}
```

---

## Builder pattern para objetos complejos

Usar builder cuando un objeto tiene más de 4 parámetros opcionales de construcción.
Lombok `@Builder` es la implementación preferida.

```java
// MAL — constructor telescópico: frágil, no documenta qué es cada argumento
Factura f = new Factura(clienteId, fecha, items, descuento, null, "BORRADOR", true);

// BIEN — builder: cada parámetro es explícito y opcional
Factura factura = Factura.builder()
    .clienteId(clienteId)
    .fecha(LocalDate.now())
    .items(items)
    .descuento(BigDecimal.ZERO)
    .estatus(EstatusFactura.BORRADOR)
    .build();
```

---

## Strategy con interfaces funcionales

Para comportamiento intercambiable, preferir interfaces funcionales sobre
jerarquías de clases cuando la estrategia es simple.

```java
// Interfaz funcional como estrategia
@FunctionalInterface
public interface CalculadorDescuento {
    BigDecimal calcular(BigDecimal subtotal);
}

// Estrategias como lambdas o method references — sin clases adicionales
CalculadorDescuento sinDescuento = subtotal -> BigDecimal.ZERO;
CalculadorDescuento descuentoFijo = subtotal -> new BigDecimal("100.00");
CalculadorDescuento descuentoPorcentual = subtotal -> subtotal.multiply(new BigDecimal("0.10"));
```

---

## Stream API sobre loops imperativos

Preferir Stream API para transformaciones, filtros y agregaciones de colecciones.
Los loops imperativos son aceptables cuando el Stream resultaría más oscuro.

```java
// MAL — loop imperativo para transformación simple
List<String> nombres = new ArrayList<>();
for (Usuario u : usuarios) {
    if (u.isActivo()) {
        nombres.add(u.getNombre().toUpperCase());
    }
}

// BIEN — Stream declara la intención, no los pasos
List<String> nombres = usuarios.stream()
    .filter(Usuario::isActivo)
    .map(Usuario::getNombre)
    .map(String::toUpperCase)
    .toList();  // Java 16+: List inmutable directamente
```

---

## CompletableFuture para operaciones asíncronas

```java
// Composición de operaciones asíncronas sin bloquear threads
public CompletableFuture<FacturaEnviada> crearYEnviarFactura(FacturaRequest request) {
    return CompletableFuture
        .supplyAsync(() -> repositorio.guardar(request.toEntidad()), executor)
        .thenApplyAsync(factura -> generarPdf(factura), executor)
        .thenApplyAsync(pdf -> servicioEmail.enviar(pdf), executor)
        .exceptionally(ex -> {
            log.error("Error al crear factura", ex);
            throw new FacturaException("No se pudo crear la factura", ex);
        });
}
```

---

## Try-with-resources obligatorio

Todo recurso que implementa `AutoCloseable` DEBE abrirse en try-with-resources.
Nunca cerrar manualmente en `finally`.

```java
// MAL — cierre manual propenso a fugas de recursos
InputStream stream = new FileInputStream(archivo);
try {
    procesar(stream);
} finally {
    stream.close();  // ¿qué pasa si procesar() lanza excepción?
}

// BIEN — cierre garantizado por la JVM
try (var stream = new FileInputStream(archivo);
     var reader = new BufferedReader(new InputStreamReader(stream))) {
    procesar(reader);
}
```

---

## Jerarquía propia de excepciones

- Excepciones de negocio heredan de `RuntimeException` (unchecked).
- Checked exceptions solo para condiciones recuperables que el llamador DEBE manejar.
- Nunca capturar `Exception` o `Throwable` sin razón documentada.

```java
// Jerarquía de dominio
public class DominioException extends RuntimeException {
    public DominioException(String mensaje) { super(mensaje); }
    public DominioException(String mensaje, Throwable causa) { super(mensaje, causa); }
}

public class FacturaNoEncontradaException extends DominioException {
    public FacturaNoEncontradaException(UUID id) {
        super("Factura no encontrada: " + id);
    }
}

public class SaldoInsuficienteException extends DominioException { ... }
```

---

## Factory method para construcción compleja

Cuando la construcción de un objeto requiere lógica, usar factory method estático
en lugar de sobrecargar constructores.

```java
public class Factura {
    // Factory methods con nombres descriptivos
    public static Factura nueva(Cliente cliente, List<LineaFactura> lineas) {
        var factura = new Factura();
        factura.folio = generarFolio();
        factura.cliente = cliente;
        factura.lineas = List.copyOf(lineas);
        factura.calcularTotales();
        return factura;
    }

    public static Factura desde(FacturaDto dto) { ... }
}
```

---

## Checklist de patrones Java antes de abrir PR

- [ ] Sin @Autowired en campos — inyección solo por constructor
- [ ] Objetos con >4 parámetros usan Builder
- [ ] Interacción con BD a través de interfaces Repository
- [ ] Recursos AutoCloseable abiertos con try-with-resources
- [ ] Excepciones de negocio extienden RuntimeException con jerarquía propia
- [ ] Transformaciones de colecciones usan Stream API
- [ ] Sin loops for que podrían ser un stream de 3 líneas
