# Acceso de participantes el día del retiro

Versión 1.0 · 4 de septiembre de 2026 · Complementa la sección 6 de `docs/ESPECIFICACION_DOFA_v2.md`.

Esta nota explica cómo dejar listo el ingreso de los participantes (código de 6 caracteres y QR)
cuando la aplicación se sirve desde el computador del consultor en la red local del retiro.

---

## 1. Antes del retiro: cambiar el host de los QR

En desarrollo la aplicación vive en `http://localhost:8080`. Los celulares de los participantes
no pueden abrir `localhost`, así que los QR y la URL corta impresa en las tarjetas deben apuntar
a la dirección del computador del consultor en la red local.

Eso se cambia **sin tocar código**, con una sola variable en `.env.docker`:

```
ACCESO_URL=http://192.168.1.50:8080
```

- Si queda vacía (`ACCESO_URL=`), se usa el host de la petición: es lo correcto para desarrollo.
- Solo se toma el origen (esquema, host y puerto); la ruta se ignora.
- Si se escribe sin `http://`, se asume `http://`.
- `.env.docker` está montado como `.env` dentro del contenedor: el cambio aplica en la siguiente
  petición, sin reiniciar. Si alguna vez se cacheó la configuración:
  `docker compose -f docker/compose.yaml exec -T app php artisan config:clear`.

Afecta a la vez:

| Dónde | Qué cambia |
|---|---|
| PDF de tarjetas (`/participantes/tarjetas.pdf`) | la URL corta impresa y el QR del enlace mágico de cada persona |
| Pantalla `Setup/Participantes` | el QR general, la URL corta y el enlace de cada participante |
| Pantalla pública `/acceso` | el QR general |
| Panel del facilitador y Monitor (video beam) | el QR general (`url_acceso`) |

**Cómo averiguar la dirección:** en el computador del consultor, `ipconfig` (Windows) y tomar la
dirección IPv4 de la tarjeta conectada a la red del retiro. Verificar desde un celular que
`http://<esa IP>:8080/acceso` abre la pantalla del código antes de imprimir las tarjetas.

## 2. Comprobación rápida

```bash
docker compose -f docker/compose.yaml exec -T app php artisan tinker --execute="\
  \$s = app(App\Services\AccesoService::class); \
  echo \$s->urlAcceso(), PHP_EOL; \
  echo \$s->urlToken(App\Models\User::where('es_participante_retiro', true)->first()), PHP_EOL;"
```

Debe imprimir las dos URL con el host de `ACCESO_URL`.

## 3. Límite de intentos por minuto

Con Docker publicando el puerto 8080, el contenedor ve **una sola dirección IP de origen** para
todos los celulares de la red local (el proxy de Docker reescribe el origen). Es decir, el límite
por IP se reparte entre todos los participantes: con 20 peticiones por minuto, 15 personas
entrando a la vez agotan la cuota y ven "Too Many Requests".

Por eso hay dos cubetas separadas, configurables en `.env.docker`:

| Variable | Valor en `.env.docker` | Cubre |
|---|---|---|
| `ACCESO_VISTAS_POR_MINUTO` | 240 | abrir `GET /acceso` (no permite adivinar nada) |
| `ACCESO_INTENTOS_POR_MINUTO` | 120 | enviar un código (`POST /acceso`) y abrir un enlace con token |

El valor por defecto de ambas en `config/acceso.php` es el del contrato (20 intentos y 120 vistas);
`.env.docker` los sube para el retiro. Si aun así aparece "Too Many Requests", subir los números y
recargar; no hace falta reiniciar.

**Desviación consciente del contrato, y cómo se compensa.** La especificación (sección 6) y el
contrato de rutas (3.3) fijan 20 intentos por minuto y por IP; el despliegue del retiro usa 120
por la razón de arriba. Para que subirlo no abra la puerta a la fuerza bruta, la cubeta de intentos
se divide además **por el código que se está probando**: `routes/acceso.php` aplica dos límites a
la vez, `acceso|<ip>` con el valor configurado y `acceso-codigo|<ip>|<codigo>` fijo en 20 por
minuto. Es decir, adivinar **un** código sigue limitado a 20 intentos por minuto aunque la sala
comparta la IP.

El riesgo residual es despreciable: el alfabeto de 32 caracteres sin ambiguos da 32^6 =
1.073.741.824 combinaciones para unos 15 códigos emitidos, y los accesos caducan a las 72 horas.

## 4. Orden de trabajo del facilitador

1. Entrar como consultor y abrir **Participantes** (`/participantes`).
2. Pegar la lista en la carga masiva, una persona por línea:
   `Nombre; correo; cargo; rol` — el correo y el cargo son opcionales, el rol por defecto es
   `participante`. Sin correo se genera uno interno `<codigo>@participantes.fedef.local`.
3. Ajustar "Vigencia del acceso" (72 horas por defecto) y crear.
4. Revisar la tabla: cada persona queda con su código de 6 caracteres y su enlace.
5. Botón **Imprimir tarjetas** → PDF con 8 tarjetas por hoja carta (nombre, cargo, código, QR del
   enlace mágico, URL corta y fecha de vencimiento). Se pueden marcar filas para imprimir solo esas.
6. Si alguien pierde la tarjeta o el código se filtra: **Regenerar**. El código y el enlace
   anteriores dejan de servir de inmediato; hay que reimprimir esa tarjeta.

## 5. Cómo entra un participante

- **Con QR:** escanea la tarjeta, entra directo a `/participar` sin escribir nada.
- **Sin QR:** abre la URL corta de la tarjeta y escribe el código. El campo pasa a mayúsculas solo,
  ignora guiones y espacios y se detiene en 6 caracteres.
- Códigos inválidos, vencidos o de usuarios que no son del retiro devuelven a la misma pantalla con
  un mensaje en español; nunca se dice si el código existe o no.
- Un participante no ve el menú del facilitador (usa `ParticipanteLayout`, sin barra de navegación)
  y recibe 403 en `/participantes` y en `/projects/{id}/dofa`.
- El QR general del video beam y de la pantalla `/acceso` lleva a la pantalla del código, no a una
  persona concreta: sirve para quien perdió la tarjeta pero recuerda su código.
