# Regla: Estilo de Código — PHP

Aplica a todo código PHP del proyecto. PHP moderno (8.1+) es un lenguaje con
tipado estático opcional pero poderoso. Estas reglas maximizan la seguridad de
tipos, la legibilidad y la mantenibilidad en proyectos Laravel y PHP puro.

---

## PSR-12 como estándar de formato

- PSR-12 es el estándar de codificación obligatorio. Toda discrepancia de formato
  se resuelve con el formateador automático, no con debate de equipo.
- Usar **Laravel Pint** en proyectos Laravel, o **PHP-CS-Fixer** en proyectos PHP puro.
- Ejecutar antes de cada commit:
  ```bash
  ./vendor/bin/pint --test    # verifica sin modificar
  ./vendor/bin/pint           # aplica correcciones
  ```
- CI debe fallar si `pint --test` produce diferencias.
- El archivo `pint.json` (o `.php-cs-fixer.php`) se versiona en el repositorio.

---

## strict_types en todos los archivos

- Todo archivo PHP DEBE comenzar con la declaración de tipos estrictos:
  ```php
  <?php

  declare(strict_types=1);
  ```
- Sin `declare(strict_types=1)`: PHP convierte tipos silenciosamente y produce bugs difíciles de rastrear.
- No hay excepciones. Ni en scripts de migración, ni en helpers, ni en tests.

---

## Type hints obligatorios

- Todo parámetro, retorno y propiedad de clase lleva tipo declarado.

**MAL — sin tipos:**
```php
function calcularTotal($items, $descuento) {
    return array_sum(array_column($items, 'precio')) * (1 - $descuento);
}
```

**BIEN — con tipos:**
```php
function calcularTotal(array $items, float $descuento): float
{
    return array_sum(array_column($items, 'precio')) * (1 - $descuento);
}
```

- Tipos de retorno `void` cuando la función no retorna valor.
- `never` cuando la función siempre lanza excepción o termina el proceso.
- `mixed` solo cuando es genuinamente imposible determinar el tipo.
  Si aparece `mixed`: señal de deuda técnica, documentar con `// TODO:`.

---

## Readonly properties (PHP 8.2+)

- Propiedades que no cambian después de la construcción se declaran `readonly`:

```php
// MAL — propiedad mutable que nunca debería mutar
class Factura
{
    public string $folio;

    public function __construct(string $folio)
    {
        $this->folio = $folio;
    }
}

// BIEN — inmutabilidad declarada explícitamente
class Factura
{
    public function __construct(
        public readonly string $folio,
        public readonly \DateTimeImmutable $fechaEmision,
    ) {}
}
```

---

## Enums nativos (PHP 8.1+)

- Reemplazar constantes de clase y strings mágicos con enums nativos:

```php
// MAL — constantes como pseudo-enum
class EstatusFactura
{
    const BORRADOR = 'borrador';
    const EMITIDA = 'emitida';
    const CANCELADA = 'cancelada';
}

// BIEN — enum nativo con métodos
enum EstatusFactura: string
{
    case Borrador = 'borrador';
    case Emitida = 'emitida';
    case Cancelada = 'cancelada';

    public function esTerminal(): bool
    {
        return $this === self::Cancelada;
    }
}
```

---

## Named arguments para funciones con muchos parámetros

```php
// MAL — posicional, ilegible
$reporte = generarReporte(true, false, null, 'pdf', 100, 1);

// BIEN — named arguments, autodocumentado
$reporte = generarReporte(
    incluirImpuestos: true,
    incluirDescuentos: false,
    filtroEmpresa: null,
    formato: 'pdf',
    limitePaginas: 100,
    pagina: 1,
);
```

---

## Match expression sobre switch

```php
// MAL — switch verboso
switch ($estatus) {
    case 'activo':
        $etiqueta = 'Activo';
        break;
    case 'inactivo':
        $etiqueta = 'Inactivo';
        break;
    default:
        throw new \InvalidArgumentException("Estatus desconocido: $estatus");
}

// BIEN — match con exhaustividad
$etiqueta = match ($estatus) {
    'activo' => 'Activo',
    'inactivo' => 'Inactivo',
    default => throw new \InvalidArgumentException("Estatus desconocido: $estatus"),
};
```

---

## Null safe operator en cadenas

```php
// MAL — verificaciones manuales de null en cadena
$ciudad = null;
if ($factura !== null && $factura->cliente !== null && $factura->cliente->direccion !== null) {
    $ciudad = $factura->cliente->direccion->ciudad;
}

// BIEN — null safe operator
$ciudad = $factura?->cliente?->direccion?->ciudad;
```

---

## Arrow functions para closures simples

```php
// MAL — closure verboso para transformación simple
$totales = array_map(function ($factura) {
    return $factura->total;
}, $facturas);

// BIEN — arrow function concisa
$totales = array_map(fn($factura) => $factura->total, $facturas);
```

---

## Convenciones de nombres

| Elemento | Convención | Ejemplo |
|----------|-----------|---------|
| Clases, interfaces, traits, enums | PascalCase | `FacturaService`, `Pagable` |
| Métodos y funciones | camelCase | `calcularTotal()`, `obtenerCliente()` |
| Variables y parámetros | camelCase | `$totalFactura`, `$clienteActivo` |
| Constantes de clase | SCREAMING_SNAKE_CASE | `MAX_INTENTOS`, `TASA_IVA` |
| Propiedades de modelo | camelCase en PHP, snake_case en BD | `$fechaEmision` ↔ `fecha_emision` |

- Booleanos: prefijo `es`, `tiene`, `puede`, `esta`:
  `$esActivo`, `$tieneSaldo`, `$puedeEditar`.
- Colecciones: plural del elemento: `$facturas`, `$usuariosActivos`.

---

## Longitud máxima de método: 40 líneas

- Un método que supera 40 líneas hace más de una cosa. Extraer métodos privados
  con nombres descriptivos.
- Complejidad ciclomática máxima: 5. Más de 5 ramas: refactorizar.
- Parámetros de método: máximo 4. Si se necesitan más, usar un DTO o array tipado.

---

## Checklist de estilo antes de hacer commit

- [ ] `declare(strict_types=1)` en todos los archivos PHP nuevos
- [ ] `./vendor/bin/pint --test` pasa sin diferencias
- [ ] Todo parámetro, retorno y propiedad tiene tipo declarado
- [ ] Sin `mixed` sin comentario justificativo
- [ ] Enums nativos en lugar de constantes de clase para dominios cerrados
- [ ] `match` en lugar de `switch` donde aplica
- [ ] Sin `null` checks manuales en cadena cuando `?->` funciona
- [ ] Métodos <= 40 líneas
- [ ] Sin abreviaciones en nombres de variables ni métodos
