# Auditoria operativa CXP

## Conexion separada

La bitacora principal se escribe en la conexion Doctrine `audit`, configurada por:

```dotenv
AUDIT_DATABASE_URL="mysql://user:password@host:3306/_temporalescxp?serverVersion=8.0.31&charset=utf8mb4"
AUDIT_STRICT=false
```

La tabla principal vive fuera de la base transaccional. Crear la estructura con:

```bash
mysql _temporalescxp < sql/audit/001_create_audit_events.sql
```

No guardar credenciales reales en el repositorio. En produccion, `AUDIT_DATABASE_URL` debe venir del gestor de secretos o variables del entorno.

## Modo estricto y fallback

`AUDIT_STRICT=true` bloquea la operacion si no se puede escribir un evento critico.

`AUDIT_STRICT=false` deja continuar la operacion y guarda el evento pendiente en `audit_outbox_events` dentro de la base principal. Reenviar pendientes con:

```bash
php bin/console app:audit:flush-outbox --limit=500
```

Monitorear la tabla `audit_outbox_events`; eventos en estado `pending` durante mucho tiempo indican caida o mala configuracion de `_temporalescxp`.

## Datos sensibles

El logger enmascara campos cuyo nombre contenga password, token, secret, api_key, authorization, cookie, session, credenciales, private_key, access_token o refresh_token. Los JSON se limitan para evitar serializaciones enormes.

No registrar secretos completos, cookies, tokens, llaves API ni cuentas bancarias completas en llamadas explicitas de auditoria.

## Consulta

La pantalla `/config/auditoria` lee desde `_temporalescxp.audit_events`.

Filtros disponibles:

- fecha desde/hasta
- cliente y sitio segun contexto
- usuario_id
- modulo
- accion
- entidad y entidad_id
- resultado
- texto libre

La vista detalle muestra antes, despues, diff, IP, user-agent, request_id y correlation_id. La exportacion CSV esta limitada a 10,000 filas; para exportes mayores debe moverse a `async_jobs`.

## Retencion

`audit_events` es append-only. No editar ni eliminar desde UI.

La depuracion debe hacerse solo con un comando administrativo explicito y politica aprobada. Recomendacion inicial:

- conservar 24 meses online
- respaldar `_temporalescxp` diariamente
- archivar eventos antiguos antes de eliminar
- registrar en bitacora cualquier purga o exportacion

## Jobs asincronos

La tabla `async_jobs` es el contrato operativo para procesos pesados. Todo job debe incluir `cliente_id` y opcionalmente `sitio_id`; no depender de sesion.

Estados:

- pendiente
- procesando
- terminado
- error
- cancelado

Cada job debe auditar creado, iniciado, progreso relevante, terminado, error y reintento.

Hasta instalar Symfony Messenger, los jobs pueden ser procesados por comandos propios. Si se agrega Messenger:

```dotenv
MESSENGER_TRANSPORT_DSN=doctrine://default
```

Ejemplo systemd:

```ini
[Unit]
Description=CXP worker
After=network.target

[Service]
WorkingDirectory=/var/www/cxp
ExecStart=/usr/bin/php bin/console messenger:consume async --time-limit=3600 --memory-limit=256M
Restart=always
RestartSec=5
User=www-data

[Install]
WantedBy=multi-user.target
```

## Vouchers y sincronizacion a nube

Los vouchers de pago se generan desde pagos y quedan como fuente unica en `pagos_vouchers` y `pagos_voucher_detalles`. La generacion es idempotente por la llave unica `pago_id`; si se regenera desde UI se actualiza el voucher existente y queda auditado como `voucher_regenerate`.

Acciones auditadas:

- `voucher_create`
- `voucher_regenerate`
- `voucher_print`
- `voucher_excel_download`
- `voucher_void`
- `cloud_sync_enqueued`
- `cloud_sync_success`
- `cloud_sync_error`

La exportacion actual de Excel es CSV compatible con Excel. Si el volumen crece, mover la generacion de archivo real a `async_jobs` y guardar la ruta en `archivo_excel`.

La sincronizacion a nube usa `CloudSheetWriterInterface`. Por defecto se registra `NullCloudSheetWriter`, que no requiere credenciales y devuelve un `external_id` deterministico con prefijo `null:`.

Variables preparadas:

```dotenv
CLOUD_SHEETS_MODE=null
CLOUD_SHEETS_SPREADSHEET_ID=
CLOUD_SHEETS_CREDENTIALS_PATH=
```

Procesar jobs pendientes de nube con:

```bash
php bin/console app:cloud-sync:run --limit=25
```

Generar archivos PDF/XLSX reales de vouchers con:

```bash
php bin/console app:voucher-files:run --limit=10
```

Para un worker temporal sin Messenger:

```bash
while true; do
  php bin/console app:audit:flush-outbox --limit=500
  php bin/console app:cloud-sync:run --limit=25
  php bin/console app:voucher-files:run --limit=10
  sleep 10
done
```

## Cheques y transferencias

La fase deja flujos operativos para cheques y transferencias:

- `cheques_pago` evita repetir `numero_cheque` por `cliente_id` y `cuenta_bancaria_id`.
- `transferencias_pago` evita duplicados por cliente, cuenta origen, fecha, referencia y monto.
- `cloud_sync_logs` evita duplicar sincronizaciones por cliente, entidad, destino y hash del payload.
- `cheques_pago_impresiones` registra impresion, reimpresion y datos de request.
- Las rutas criticas usan POST + CSRF.
- La impresion de cheque requiere un formato activo; se busca por cuenta, luego banco, luego default del cliente.

