# NOTA DE COORDINACIÓN — Documento de identificación · Portal `CL032BACK` ↔ Tlalpan `CL001SICAMV2`

> **Para el equipo que trabaja el lado de Tlalpan.**
> Qué acaba de cambiar en el Portal, qué puede dar por garantizado y qué **no puede** cambiarse de
> un solo lado.
>
> Fecha de la entrega: **12-ago-2026** · Fase 11, lado Portal, fases 1 y 2.

---

## 1. Qué cambió en el Portal

**Nada de lo que ya estaba.** Los cuatro endpoints del canal, la convención de firma, el 404
uniforme y la ruta de almacenamiento siguen **idénticos**. La entrega es aditiva:

| Cambio | Efecto en Tlalpan |
|---|---|
| `GET /invitados/transferencia/pendientes` ahora incluye **`documentoBytes`** y **`documentoSha256`** por invitado | Aditivo. Un consumidor que los ignore sigue funcionando |
| Ruta nueva **`GET /invitados/transferencia/salud`** → `200` con cuerpo vacío | Opcional. Solo si se quiere abaratar el *half-open* del circuit breaker |

**No cambió:** `documento` (misma referencia, mismo formato), el endpoint que sirve el archivo, el
404 uniforme, `SeguridadInvitados`, `HmacSigner`, la subida del socio ni la ruta
`solicitudinvitado/{idSolicitud}/{idInvitado}.{ext}`.

---

## 2. El contrato de los tres campos

```jsonc
{
  "idInvitadoPortal": 31,
  // ...
  "documento":       "/invitados/transferencia/documento/31",  // string | null
  "documentoBytes":  1204873,                                   // int    | null
  "documentoSha256": "ac6be93609e414903d30c6d13f28e49499880b99f2f676e728c8caeabccc4ce0"  // string(64) | null
}
```

| Campo | Tipo | Significado |
|---|---|---|
| `documento` | `string\|null` | **Referencia**, no el archivo. Path relativo listo para firmar y pedir |
| `documentoBytes` | `int\|null` | Tamaño exacto en bytes del archivo que ese `documento` va a entregar |
| `documentoSha256` | `string(64)\|null` | SHA-256 en hexadecimal minúsculas del mismo archivo |

### Garantías que el Portal sí da

1. **Coherencia entre los tres.** Si `documento` es `null`, los otros dos son `null`. Si
   `documento` trae valor, `documentoBytes` trae valor.
2. **`documentoBytes` y `documentoSha256` describen el archivo que el endpoint va a entregar**,
   incluso en el caso raro de que convivan dos extensiones para el mismo `idInvitado`: ambos lados
   resuelven con **el primer archivo del listado**, el mismo que sirve la descarga.
3. **El hash cambia si el archivo cambia.** El memo del Portal está indexado por fecha de
   modificación y tamaño: un reemplazo de la foto produce un hash nuevo en el ciclo siguiente.
4. **Formatos y tope de subida.** El Portal valida al capturar con
   `file|mimes:jpeg,jpg,png,webp,pdf|max:12288`.

   **Formatos: imagen (JPG, PNG, WEBP) y PDF. ✅ PDF admitido en ambos lados desde el
   13-ago-2026.** Lo pidió el negocio: muchos socios tienen la identificación del invitado
   escaneada en PDF y hasta entonces tenían que fotografiar la pantalla para poder subirla.

   **Tlalpan se amplió PRIMERO** (`'application/pdf' => 'pdf'` en su `documentos.mimes`), y solo
   después el Portal cambió su regla. Ese orden no fue casual y **es el que hay que repetir si
   algún día se agrega otro formato**: el rechazo por MIME de `AlmacenDocumentos::guardar()` es
   definitivo por diseño —deja `solicitudInvitadoDocumentoIntentos` en el máximo y no reintenta,
   correctamente, porque volver a pedir el archivo daría el mismo—, así que un Portal más laxo
   que Tlalpan produce documentos que se suben bien, se ven bien aquí y **no llegan nunca** al
   operador. Es la misma regla que rige el tope de tamaño (punto 3 de la sección siguiente):
   **el receptor se amplía primero, el emisor después.**

   > Nota sobre `image` → `file`: la regla `image` de Laravel se apoya en `getimagesize()`, que
   > no reconoce PDF. Se sustituyó por `file`. **No se perdió protección:** `mimes` nunca miró la
   > extensión del nombre —adivina la extensión desde el MIME detectado del contenido—, así que
   > un ejecutable renombrado a `.pdf` se sigue rechazando. Verificado con prueba.

   **Tope — cambió: antes eran 8 MB.** Se subió para cubrir las cámaras de 48 MP en modo
   "compatible" (iPhone Pro y gama alta Android), que producen JPEG de 8–12 MB y quedaban
   rechazados con una foto perfectamente legítima. ✅ **Tlalpan ya ajustó
   `INVITADOS_DOCUMENTOS_MAX_BYTES` a `12582912` — los dos lados coinciden.** Ver el punto 3
   de la sección siguiente.

