# Portal privado de prestadores

## Alcance

`web_mycode` ofrece un portal privado para que cada comercio o profesional
registre y edite su propia información. El propietario usa el login clásico de
usuario; no existe un tipo de login separado para prestadores.

La web no publica un directorio, mapa, fichas ni puntos de prestadores. El
descubrimiento público queda diferido a la aplicación móvil/aplicación cliente.
Las lecturas `public/service-catalogs/*` del API se conservan únicamente para
rellenar los selectores de los formularios privados.

El mapa de Google de `/mi-prestador/sucursales` es una herramienta privada de
edición de coordenadas. No es un mapa público de locales.

## Idioma

- Todas las pantallas públicas y privadas del portal están disponibles en
  español e inglés.
- Laravel selecciona el idioma desde la preferencia guardada en sesión y, si no
  existe, desde `Accept-Language`.
- El switch visible ES/EN reutiliza `/locale/{locale}`, conserva la selección
  entre páginas y regresa a la pantalla del portal desde la que fue activado.
- Las consultas de categorías, servicios, especialidades y atributos propagan
  únicamente `locale=es|en`; la API devuelve el nombre y la descripción en ese
  idioma y mantiene ambos valores editables en el panel administrativo.

## Arquitectura y sesión

- Laravel funciona como BFF. El navegador llama solamente rutas del mismo
  origen bajo `/mi-prestador/*` y `/api/bridge/provider/*`.
- El BFF usa los endpoints clásicos de autenticación de `api_mycode`, guarda el
  JWT en la sesión Laravel y nunca lo entrega al HTML ni al JavaScript.
- `provider.web` protege las pantallas privadas y `provider.bridge` obtiene el
  token desde la sesión del servidor. Un `Authorization` enviado por el
  navegador no reemplaza esa sesión.
- El bridge acepta sólo operaciones de propietario incluidas en su registro de
  rutas, valida UUID, payloads y archivos, y proyecta las respuestas para no
  exponer secretos, trazas ni rutas privadas.
- La clave de Google Maps se incorpora solamente a las pantallas privadas de
  sucursales. Debe estar restringida por dominio y por API en Google Cloud.

## Estados y política de edición

El ciclo reconocido es `draft`, `pending_review`, `published`, `rejected`,
`suspended` y `archived`.

- `draft` y `rejected`: el propietario puede editar y volver a enviar.
- `pending_review`, `published`, `suspended` y `archived`: el portal queda en
  modo sólo lectura.
- La publicación depende del estado autoritativo devuelto por `api_mycode`; la
  UI no promueve estados por sí sola.
- Para enviar a revisión se requiere identidad, al menos una categoría y una
  sucursal activa con `location_confirmed_by_user=true`, horario y contacto
  público. La verificación documental es opcional.

## Resoluciones y apelaciones

`/mi-prestador/resoluciones` muestra únicamente resoluciones de prestadores y
sucursales que administra la sesión autenticada. El detalle canónico es
`/mi-prestador/resoluciones/{uuid}`; el enlace del correo conserva este destino
a través del login, 2FA o código de recuperación y se consume una sola vez.

La vista incluye prestador, sucursal, motivo localizado, decisión, medida,
fecha y estado de apelación. Una advertencia o suspensión vigente puede
apelarse una sola vez con un argumento de texto plano de 1 a 2.000 caracteres.
La interfaz confirma antes de enviar, bloquea doble envío y deja la apelación
existente en sólo lectura. Mientras esté pendiente informa explícitamente que
la medida continúa vigente, sin prometer plazo ni resultado.

Laravel sólo proyecta la lista y el detalle autorizados por el API. El HTML,
JSON y JavaScript del propietario no contienen identidad del denunciante,
moderador, revisor, justificación interna, descripción privada, eventos,
coordenadas ni enlaces del paginador upstream. `403` y `404` usan mensajes
neutros; `409` refresca el estado; `422` conserva el argumento y marca su campo;
`429` informa el límite y los errores de conexión ofrecen reintento seguro.
Los fallos no reconocidos nunca muestran mensajes técnicos del navegador ni del
bridge; `403` y `404` comparten el mismo texto neutro para no revelar recursos
que pertenecen a otro prestador.
Todos esos mensajes están disponibles en español e inglés.

## Archivos permitidos

| Uso | Formatos | Máximo |
| --- | --- | ---: |
| Logo del prestador | JPG, JPEG, PNG, WebP | 4 MB |
| Portada del prestador | JPG, JPEG, PNG, WebP | 8 MB |
| Imagen de sucursal | JPG, JPEG, PNG, WebP | 8 MB |
| Documento de verificación | PDF, JPG, JPEG, PNG | 10 MB |

Los archivos se envían como multipart, nunca como base64. Las rutas internas de
documentos de verificación no deben llegar al DOM ni a las respuestas del BFF.

## Variables de entorno

Configurar valores reales sólo en el `.env` del servidor:

