# 03 · Configuración y `.env`

Cada variable, qué controla, y **qué pasa si está mal**. Los defectos están elegidos para que
un `.env` incompleto deje el canal apagado, nunca a medias.

---

## 3.1 Los tres archivos de configuración

| Archivo | Gobierna | ¿Se toca al activar? |
|---|---|---|
| `config/sincronizacion.php` | **Canal de ida** — HMAC, allowlist, long-poll, lease | ❌ No |
| `config/replicacion.php` | **Canal inverso** + monitoreo tipificado | ✅ Sí |
| `config/reservacion.php` | Reglas de negocio (TTL, folio visible, umbrales) | ⚠️ Solo si cambia D1 |

> `sincronizacion.php` **no se toca**. Gobierna el canal que está en producción, y el canal
> inverso reutiliza su secreto HMAC y su allowlist sin modificarlos.

---

## 3.2 🔑 Los dos interruptores del canal inverso

```php
// config/replicacion.php
'inversa' => [
    'habilitada' => filter_var(env('REPLICACION_INVERSA_HABILITADA', false), FILTER_VALIDATE_BOOL),
    'punteros'   => filter_var(env('REPLICACION_INVERSA_PUNTEROS',   true),  FILTER_VALIDATE_BOOL),
],
```

**Son independientes, y esa independencia es el punto.**

| Interruptor | Gobierna | Hoy | Requiere el esquema |
|---|---|---|---|
| `habilitada` | **Recepción**: rutas `/entrante` y `/estado` | `false` | Sí |
| `punteros` | **Emparejamiento**: el veredicto escribe el `mapeo` | `true` | Sí |

### `habilitada` — la recepción

En `false`, las rutas **no se registran** → `404`. Registrarlas apagadas devolvería `500`, y
`500` abre el cortacircuitos de Reservaciones V2; `404` le dice "este canal no existe" y descarta
limpiamente.

### `punteros` — el emparejamiento

En `true`, `finalizarConfirmacion()` escribe el `mapeo` que **Reservaciones V2 ya envía hoy**.

No recibe nada del privado ni cambia lo que el socio ve: solo rellena
`idReservacionHabitacionPrivado` y sus hermanas en el mismo commit del sello. Lo que consigue es
que, **el día que se active la recepción, cada reservación confirmada desde hoy ya venga
emparejada**.

Sin esto, al activar, toda reservación anterior caería en la adopción por respaldo: se
reemplaza el detalle completo, **se regeneran los ids que el socio ya tiene en sus PDFs y
notificaciones**, y se levanta una `R12` por cada una. Rellenarlos desde ya convierte ese
sobresalto en un no-evento.

> ⚠️ **`punteros` DEBE ponerse en `false` si se retira el esquema**
> ([08](08-DESACTIVACION-Y-REVERSA.md)). `MapeoReplicaService` se invoca desde el commit que
> **asienta la venta**: con las columnas fuera, ese commit revienta en **cada venta
> confirmada** y el socio queda cobrado y atascado en `Confirmando`.
>
> **`habilitada` no se pone en `true` sin seguir** el orden de
> [07 · Activación paso a paso](07-ACTIVACION-PASO-A-PASO.md).

---

## 3.3 Monitoreo (activo hoy, sirve a los dos canales)

```php
// config/replicacion.php
'monitoreo' => [
    'habilitado' => filter_var(env('REPLICACION_MONITOREO_HABILITADO', false), FILTER_VALIDATE_BOOL),
    'correos'    => /* coma-separados */,
    'throttle_minutos' => [
        'defecto' => (int) env('REPLICACION_MONITOREO_THROTTLE', 60),
        'R1'      => 10,   // dinero cobrado sin reservacion: una hora es inaceptable
        'R7'      => 0,    // dispara en la TRANSICION, evento unico por marca
        'R10'     => 0,    // nunca se silencia: cada intento de pisar al maestro debe verse
    ],
    'bitacora_dias' => (int) env('REPLICACION_BITACORA_DIAS', 180),
],
```