### Lo que el Portal NO garantiza — y hay que programar tolerándolo

1. **`documentoSha256` puede llegar en `null` con `documentoBytes` presente.** Ocurre si el hash
   se apaga en el Portal (`INVITADOS_DOCUMENTO_HASH=false`, la salida de emergencia por costo) o
   si el archivo no se pudo leer al armar el lote. **No es un error: descarguen sin verificar.**
   Tratarlo como error dejaría solicitudes atoradas por una decisión de configuración del otro
   lado.
2. **El metadato es una foto del momento del lote.** Si el socio reemplaza la identificación entre
   el lote y la descarga, el hash de lo descargado **no** coincidirá. Eso es exactamente lo que el
   campo sirve para detectar: la respuesta correcta es **re-descargar**, no marcar error
   definitivo.
3. **El tope del Portal no es el tope de Tlalpan, aunque hoy coincidan.** 12 MB es lo que el Portal
   acepta subir; el techo propio de Tlalpan se impone verificando `Content-Length` **y** cortando
   el stream. No confiar en el límite del otro lado
   ([`PROPUESTA_DOCUMENTO_IDENTIFICACION_INVITADOS.md` §6](PROPUESTA_DOCUMENTO_IDENTIFICACION_INVITADOS.md)).

   **Estado: alineado.** Ambos lados están en **12 582 912 bytes** exactos
   (`max:12288` × 1024 en el Portal ↔ `INVITADOS_DOCUMENTOS_MAX_BYTES` en Tlalpan).

   **⚠️ Regla de orden para el futuro.** Si alguna vez vuelve a moverse el tope:
   **primero Tlalpan, después el Portal.** El motivo es que el rechazo de
   `AlmacenDocumentos::guardar()` es **definitivo por diseño** —pone
   `solicitudInvitadoDocumentoIntentos` en el máximo y no reintenta, correctamente, porque volver a
   pedir el archivo daría exactamente el mismo—. Un Portal más laxo que Tlalpan produce documentos
   que se suben bien, se ven bien en el Portal y **nunca llegan al operador**, dejando como único
   rastro un renglón en `BitacoraTransferencia`. Al revés (Tlalpan más laxo) no rompe nada.

   Se sigue enviando `documentoBytes` en el lote: permite descartar **antes** de gastar la descarga
   si los topes llegaran a desalinearse.

### En camino del lado del Portal — no requiere acción de su parte

**Optimización de la imagen antes de almacenarla** (compresión en el navegador del socio y, más
adelante, re-codificación en el servidor: 1600 px, calidad 0.85, sin EXIF). Bajaría lo almacenado y
lo transferido a **~400 KB por documento**.

No cambia el contrato ni ningún nombre de campo. Se los anticipamos por dos razones que sí los
tocan:

- **Reduce mucho la presión sobre el `timeout` de 60 s y sobre el circuit breaker.** Con documentos
  de cientos de KB en vez de varios MB, una descarga lenta deja de ser un escenario realista.
- **Elimina los metadatos EXIF —incluida la geolocalización—** de fotografías de identificaciones
  de terceros. Hasta que esté, los archivos que reciben **sí** conservan esos metadatos; tenerlo
  presente para su propia política de conservación y acceso.

