# Calendario

El modulo `Calendario` de AnimalCharlie. concentra visitas, auditorias, tareas y eventos vinculados a empleados, proyectos, centros de trabajo, informes y expedientes SIGE. Entra por una agenda cronologica legible, mantiene el calendario como indice y abre la ficha del evento aparte para no comprimir ambas superficies.

## Uso operativo

- Acceso: barra lateral, panel inicial, lanzador ERP o `#calendario`.
- Vistas: `Agenda` es la entrada por defecto; dia, semana, mes y tecnicos siguen disponibles desde la barra compacta superior.
- Filtros: tecnico permanece visible por ser el filtro operativo mas frecuente; proyecto, centro, tipo y estado viven en `Mas filtros`, con contador de filtros secundarios activos.
- Resumen: hoy, semana, solapes y pendientes forman una sola tira compacta en vez de cuatro tarjetas.
- Navegacion enfocada `Calendario / Evento`: seleccionar un evento o pulsar `Nuevo evento` abre la ficha a ancho completo; `Calendario y evento` queda disponible en PC y tablet apaisada.
- Editor: crea o actualiza titulo, cliente portal, tipo, tecnico, proyecto, centro, informe, SIGE, fechas, estado y notas sin retirar ningun campo.
- Guardado estable: mientras `POST` o `PUT` y la recarga posterior siguen activos, `Guardar evento` queda bloqueado y muestra `Guardando...`. Las recargas de personas/calendario llevan un identificador monotono, de modo que una respuesta iniciada antes no puede restaurar el titulo antiguo después de una edición más reciente.
- Agenda unica: la vista `Agenda` sustituye la antigua lista `Proximos eventos` que repetia debajo los mismos registros y alargaba la pantalla.
- Estado vacio estable: aunque no existan eventos o los filtros no devuelvan resultados, `Agenda` conserva su contenedor cronologico y muestra dentro el aviso correspondiente.
- Las acciones `Guardar evento`, `Historico tecnico` y `Borrar` permanecen pegadas por encima de la sugerencia ambiental de Charlie para que el panel flotante no las intercepte.
- Acciones relacionadas: `Abrir certificacion` y `Abrir SIGE` saltan al registro vinculado cuando el evento tiene referencia.
- Prado Vivo: desde `Clientes > Portal`, `Nueva cita` abre Calendario con el cliente activo como contexto de trabajo.
- Tecnicos globales: el selector del editor y el filtro de tecnico consumen `GET /api/module-user-access?module=calendar&permission=calendar.write&status=active`, muestran usuarios reales con acceso de escritura y empleado vinculado, guardan `employee_id` para mantener compatibilidad con Personal y eventos existentes, y conservan `links.history`/`userHistoryUrl` para abrir `Usuarios > Auditoria` filtrada a `calendar`.
- Usuarios cliente: `Cliente portal` guarda `calendar_events.customer_id`; el bloque `Usuarios cliente` consume `GET /api/clients/{id}/users` para revisar accesos, WhatsApp y credenciales antes de la visita sin duplicar la logica de `Usuarios`. Si el evento viene desde `Clientes > Portal`, el cliente se precarga; si el proyecto seleccionado tiene un cliente con nombre coincidente, el editor puede resolver el cliente automaticamente.

La UI sigue `UI.md`: carcasa `ac-app-module`, toolbar compacta, filtros secundarios plegables, resumen lineal, `master-detail` con scroll interno y editor contenido para no crear scroll global. La semana usa siete columnas iguales; en iPad Air vertical conserva desplazamiento horizontal interno para evitar tarjetas ilegibles. Los colores por tipo se aplican mediante las clases `type-bea`, `type-sige`, `type-urgent` y `type-training`.

## API interna

Todas las rutas privadas requieren sesion con token.