```dotenv
REPLICACION_MONITOREO_HABILITADO=true
REPLICACION_MONITOREO_CORREOS=soporte@tudominio.mx,desarrollo@tudominio.mx
REPLICACION_MONITOREO_THROTTLE=60
REPLICACION_BITACORA_DIAS=180
```

| Variable | Defecto | Qué controla |
|---|---|---|
| `REPLICACION_MONITOREO_HABILITADO` | `false` | **Solo el correo.** La bitácora se escribe siempre |
| `REPLICACION_MONITOREO_CORREOS` | vacío | Destinatarios. Vacío → no se envía y se registra el aviso en el log |
| `REPLICACION_MONITOREO_THROTTLE` | `60` | Ventana por defecto, en minutos |
| `REPLICACION_BITACORA_DIAS` | `180` | Retención. `0` desactiva la purga |

**`habilitado` gobierna únicamente el envío.** La bitácora no depende de él: es lo que separa
"no nos avisaron" de "no quedó rastro".

**El throttle `0` es seguro solo donde se usa.** `R7` dispara en la *transición* a
`Descartado` —evento único por marca— y `R10` no debe ocurrir jamás en régimen normal. Ponerle
`0` a `R6` sería un error: ése sí se repite cada ciclo, y con un cron de un minuto serían
1,440 correos al día.

> ⚠️ **`APP_NAME` sale en el encabezado de cada alerta.** Si está en `Laravel`, los correos
> dicen "Servidor: Laravel (local)" y no se distinguen de los de otros sistemas que llegan al
> mismo buzón. Ponlo en algo como `CL032 Reservaciones en Línea`.

---

## 3.4 Detección de avisos degradados (`R14`)

```php
'avisos' => [
    'ventana_minutos' => (int) env('REPLICACION_AVISOS_VENTANA', 15),
    'umbral_fallos'   => (int) env('REPLICACION_AVISOS_UMBRAL', 5),
],
```

Los avisos al socio (`avisarSinCupo`, `avisarMembresia`, `avisarReplicacionResuelta`) son
best-effort **y deben seguir siéndolo**: un canal de notificaciones caído no puede tumbar un
veredicto. Lo que faltaba era ver el fallo **sostenido**. Se cuenta por ventana y se alerta
**una vez** al cruzar el umbral, no en cada fallo — alertar por fallo convertiría una caída del
SMTP en un correo por socio afectado, justo cuando el correo no funciona.

---

## 3.5 Folio visible (decisión D1)

```php
// config/reservacion.php
'folio_visible' => env('RESERVACION_FOLIO_VISIBLE', 'portal'),
```

| Valor | Muestra |
|---|---|
| `portal` | `reservacionNo` — el del portal (año + 5 dígitos, p. ej. `202600001`). **Valor actual** |
| `privado` | `reservacionNoPrivado` — el del club (p. ej. `48213`), que llega en el veredicto |

**Los dos folios se guardan siempre**, se muestren o no. Cambiar de uno a otro es una variable
y un `config:clear`, sin desplegar código. Con `privado`, si el veredicto se perdió y no hay
folio del club, se muestra el del portal: un folio distinto al esperado es un reclamo; ninguno
es una llamada a soporte.

---

## 3.6 Canal de ida — referencia (no se toca)

`config/sincronizacion.php`, con las variables que ya están en producción:

```dotenv
SINCRONIZACION_HMAC_SECRET=<secreto compartido con Reservaciones V2>
SINCRONIZACION_HMAC_SOURCE=villastur
SINCRONIZACION_IPS_PERMITIDAS=              # sin efecto mientras la allowlist esté apagada
SINCRONIZACION_IP_ALLOWLIST_HABILITADA=false
SINCRONIZACION_VENTANA=60
SINCRONIZACION_NONCE_TTL=60
SINCRONIZACION_WAIT=25
SINCRONIZACION_LIMITE=50
SINCRONIZACION_LEASE=60
SINCRONIZACION_RATE_LIMIT=120,1
```

