# PROMPT — Documento de identificación del invitado · lado PORTAL (`CL032BACK`)

> **Ejecutar en:** `D:\FOURSIDES\CL001ASTU\PRE-PRODUCCION\VILLASTUR\CL032RESERVACIONESENLINEA\CL032BACK`
> **Contrato de referencia:** `PROPUESTA_DOCUMENTO_IDENTIFICACION_INVITADOS.md` (cópialo desde `CL001SICAMV2`)
> **Prompt hermano:** `PROMPT_IMPLEMENTACION_DOCUMENTOS_TLALPAN.md` (se ejecuta en `CL001SICAMV2`)

Actúa como desarrollador senior de este proyecto. Vas a completar el **lado Portal** de la fase 11 del canal de invitados: que Tlalpan pueda descargar de forma eficiente y verificable la identificación que sube el socio.

---

## Lee esto antes de escribir una línea

**Tu trabajo aquí es pequeño a propósito.** El endpoint que sirve el documento **ya existe, ya está firmado y ya funciona**. No lo reescribas, no lo "mejores" y no lo dupliques.

Verifica tú mismo el estado antes de tocar nada:

| Pieza | Archivo | Estado |
|---|---|:---:|
| `GET /invitados/transferencia/documento/{idInvitado}` | `app/Http/Controllers/Invitados/TransferenciaController.php:209` | ✅ Hecho |
| 404 **uniforme** (no distingue "no existe" de "sin foto") | mismo, `sinDocumento()` | ✅ Hecho |
| HMAC + allowlist de IP + nonce + ventana | `app/Http/Middleware/SeguridadInvitados.php` | ✅ Hecho |
| Convención: **GET → path**, POST → body | `SeguridadInvitados:93` | ✅ Hecho |
| La referencia viaja en el lote | `app/Http/Resources/Invitados/Transferencia/PendienteResource.php:78` | ✅ Hecho |
| Almacenamiento privado `solicitudinvitado/{idSolicitud}/{idInvitado}.{ext}` | `app/Http/Controllers/Invitados/InvitadoController.php:109` | ✅ Hecho |
| Validación de subida `file\|mimes:jpeg,jpg,png,webp,pdf\|max:12288` | `app/Http/Requests/Invitados/Invitado/GuardarFormRequest.php:63` | ✅ Hecho · PDF desde el 13-ago-2026 |

**Ese 404 uniforme es deliberado**: distinguir los motivos convertiría el endpoint en un oráculo para enumerar ids. Está documentado en el propio controlador. **No lo cambies.**

## Regla de red que no se negocia

**El Portal NUNCA inicia una conexión hacia Tlalpan.** Tlalpan siempre es quien llama. Aquí solo se *exponen* endpoints y se responde. No agregues clientes HTTP, ni webhooks, ni notificaciones salientes hacia la red privada.

---

## Lo que hay que construir

### 1. `InvitadoResource` — dos campos nuevos (recomendado)

En `app/Http/Resources/Invitados/Transferencia/InvitadoResource.php`, junto a `documento`:

```php
'documentoBytes'  => $this->resource['documentoBytes'],   // int|null
'documentoSha256' => $this->resource['documentoSha256'],  // string|null
```

Los arma `PendienteResource`, que **ya lista el directorio una sola vez por solicitud** (`documentosDe()`). Extiende ese mismo listado para devolver también tamaño y hash, en lugar de solo `true`.

**Por qué sirven** — sin esto Tlalpan descarga a ciegas:

| Campo | Para qué |
|---|---|
| `documentoBytes` | Tlalpan **rechaza antes de descargar** lo que excede su tope. Es un `stat`, barato |
| `documentoSha256` | Verificar integridad de lo recibido y **detectar reemplazo** si el socio cambia la foto |

> ⚠️ **Cuida el costo del hash.** `Storage::size()` es un `stat`; `hash_file()` lee el archivo completo. Con pocas solicitudes por ciclo es irrelevante, pero **mide antes de darlo por bueno**: si el lote crece, calcula el hash una sola vez y **cachéalo** (por ejemplo en un `.sha256` junto al archivo, o en columna). Si el costo resulta alto y no lo puedes cachear, entrega solo `documentoBytes` y deja `documentoSha256` en `null`: Tlalpan debe tolerarlo.

`documento`, `documentoBytes` y `documentoSha256` van los tres en `null` cuando no hay archivo. Mantén esa coherencia.

### 2. `HEAD /invitados/transferencia/salud` (opcional)

Ruta ligera, dentro del **mismo grupo con `SeguridadInvitados`**, que responda 200 sin cuerpo.

**Único propósito:** cuando el *circuit breaker* de Tlalpan está abierto y toca reintentar, probar con esto es más barato que arrancar una descarga de 12 MB para tantear.

**No es un "¿hay conexión?" que Tlalpan consulte antes de cada descarga** — eso sería redundante y una carrera. Si no lo implementas, Tlalpan reintenta con una descarga real y el diseño funciona igual. Impleméntalo solo si no añade complejidad.