Les avisaremos cuando entre en vigor. Verán bajar el tamaño de las descargas; nada más cambia.

---

## 3. La sonda de salud — cómo se consume

```
GET /invitados/transferencia/salud   →   200, cuerpo vacío
```

**Se sondea con `GET`, no con `HEAD`.** La convención de firma del canal ramifica por el método:
**GET firma el path**, cualquier otro verbo firma el body. Un `HEAD` caería en la rama del body y
obligaría a firmar la cadena vacía —otro contrato, más débil, porque no ata la firma al path— y a
tocar `SeguridadInvitados`, que está verificado en ambos lados. Un `GET` a la sonda ya es
baratísimo, así que no había nada que ganar.

Se firma **exactamente igual** que `/pendientes`: `sha256=HMAC(secreto, "{ts}.{nonce}{.}/invitados/transferencia/salud")`,
con los mismos headers `X-Signature`, `X-Timestamp`, `X-Nonce`.

**Para qué sirve y para qué no:**

- ✅ **Sí:** cuando el circuit breaker está abierto y toca reintentar (*half-open*), probar con
  esto en vez de arrancar una descarga de 12 MB para tantear.
- ❌ **No:** consultarla antes de cada descarga. Sería redundante —la descarga misma responde esa
  pregunta— y una carrera: hay conexión en T y no en T+1.
- ❌ **No es obligatoria.** Si no se usa, el breaker reintenta con una descarga real y el diseño
  funciona igual.

Va **dentro del mismo grupo firmado** que el resto del canal, a propósito: así el reintento
verifica de paso lo que de verdad importa —que la IP sigue en la allowlist y que el secreto y los
relojes siguen coincidiendo—. Un `403`/`401`/`503` en la sonda es un **diagnóstico**, no un falso
"está caído".

---

## 4. Reglas de cambio — esto es lo importante

> **`documento`, `documentoBytes` y `documentoSha256` son contrato entre dos sistemas en dos redes
> distintas. Cambiar el nombre, el tipo o la semántica de cualquiera de los tres ROMPE el otro
> lado, y el síntoma llega tarde y desde el usuario final.**

1. **Ningún cambio de forma se despliega sin anunciarse antes.** Se avisa al otro equipo, se acuerda
   el orden de despliegue y solo entonces se sube.
2. **Los cambios aditivos** (campos nuevos) son seguros **siempre que el consumidor lea los
   desconocidos con default `null`** y no valide la forma con lista cerrada.
3. **Los cambios que quitan o renombran** exigen despliegue coordinado, con el consumidor tolerando
   ambas formas durante la transición.
4. **Apagar el hash** (`INVITADOS_DOCUMENTO_HASH=false`) **se avisa**, aunque técnicamente no
   rompa nada: elimina la detección de reemplazo de la foto, y del lado de Tlalpan eso deja de
   funcionar sin ningún error visible.
5. **La ruta de almacenamiento** `solicitudinvitado/{idSolicitud}/{idInvitado}.{ext}` y el **path
   de los endpoints** son parte del contrato criptográfico: el path exacto es lo que se firma en
   los GET. Cambiar uno sin el otro produce un **401 mudo**, que es de los que cuestan un día de
   diagnóstico.

**Contacto para cualquiera de estos cambios:** el equipo del Portal (`CL032BACK`), antes del
despliegue, no después.

---

## 5. LA PURGA DEL PORTAL YA ESTÁ EN VIGOR — léase completo

> **Esta sección se actualiza el 20-ago-2026 en cumplimiento del compromiso que cerraba su
> versión anterior: avisar ANTES de que ocurra el primer borrado.** La decisión D6 se autorizó y
> el comando está implementado.

### 5.1 Qué borra el Portal, y cuándo

**Días de retención acordados: CERO.** El Portal conserva la solicitud durante todo el día de su
fecha de salida y la elimina **a las 00:05 del día siguiente**, junto con sus invitados, su
reservación y **el documento de identificación en disco**.

```
solicitudFechaSalida >= hoy   →  el Portal la conserva
solicitudFechaSalida <  hoy   →  el Portal la elimina, sin vuelta atrás
```