**El canal inverso reutiliza `SINCRONIZACION_HMAC_SECRET`, `SINCRONIZACION_IPS_PERMITIDAS`,
`SINCRONIZACION_VENTANA` y `SINCRONIZACION_NONCE_TTL`.** No hay secreto nuevo que repartir: el
mismo middleware protege las cuatro rutas, y lo único que cambia es **qué se firma** (ver
[04](04-SEGURIDAD-Y-FIRMA.md)).

> **`SINCRONIZACION_IPS_PERMITIDAS` ya no protege nada en el servidor público.** La allowlist
> se especificó para la IP ZeroTier de Reservaciones V2, pero **el privado no es miembro de esa
> red** — solo el Intermedio lo es ([ARQUITECTURA_SERVIDORES.md](../ARQUITECTURA_SERVIDORES.md)
> §2), y ése es el aislamiento que protege R1. Reservaciones V2 sale por Internet y llega con la
> IP pública **dinámica** de su ISP, así que la lista no puede casar nunca.
>
> Por eso la capa está apagada con `SINCRONIZACION_IP_ALLOWLIST_HABILITADA=false` **en el
> público**. En local conviene dejarla en `true`: ahí el origen es `127.0.0.1` y la capa se
> ejercita. El **defecto del código es `true`**, de modo que un despliegue que no declare la
> variable queda protegido; apagarla exige escribirlo.
>
> Reactivarla es cambiar esa variable —no el código— el día que el privado tenga **IP fija** o
> se resuelva con **mTLS**. La lista acepta IP suelta o **CIDR** (`192.168.191.0/24`).

---

## 3.7 ⚠️ La caché no es un detalle

```dotenv
CACHE_STORE=file     # valor actual
```

De la caché dependen **dos cosas críticas**:

1. **El anti-replay por nonce** del canal de escritura. Sin caché, un atacante con una firma
   válida capturada podría reusarla dentro de la ventana de 60 s.
2. **El throttle de las alertas.** Si deja de funcionar, o llegan todos los correos o no llega
   ninguno, según el modo de fallo.

Con `CACHE_STORE=file`, el candado vive en `storage/framework/cache`. **Si ese directorio no
es escribible, el throttle deja de filtrar en silencio.** La mitigación está por diseño —la
bitácora no depende de la caché— y `MonitorReservaciones` verifica una vez por proceso que la
caché responda; si no, lo registra como `R3` con firma global.

> Si algún día se cambia a `CACHE_STORE=database`, hay que crear la tabla `cache`: hoy su
> migración está pendiente. Ver `database/sql/registrar_migraciones_aplicadas.sql`.

---

## 3.8 `.env` completo para activar el canal inverso

```dotenv
# ─── Canal inverso (Reservaciones V2 → portal) ────────────────────────────────────────
REPLICACION_INVERSA_HABILITADA=true      # enciende la RECEPCION
REPLICACION_INVERSA_PUNTEROS=true        # ya estaba en true (es el defecto)

# ─── Monitoreo (ya activo) ─────────────────────────────────────────────────
REPLICACION_MONITOREO_HABILITADO=true
REPLICACION_MONITOREO_CORREOS=soporte@tudominio.mx
REPLICACION_MONITOREO_THROTTLE=60
REPLICACION_BITACORA_DIAS=180
REPLICACION_AVISOS_VENTANA=15
REPLICACION_AVISOS_UMBRAL=5

# ─── Identidad del sistema en los correos ──────────────────────────────────
APP_NAME="CL032 Reservaciones en Línea"

# ─── Folio visible (D1) ────────────────────────────────────────────────────
RESERVACION_FOLIO_VISIBLE=portal
```

Después de cualquier cambio:

```bash
php artisan config:clear
```

> En este proyecto **no** se usa `config:cache`. Si algún día se usara, `config:clear` no
> basta: hay que volver a cachear.

---

**Anterior:** [02 · Esquema](02-ESQUEMA-BASE-DE-DATOS.md) ·
**Siguiente:** [04 · Seguridad y firma](04-SEGURIDAD-Y-FIRMA.md)