### 3. Comando de purga — **CONDICIONADO A AUTORIZACIÓN**

> 🔒 **NO lo implementes si la decisión D6 no está autorizada.** Está en proceso. Si al ejecutar este prompt aún no hay respuesta, **entrega las fases 1 y 2 y detente aquí**, dejándolo anotado como pendiente.

Si está autorizada: `app/Console/Commands/PurgarDocumentosInvitados.php`

**La condición de borrado tiene dos candados, no uno:**

```sql
WHERE solicitudTransferencia = 'Si'
  AND solicitudFechaSalida < DATE_SUB(CURDATE(), INTERVAL <<DIAS>> DAY)
```

`solicitudTransferencia = 'Si'` ya existe y ya se marca cuando Tlalpan acusa recibo. **Sin ese candado, una solicitud que nunca llegó a Tlalpan se purgaría igual al envejecer, y su identificación se perdería para siempre.** No lo omitas por simplicidad.

Requisitos del comando:

- `--dias=` configurable, con `env` y valor por defecto **30**
- **`--dry-run` que solo reporte**, y que sea lo que se corre la primera vez
- Registrar en log cuántos archivos y cuántos MB se eliminaron, con los ids afectados
- Borrar el **archivo**, no el registro del invitado
- Idempotente: correrlo dos veces no falla ni cuenta de más
- Registrarlo en el planificador con `withoutOverlapping()`, cadencia diaria fuera de horario

> **Esto es irreversible y borra datos personales de terceros.** Ejecuta `--dry-run` en producción y **revisa la salida con alguien más** antes de la primera corrida real.

---

## Lo que NO debes tocar

- `TransferenciaController::documento()` ni `sinDocumento()`. El 404 uniforme es una decisión de seguridad.
- `SeguridadInvitados`. La convención de firma coincide con Tlalpan y está verificada en ambos lados.
- `HmacSigner`. Es idéntica en tres proyectos a propósito.
- `InvitadoController@guardar` ni `GuardarFormRequest`. La subida del socio funciona.
- La ruta de almacenamiento `solicitudinvitado/{idSolicitud}/{idInvitado}.{ext}`. Tlalpan y el Portal dependen de ella.
- El disco: sigue siendo `local` (privado). **Nunca lo muevas a `public`.**

## Criterios de aceptación

1. `GET /invitados/transferencia/pendientes` firmado devuelve, por invitado con foto, `documento`, `documentoBytes` y `documentoSha256` coherentes con el archivo real.
2. Un invitado **sin** foto devuelve los tres campos en `null`, sin romper el lote.
3. El tiempo de respuesta de `pendientes` **no se degrada** de forma perceptible.
4. El endpoint de documento **sigue devolviendo 404 uniforme** en todos los casos negativos.
5. Una petición **sin firma o desde otra IP** sigue siendo rechazada.
6. *(Si se autoriza la purga)* `--dry-run` **no borra nada** y lista exactamente lo que borraría.
7. *(Si se autoriza la purga)* Una solicitud con `solicitudTransferencia = 'No'` **nunca se purga**, por vieja que sea.

## Entregables

1. **Script SQL manual**, si tu implementación necesitara algún cambio de esquema. *(Con el diseño propuesto no hace falta ninguno; si concluyes que sí, justifícalo.)* Debe ser **re-ejecutable**, con diagnóstico previo, verificación y rollback.
2. **`CONFIGURACION_DOCUMENTOS_PORTAL.md`** en la raíz del proyecto, para quien despliega y no leyó el código:
   - Variables de `.env` nuevas (días de purga, etc.), cuáles son obligatorias
   - Permisos del disco `local` y dónde viven físicamente los archivos
   - **Crecimiento del almacenamiento**: hasta 12 MB por invitado, cómo medirlo y cómo vigilarlo
   - Entrada del planificador para la purga y cómo verificarla
   - **Procedimiento de la primera purga**: `--dry-run`, revisión a cuatro ojos, respaldo previo
   - Rollback y diagnóstico
3. **Nota de coordinación** con Tlalpan: si cambias la forma de `documento`, `documentoBytes` o `documentoSha256`, **rompes al otro lado**. Cualquier cambio de ese contrato se anuncia antes.

Escribe todo en español, con los comandos listos para copiar y pegar, y **sin valores reales de secretos ni IPs de producción**: usa marcadores explícitos.

---

## Decisiones ya tomadas — no las replantees

| Decisión | Valor fijado |
|---|---|
| **Quién inicia** | Tlalpan, siempre. El Portal solo expone y responde |
| **Transporte** | `GET` por archivo, binario en crudo. **Nunca** base64 dentro del lote de `pendientes` |
| **Firma** | GET → path, POST → body. Ya implementada y coincidente |
| **404** | Uniforme, sin distinguir motivos |
| **Disco** | `local` (privado). Los archivos **no** son accesibles por URL |
| **Purga** | Por **antigüedad + `solicitudTransferencia = 'Si'`**, nunca solo por antigüedad |
