# PROMPT — Aceptar PDF como identificación · lado PORTAL (`CL032BACK`)

> **Ejecutar en:** `D:\FOURSIDES\CL001ASTU\PRE-PRODUCCION\VILLASTUR\CL032RESERVACIONESENLINEA\CL032BACK`
> **⚠️ REQUISITO PREVIO: `PROMPT_IMPLEMENTACION_PDF_TLALPAN.md` debe estar DESPLEGADO.** Si no lo está, detente y avisa.
> **Prompt hermano:** `PROMPT_IMPLEMENTACION_PDF_FRONT.md` (se ejecuta en `CL032FRONT`)

Actúa como desarrollador senior de este proyecto. El negocio pidió que el socio pueda subir la identificación del invitado **también en PDF**. Tu trabajo es la validación, el almacenamiento y la entrega en el Portal.

---

## No arranques sin confirmar esto

Tlalpan rechaza por MIME lo que no esté en su lista blanca, y **ese rechazo es definitivo**: marca los intentos al máximo y no reintenta, así que un PDF aceptado aquí antes de que Tlalpan lo admita **se pierde para siempre** y el operador ve la solicitud sin identificación.

**Confirma que `config/invitados.php` de `CL001SICAMV2` ya tiene `'application/pdf' => 'pdf'` en `documentos.mimes` y que está desplegado.** Si no puedes confirmarlo, no apliques el cambio de la regla de validación: haz todo lo demás y déjalo señalado.

---

## Estado actual — verifícalo antes de tocar nada

| Pieza | Ubicación | ¿Sirve para PDF hoy? |
|---|---|:---:|
| Regla de validación | `app/Http/Requests/Invitados/Invitado/GuardarFormRequest.php:63` | ❌ **La regla `image` rechaza PDF. Es tu cambio principal** |
| Mensajes de error | mismo archivo, `messages()` | ❌ `documento.image` queda obsoleto |
| Guardado en disco | `app/Http/Controllers/Invitados/InvitadoController.php:108-114` | ✅ `->extension()` deduce del MIME real: devuelve `pdf` |
| Entrega al socio (reanudar captura) | `InvitadoController::documento()` ~línea 209 | ✅ `response()->file()` resuelve el `Content-Type` solo |
| Entrega a Tlalpan | `app/Http/Controllers/Invitados/TransferenciaController.php:209` | ✅ Igual. Además **ya resuelve por nombre sin extensión**, así que la extensión no le importa |
| Borrado al eliminar invitado | `InvitadoController::eliminar()` ~línea 248 | ✅ Igual, resuelve por nombre sin extensión |
| Metadatos del lote (`documentoBytes`, `documentoSha256`) | `app/Http/Resources/Invitados/Transferencia/PendienteResource.php` | ✅ Opera sobre bytes, no interpreta el formato |

**Que todo el almacenamiento resuelva por *nombre sin extensión* es lo que hace este cambio pequeño.** El docblock de `PendienteResource` (~línea 66) **ya dice** `(jpg, png, pdf…)`: era una inconsistencia documental heredada que este cambio vuelve verdadera. Déjala, ya no miente.

---

## Lo que hay que construir

### 1. La regla de validación — `GuardarFormRequest.php:63`

Hoy:

```php
'documento' => 'required|image|mimes:jpeg,jpg,png,webp|max:12288',
```

**La regla `image` es la que rechaza el PDF** (usa `getimagesize()`, que no reconoce PDF). Hay que quitarla y sustituirla por `file`, ampliando `mimes`:

```php
'documento' => 'required|file|mimes:jpeg,jpg,png,webp,pdf|max:12288',
```

**Lo que se pierde al quitar `image`, y por qué no importa aquí:** `image` verificaba que el archivo fuera decodificable como imagen. La protección real la sigue dando `mimes`, que en Laravel **no compara la extensión del nombre**: adivina la extensión a partir del MIME detectado del contenido y la contrasta con la lista. Un `.exe` renombrado a `.jpg` sigue rechazándose. **Verifícalo con una prueba, no lo tomes de este documento.**

⚠️ **Hay una regla `dimensions` comentada al final de esa línea.** Si alguien la activa después del cambio, **rechazará todos los PDF**, porque no tiene dimensiones. Deja escrito ahí mismo que ya no es aplicable de forma incondicional.

### 2. Los mensajes — `messages()` del mismo archivo

- `documento.image` queda muerto: bórralo o sustitúyelo.
- `documento.mimes` debe nombrar el formato nuevo: *"El documento debe ser una imagen (JPG, PNG, WEBP) o un PDF."*
- Revisa `documento.required`, `documento.max` y `documento.uploaded`: **si dicen "imagen", ahora mienten.** El front espeja estos textos; que coincidan importa.