| Variable | Uso |
| --- | --- |
| `API_MYCODE_URL` | Origen HTTPS de `api_mycode`. |
| `API_MYCODE_ADMIN_JWT_SECRET` | Verificación del JWT del panel administrativo; usa `JWT_SECRET` como fallback. No se expone al portal. |
| `JWT_SECRET` | Fallback compatible del secreto anterior. |
| `API_ENCRYPTION_KEY_ID` | Identificador compartido para cifrado opcional de payloads JSON. |
| `API_ENCRYPTION_KEY_B64` | Secreto base64 de 32 bytes para AES-256-GCM cuando el cifrado está habilitado. |
| `GOOGLE_MAPS_API_KEY` | Clave JavaScript de Maps, restringida al dominio y APIs necesarias; sólo se renderiza en editores privados de sucursales. |

También deben estar correctamente configurados `APP_KEY`, `APP_URL`, el driver
de sesión y el backend de caché. Nunca guardar valores secretos en Git, logs,
HTML, `localStorage` o `sessionStorage`. Después de cambiar el `.env`, reconstruir
la caché de configuración; no basta con reiniciar el navegador.

## Prerrequisito de `api_mycode`

Antes de desplegar la web, `api_mycode` debe estar accesible y tener aplicadas
las migraciones de catálogos, prestadores, sucursales, operaciones, estados y
verificaciones. La base PostgreSQL debe tener PostGIS habilitado y el índice
espacial de ubicaciones creado.

En el servidor del API:

```bash
cd /var/www/services/apimycode
php artisan migrate --force
php artisan migrate:status
```

Verificar que respondan `/api/v1/public/service-catalogs/categories` y las rutas
de propietario. Una llamada directa del servidor a
`/api/v1/service-providers/mine` exige un Bearer vigente del propietario y pasa
por `auth:api` más `token.version`; la sesión Laravel sólo autentica al
navegador frente al bridge de `web_mycode`, no frente al API directo. Una
respuesta vacía de prestadores puede ser correcta; un error de migración,
PostGIS o conexión no.

## Verificación previa y CI

El entorno de verificación necesita las dependencias de desarrollo de Composer;
se ejecuta antes de preparar el artefacto productivo:

```bash
composer install --prefer-dist --no-interaction
npm ci
php artisan test
npm run test:admin-dashboard
npm run test:admin-users-status
npm run test:provider
npm run production
php artisan architecture:audit --strict
git diff --check
```

El gate termina sin fallos, genera `public/js/provider/app.js` y no
reporta deuda arquitectónica nueva ni errores de espacios en el diff. La
aceptación interactiva en navegador se ejecuta por separado; este documento no
afirma que esos flujos manuales hayan sido probados.

## Despliegue productivo

Apache debe permitir el contrato que anuncia el portal: hasta dos evidencias de
10 MB por solicitud. En el SAPI que sirve la web y la API se requieren, como
mínimo:

```ini
upload_max_filesize = 10M
post_max_size = 25M
```

`post_max_size` incluye ambos archivos y la sobrecarga `multipart/form-data`.
No basta con cambiar el `php.ini` de CLI: hay que editar el archivo del módulo
Apache o PHP-FPM realmente habilitado, validar la configuración y recargar el
servicio. Si PHP conserva un límite inferior, descartará el archivo antes de
que Laravel pueda aplicar su validación de 10 MB.

Después de aprobar el gate, instalar sin dependencias de desarrollo y reconstruir
las cachés desde la raíz de `web_mycode` del servidor:

```bash
composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
npm ci
npm run production
php artisan optimize:clear
php artisan config:cache
php artisan view:cache
php artisan route:clear
```

Se usa `route:clear` porque el proyecto mantiene una ruta Closure heredada. Si
esa ruta se reemplaza por un controlador, `php artisan route:cache` puede
validarse y habilitarse en el despliegue. No ejecutar la suite PHP después de
`composer install --no-dev`, porque PHPUnit pertenece al entorno de desarrollo.

## Smoke checks posteriores al despliegue

Reemplazar `https://mycode.cl` sólo si el dominio del ambiente es distinto:

```bash
curl -sS -o /dev/null -w '%{http_code}\n' https://mycode.cl/mi-prestador/login
curl -sS -o /dev/null -w '%{http_code} %{redirect_url}\n' https://mycode.cl/mi-prestador
curl -sS https://mycode.cl/mi-prestador/session/status
curl -sS -o /dev/null -w '%{http_code}\n' \
  https://mycode.cl/api/bridge/provider/service-providers/mine
curl -sS -o /dev/null -w '%{http_code} %{redirect_url}\n' \
  https://mycode.cl/mi-prestador/resoluciones
```

Resultados esperados sin sesión: login `200`, portal privado `302` hacia
`/mi-prestador/login`, estado `{"authenticated":false}` y bridge `401`.

Con una cuenta de prueba verificada, validar login clásico, 2FA o recuperación
si corresponden, edición de perfil/sucursales/operaciones, cargas y envío a
revisión. No usar cuentas ni documentos reales para smoke tests.

## Privacidad y seguridad

- No registrar cuerpos de autenticación, JWT, códigos 2FA ni documentos.
- Mantener HTTPS, cookies seguras, protección CSRF y límites de intentos.
- Restringir `GOOGLE_MAPS_API_KEY` por referer del dominio y sólo a las APIs
  necesarias para el editor.
- No enlazar rutas privadas de almacenamiento. Las previsualizaciones válidas
  deben venir de URLs públicas proyectadas por el API.
- Revisar permisos y estado en `api_mycode`; la web nunca es la autoridad para
  propiedad, moderación o publicación.