**Ese corte es exactamente el que Tlalpan ya aplica** en `TransferenciaSolicitudesPortal`,
`ReplicadorSolicitud` y `ReconciliarReplicacion` (los tres en `solicitudFechaSalida >= hoy`), y
no es casualidad: se eligió así justamente para que **Tlalpan no tenga que cambiar ni una línea**.

- El día en que el Portal borra una solicitud, Tlalpan **ya había dejado de enviarle** su estado.
- Por lo tanto **no habrá** avisos de `veredicto sobre una solicitud inexistente` atribuibles a
  la purga. Si aparecen, es un síntoma real que hay que investigar, no ruido esperado.
- **Ninguna réplica se pierde**: `ReplicadorSolicitud` filtra por la misma fecha en origen.

**Fecha de entrada en vigor: se comunicará al equipo de Tlalpan antes de la primera corrida real
en producción.** En pre-producción el comando ya se ejecutó en modo `--dry-run`.

### 5.2 Lo que esto significa para Tlalpan: la descarga es AHORA OBLIGATORIA

Se resolvió la columna izquierda de la tabla que estaba aquí:

| Antes (D6 sin autorizar) | **Ahora (D6 autorizada)** |
|---|---|
| La descarga del documento era una **optimización** | **La descarga es OBLIGATORIA.** Tlalpan es el archivo de registro |
| El Portal conservaba indefinidamente | El Portal conserva, como mucho, hasta el día de la salida |

> ⚠️ **Un documento no descargado a tiempo se pierde definitivamente.** La ventana de descarga
> termina el día de la fecha de salida de la solicitud. Después, `GET /invitados/transferencia/
> documento/{idInvitado}` responderá "sin documento" y no habrá de dónde recuperarlo.

**Dos consecuencias concretas para el lado de Tlalpan:**

1. **Si el circuit breaker del canal de documentos estuvo abierto varios días**, las solicitudes
   que vencieron durante ese lapso ya no son recuperables. Conviene que `MonitorInvitados`
   escale la alerta con más urgencia de la que tenía cuando el Portal era el respaldo.
2. **Un fallo de descarga cerca de la fecha de salida ya no admite "se reintenta mañana".**

### 5.3 Lo que sigue siendo decisión del negocio de Tlalpan

**Tlalpan necesita su propia política de conservación**, y ahora con más razón: pasa a ser el
único lugar donde vive la identificación. Es decisión del negocio, no del código. Mientras no
exista, Tlalpan conserva indefinidamente y crece: con 12 MB máximos por invitado, conviene
dimensionarlo.

### 5.4 Interruptor de emergencia

Si el equipo de Tlalpan detecta que necesita más tiempo de descarga, la purga del Portal se
detiene o se retrasa **sin desplegar**, con una línea en el `.env` del Portal:

```dotenv
PURGA_ACTIVA=false        # detiene la purga por completo
PURGA_DIAS_GRACIA=7       # o la retrasa una semana después de la fecha de salida
```

Basta con avisarle al equipo del Portal. **Subir los días de gracia siempre es seguro**; lo que
no se puede es adelantar el corte al mismo día de la salida, porque ahí sí se desalinearía de los
filtros de Tlalpan y llenaría el log de advertencias.

---

## 6. Referencias

- Diseño completo: [`PROPUESTA_DOCUMENTO_IDENTIFICACION_INVITADOS.md`](PROPUESTA_DOCUMENTO_IDENTIFICACION_INVITADOS.md)
- Canal de transferencia: [`../PROPUESTA_TRANSFERENCIA_SOLICITUDES_INVITADOS.md`](../PROPUESTA_TRANSFERENCIA_SOLICITUDES_INVITADOS.md) §5.4
- Despliegue del lado Portal: [`CONFIGURACION_DOCUMENTOS_PORTAL.md`](CONFIGURACION_DOCUMENTOS_PORTAL.md)
- Reglas de red: [`../ARQUITECTURA_SERVIDORES.md`](../ARQUITECTURA_SERVIDORES.md) §4