| Ruta | Acceso | Descripcion |
| --- | --- | --- |
| `GET /api/calendar` | roles `admin`, `technician`, `reviewer` o permisos `calendar.read` / `calendar.write` | Lista eventos filtrables por `start` y `end`, incluyendo `customer_id` y `customer_name` cuando el evento esta enlazado a cliente portal. |
| `POST /api/calendar` | roles `admin`, `technician` o permiso `calendar.write` | Crea o actualiza un evento cuando el payload incluye `id`. |
| `PUT /api/calendar/{id}` | roles `admin`, `technician` o permiso `calendar.write` | Actualiza el evento indicado. |
| `DELETE /api/calendar/{id}` | roles `admin`, `technician` o permiso `calendar.write` | Borra el evento indicado. |

El permiso `calendar.write` tambien permite leer el calendario para que un perfil operativo no necesite duplicar `calendar.read`.
La UI usa `GET /api/module-user-access?module=calendar&permission=calendar.write&status=active` para construir el selector y el filtro de tecnico. `options[].employeeId` es el valor que se guarda en `employeeId`; si un evento antiguo referencia un empleado sin usuario vinculado, la UI conserva ese empleado como opcion local para que pueda abrirse o filtrarse. El boton `Histórico técnico` solo se habilita cuando el tecnico seleccionado tiene `links.history` o `userHistoryUrl` en `options[]`.

## Payload de evento

Campos aceptados por el backend:

| Campo | Uso |
| --- | --- |
| `id` | Identificador opcional. Si no existe en alta, se genera `cal-...`. |
| `title` | Titulo obligatorio del evento. |
| `eventType` / `event_type` | Tipo operativo, por defecto `task`. |
| `customerId` / `customer_id` | Cliente portal vinculado. Se guarda en `calendar_events.customer_id` para reutilizar `clientUsersSummary` y permisos de portal desde otros modulos. |
| `employeeId` / `employee_id` | Tecnico o empleado vinculado. Preferentemente sale de `options[].employeeId` de `/api/module-user-access` para que el tecnico sea un usuario real con acceso a Calendario. |
| `projectId` / `project_id` | Proyecto vinculado. |
| `locationId` / `location_id` | Centro de trabajo vinculado. |
| `reportId` / `report_id` | Informe o certificacion relacionada. |
| `sigeId` / `sige_id` | Expediente SIGE relacionado. |
| `startsAt` / `starts_at` | Inicio obligatorio. |
| `endsAt` / `ends_at` | Fin opcional. |
| `status` | Estado, por defecto `planned`. |
| `notes` | Notas internas. |

## Auditoria

Calendario registra actividad normalizada en `audit_log`:

- `calendar_save` al crear o guardar desde `POST /api/calendar`, incluyendo `customer_id` cuando existe cliente portal.
- `calendar_update` al actualizar desde `PUT /api/calendar/{id}`, incluyendo `customer_id` cuando existe cliente portal.
- `calendar_delete` al borrar desde `DELETE /api/calendar/{id}`.

`tools/verify_billing_calendar_workflows.py` espera a que finalice cada guardado antes de editar o reabrir el evento; no usa una pausa fija como prueba de persistencia.

El modulo queda como `calendar` por `entity_type`, por accion `calendar_*` y por `details.module` cuando otros modulos lo envien de forma explicita. Esto permite que `/api/activity?module=calendar` y `GET /api/users/{username}/history?module=calendar` agrupen la actividad sin reglas duplicadas en la UI.

## Consumo por otros modulos

Los modulos que necesiten leer agenda deben pedir `calendar.read` o `calendar.write`. Los modulos que preparen citas, visitas o tareas deben enviar el payload anterior a `POST /api/calendar` y dejar que el backend aplique permisos, auditoria e insercion `created_by`.

Para enlaces desde otras fichas, usar el hash `#calendario` y precargar contexto en estado de navegador o mediante selectores ya existentes, en vez de crear rutas paralelas. Si el contexto trae cliente CRM, enviar `customerId` para que Calendario pueda mostrar `Usuarios cliente` y reutilizar el alcance de `require_customer_access`.