### 3. El docblock de la clase

El encabezado de `GuardarFormRequest` documenta `documento` como *"imagen de la identificación con fotografía visible (D3 p.10)"*. Actualízalo: sigue siendo obligatorio que la fotografía sea visible y legible — **eso es regla de negocio y no cambia**—, pero ahora el contenedor puede ser un PDF.

### 4. Endurecer la entrega (recomendado, decide y justifica)

Un PDF **no es un formato inerte**: puede llevar JavaScript embebido. Los dos endpoints que sirven el archivo lo entregan **en línea**:

- `InvitadoController::documento()` — al socio, para repintar su miniatura
- `TransferenciaController::documento()` — a Tlalpan

Evalúa agregar `X-Content-Type-Options: nosniff` y una CSP restrictiva en la respuesta. Pruébalo en un navegador real antes de darlo por bueno; si el visor deja de renderizar, es peor el remedio. Si decides no ponerlo, di por qué.

### 5. Documentación del proyecto

Estos archivos enumeran los formatos admitidos y hoy **omiten PDF**. Actualízalos:

- `CONFIGURACION_DOCUMENTOS_PORTAL.md` — tabla "Extensiones posibles" (~línea 78)
- `NOTA_COORDINACION_DOCUMENTOS_TLALPAN.md` — §2, punto 4 (la nota que lee el equipo de Tlalpan). **Deja asentado que Tlalpan ya lo admite y desde cuándo.**
- `PROPUESTA_DOCUMENTO_IDENTIFICACION_INVITADOS.md` y `PROMPT_IMPLEMENTACION_DOCUMENTOS_PORTAL.md` — donde citen la regla `image|mimes:...`
- `config/invitados.php` — el bloque `documentos` describe el archivo que sube el socio

---

## Casos borde

| Caso | Comportamiento correcto |
|---|---|
| **PDF cifrado o protegido con contraseña** | Pasa `finfo` (el encabezado es un PDF legítimo) y se almacena. El operador no podrá abrirlo. **No lo resuelvas en el servidor**: decide si vale la pena advertirlo y déjalo señalado en el reporte |
| **PDF de 40 páginas** | Pasa si respeta los 12 MB. No es problema técnico; menciónalo como asunto de negocio si lo consideras |
| **Archivo que no es imagen ni PDF** | Rechazado por `mimes`, con el mensaje nuevo |
| **`.exe` renombrado a `.pdf`** | **Debe seguir rechazándose.** Es la prueba que demuestra que ampliar `mimes` no abrió la puerta |
| **PDF de 0 bytes** | `required` no lo atrapa (existe). `mimes` sí, porque `finfo` no detecta nada. Verifícalo |

---

## Lo que NO debes hacer

- ❌ Sustituir `mimes` por `mimetypes` sin entender la diferencia, ni validar por la extensión del nombre
- ❌ Agregar una librería de PDF, ni rasterizar, comprimir o "sanear" el PDF en el servidor
- ❌ Mover `max:12288` — está alineado con Tlalpan y los dos se mueven juntos o no se mueven
- ❌ Cambiar la ruta de almacenamiento, el disco privado ni el esquema de nombres
- ❌ Aceptar MIME adicionales de paso (`application/x-pdf`, `application/octet-stream`, TIFF, HEIC)
- ❌ Tocar el canal de transferencia: `PendienteResource`, `InvitadoResource` y `TransferenciaController` **no necesitan cambios**. Si crees que sí, explica por qué antes de tocarlos

---

## Verificación

1. Subir un **PDF real** → se guarda como `storage/app/private/solicitudinvitado/{idSolicitud}/{idInvitado}.pdf`
2. Subir un **JPEG** → **sigue funcionando idéntico**. Es la prueba de no-regresión
3. Reanudar la captura (`/invitados/solicitud/pendiente`) con un invitado que subió PDF: el endpoint entrega el archivo con `Content-Type: application/pdf`
4. `GET /invitados/transferencia/documento/{idInvitado}` (firmado) entrega el PDF a Tlalpan
5. `documentoBytes` y `documentoSha256` del lote **describen el PDF real** (compara el SHA-256 contra el del archivo en disco)
6. Un `.exe` renombrado a `.pdf` → **rechazado**
7. Extremo a extremo con Tlalpan: subir un PDF y confirmar que el operador **lo ve**
8. `php -l` en cada archivo tocado

## Al terminar, reporta

1. Archivos modificados, con las líneas
2. Confirmación de que verificaste el despliegue previo de Tlalpan
3. Resultado de las pruebas 2, 5, 6 y 7 — son las que no se pueden dar por hechas
4. Qué decidiste sobre los headers de seguridad y sobre el PDF con contraseña
5. Cualquier decisión donde te apartaste de este prompt, con el motivo
