# Aviso en el Editor Web por cada solicitud de contacto nueva

Cuando un cliente envía el formulario de contacto, las personas que entran al Editor Web reciben una
notificación en la campanita del panel (Filament), con un botón **View** al detalle de la solicitud.

## Piezas

- **`AdminPanelProvider`**: `->databaseNotifications()` pone la campanita en la barra superior (consulta cada 30 s).
- **`App\Observers\ContactRequestObserver`** (`created`), registrado en `AppServiceProvider::boot()`. Como escucha el
  modelo, cubre cualquier formulario que guarde un `ContactRequest`. Hoy solo lo hace `ContactRequestPost`; los
  formularios de catálogo y de productos mandan correo pero **no guardan registro**, así que no generan aviso.
- **Destinatarios: todos los que tengan `access_web_editor`** (`User::permission('access_web_editor')`), ya sea por rol
  o por permiso directo. Hoy son `admin` y `superadmin`. No hay permiso nuevo ni cambio de `PermissionSeeder`.
  Si algún día hay que elegir quién recibe el aviso, el camino es un permiso de suscripción
  `receive_contact_request_notifications` como los `receive_*_notifications` de tickets.
- Contenido: título "New contact request", cuerpo `nombre (empresa, país)` y un botón **View**.
- La URL del botón es **relativa** (`isAbsolute: false`): el formulario corre en el subdominio de una división y el
  admin abre la campanita en el dominio donde tiene su sesión. Una URL absoluta apuntaría al subdominio del visitante.

## Dos notificaciones en la misma tabla

Los avisos de tickets (campanita Vuexy) y los de Filament comparten la tabla `notifications`. Se separan por el campo
`data->format`:

- Filament solo lee las filas con `data->format = 'filament'` (lo hace el propio paquete), así que las de tickets
  nunca salen en su campanita.
- `App\Livewire\Notifications\NotificationBell` (Vuexy) lee todas las del usuario. Se le agregó `ownNotifications()`,
  que excluye `format = 'filament'`, y se usa en la lista, el contador, **"marcar todas como leídas"** y
  **"borrar todas"**. Sin eso, un admin que limpia su campanita de tickets borraba también los avisos del panel.
- `notifications:prune` (03:10) poda por antigüedad todas las filas, de cualquier campanita.

## Va por la cola

`Filament\Notifications\DatabaseNotification` implementa `ShouldQueue` y `QUEUE_CONNECTION=database`: la fila la
inserta el **worker**, no la petición. Ventaja: el formulario no espera. Consecuencia: **sin worker activo no llegan
los avisos** (en producción `laravel-worker` ya corre y `deploy.sh` lo reinicia con `queue:restart`). En una prueba
con `DB::beginTransaction()` hay que forzar `config(['queue.default' => 'sync'])`, o el job se encola y se pierde
con el rollback.

## El aviso nunca debe costar el lead

La solicitud ya está guardada cuando se dispara el observer. Todo va en `try/catch` y un fallo solo se registra
(`Could not send the contact request notification`), sin afectar al cliente ni al correo.

## Verificado en local

- Crear una solicitud dentro de una transacción con rollback: 3 destinatarios (los 3 usuarios con
  `access_web_editor`), 3 notificaciones con `format = filament` y URL `/admin/contact-requests/{id}`.
- Campanita de Filament en el navegador: contador, título, cuerpo y botón **View** que navega al detalle.
- La campanita Vuexy ve 1 de 2 filas del usuario (excluye la de Filament) y su "borrar todo" deja la de Filament.

**No se probó**: el flujo completo con un envío real del formulario público ni el worker en producción;
tampoco el aviso en tiempo real (no hay Reverb/Echo; la campanita consulta cada 30 s).