Acciones auditadas:

- `cheque_create`
- `cheque_edit`
- `cheque_print`
- `cheque_reprint`
- `cheque_void`
- `cheque_deliver`
- `cheque_duplicate_denied`
- `cheque_print_denied`
- `transfer_create`
- `transfer_edit`
- `transfer_void`
- `transfer_sync_enqueue`
- `transfer_sync_retry`
- `transfer_duplicate_denied`

## Formatos de impresion

Los formatos viven en:

- `formatos_impresion_pago`
- `formato_campos_impresion`

El modulo esta disponible en `/config/formatos-pago` para usuarios `ROLE_ADMIN`. Permite crear, duplicar, activar/desactivar, ajustar coordenadas con drag/drop e inputs X/Y/ancho/alto, e imprimir prueba.

Campos minimos:

- fecha
- beneficiario
- monto_numero
- monto_letras
- numero_cheque
- detalle_facturas
- no_negociable

Acciones auditadas:

- `print_format_create`
- `print_format_edit`
- `print_format_duplicate`
- `print_format_toggle`
- `print_format_test`

## Desbloqueo de documentos

El desbloqueo operativo usa `ROLE_DESBLOQUEAR_FACTURA`.

Regla funcional:

- No cambia `estado`, saldos, retenciones ni aplicaciones.
- Registra una autorizacion temporal en `desbloqueado_at`, `desbloqueado_hasta`, `desbloqueado_by` y `desbloqueo_motivo`.
- La autorizacion dura 8 horas y documenta que observaciones/lineas pueden revisarse operativamente.
- Si el usuario tiene `ROLE_DESBLOQUEAR_FACTURA`, no se pide clave adicional.
- Si no tiene el rol, se exige la clave del usuario autenticado.

No se permite desbloquear:

- documento anulado
- documento pagado o sin saldo
- documento con cheque entregado
- documento con transferencia sincronizada

Todo intento exitoso o denegado queda en bitacora con motivo, estado, si se pidio clave, IP y user-agent. El historial visible vive en `documento_desbloqueo_historial`.

## Documentos relacionados

`DocumentoCxp` ahora tiene `documento_relacionado_numero`. La busqueda se resuelve bajo demanda desde el detalle, no en listados grandes.

Busqueda:

- primero `documento_relacionado_numero`
- si no existe, `referencia`
- si no existe, `documentoPadre`
- siempre dentro del mismo cliente y sitio autorizado

La vista rapida muestra numero, fecha, proveedor, tipo, total anotado, total real, diferencia, estado y sitio. La consulta y los cambios del campo quedan auditados.

## Pruebas manuales recomendadas

- Generar voucher desde un pago aplicado.
- Regenerar el mismo voucher y confirmar que no aparece duplicado.
- Abrir vista imprimible del voucher.
- Descargar CSV compatible con Excel.
- Encolar PDF/XLSX de voucher y ejecutar `php bin/console app:voucher-files:run --limit=10`.
- Encolar sincronizacion a nube y revisar `/config/jobs`.
- Ejecutar `php bin/console app:cloud-sync:run --limit=25` y confirmar `cloud_sync_logs.estado = sincronizado`.
- Revisar bitacora para creacion, regeneracion, impresion, descarga, anulacion y sincronizacion.
- Intentar consultar voucher de otro cliente con usuario no superadmin y confirmar acceso denegado.
- Aplicar la migracion en un ambiente con PDO MySQL y confirmar llaves unicas de cheque/transferencia.
- Crear cheque desde pago de forma cheque.
- Intentar repetir numero de cheque en la misma cuenta.
- Crear formato de cheque, mover campos e imprimir prueba.
- Imprimir y reimprimir cheque real.
- Marcar cheque entregado y probar anulacion con/sin permiso superior.
- Registrar transferencia desde pago de forma transferencia.
- Intentar duplicar transferencia por cuenta, fecha, referencia y monto.
- Sincronizar transferencia a nube y revisar job/log.
- Desbloquear documento con `ROLE_DESBLOQUEAR_FACTURA` y confirmar que no pide clave.
- Intentar desbloquear sin rol y sin clave; debe denegar y auditar.
- Intentar desbloquear documento pagado/anulado o con cheque entregado; debe denegar.
- Guardar numero de documento relacionado y consultarlo desde el modal.
- Intentar consultar relacionado fuera de cliente/sitio autorizado.

## Riesgos pendientes

- Mover exportes/reportes/importaciones reales a workers requiere implementar handlers por caso.
- Integracion Google Sheets real requiere implementar un writer concreto con credenciales externas; el writer nulo solo valida el flujo operativo.
- PDF/XLSX reales de voucher se generan por job; si el worker no corre, la UI seguira mostrando solo el boton de generacion.
- El texto de monto en letras de cheques es una version simple; si se requiere redaccion legal completa, implementar conversor local para Guatemala antes de produccion.
- El desbloqueo no habilita recalculo fiscal ni reversion de pagos; cualquier flujo que requiera recalculo debe definirse como cambio separado.
- La busqueda de documento relacionado usa numero/referencia exacta; si se requiere matching por origen ERP externo, agregar campo de origen dedicado antes de ampliar busquedas.
- La auditoria automatica cubre cambios Doctrine; operaciones que no pasan por entidades deben llamar explicitamente a `AuditLogger`.
