Skip to content

Agenda — Documentacion Tecnica Backend

Modulo: general Feature: Agenda (gestion de eventos, visibilidad compuesta, recurrencia, eventos de sistema) Fecha: 2026-03-31


Documento de Negocio


Nivel de Schema

Todas las tablas de la agenda viven en el schema public (LEVEL_EMPRESA).

RazonDetalle
Usuarios en publicLa tabla usuarios (view dblink) y grupos ya estan en public
Visibilidad cross-sucursalEventos con audiencia EMPRESA o SUCURSAL deben ser accesibles sin cross-schema queries
Agenda siempre oficialLa agenda no participa del patron dual-database; ignora el modo prueba
Acceso globalLa agenda es una feature de general, no pertenece a ningun schema de sucursal

Arquitectura de Capas

CapaArchivo (referencia)Responsabilidad
RouteRoutes/General/AgendaRoute.phpEndpoints REST de eventos
Controllercontroller/modulo-general/AgendaController.phpManejo HTTP, request/response
Serviceservice/General/AgendaService.phpLogica de negocio, resolucion de audiencia
DomainDomain/Agenda/Reglas: visibilidad, recurrencia, idempotencia
Modelmodels/modulo-general/EventoModel.phpAcceso a datos en schema public
DTOResources/General/EventoDTO.phpObjeto de transferencia de datos

Flujo de Peticion

Request
  --> AuthMiddleware (verifica JWT, extrae usuario)
  --> ConnectionMiddleware (configura conexion; schema public para agenda)
  --> AgendaRoute
  --> AgendaController
  --> AgendaService
       --> AudienciaService (resolucion de visibilidad)
       --> RecurrenciaService (generacion de instancias)
       --> EventoModel (acceso a datos)
  --> Response (JSON)

Endpoints API

GET /api/agenda/eventos

Lista los eventos visibles para el usuario autenticado, segun las reglas de audiencia.

Parametros de consulta (opcionales):

CampoTipoDescripcion
fecha_desdedateInicio del rango de fechas
fecha_hastadateFin del rango de fechas
tipostringFiltro por tipo: TAREA, AVISO, REUNION, SISTEMA
estadostringFiltro por estado: PENDIENTE, EN_PROGRESO, COMPLETADO, CANCELADO, VISTO
prioridadstringFiltro por prioridad: BAJA, MEDIA, ALTA, CRITICA
origenstringFiltro por origen: MANUAL, SISTEMA
modulo_origenstringFiltro por modulo de sistema (ctacte, crm, etc.)
etiquetastringFiltro por etiqueta
incluir_vistosbooleanIncluir eventos de sistema ya vistos (default: false)

Logica de visibilidad: el servicio resuelve la audiencia del usuario combinando:

  1. Eventos donde usuario_creador_id = usuario_actual
  2. Eventos donde existe una fila en evento_audiencia que incluye al usuario (ver algoritmo de resolucion)

GET /api/agenda/eventos/{id}

Retorna el detalle completo de un evento, incluyendo audiencia, adjuntos y comentarios. Solo retorna el evento si el usuario tiene visibilidad sobre el.


POST /api/agenda/eventos

Crea un evento nuevo. Para eventos de tipo REUNION, fecha_fin es obligatorio. Si recurrente = true, genera las instancias futuras segun la configuracion de recurrencia.

Body:

CampoTipoReqDescripcion
titulostringSiTitulo del evento
tipostringSiTAREA, AVISO, REUNION (nunca SISTEMA via este endpoint)
prioridadstringSiBAJA, MEDIA, ALTA, CRITICA
fecha_iniciodatetimeSiInicio
todo_el_diabooleanSiSi ocupa todo el dia
fecha_findatetimeCondObligatorio si tipo = REUNION
descripcionstringNoTexto libre
colorstringNoColor hex (#RRGGBB)
etiquetasarrayNoLista de strings
audienciaarrayNoLista de targets (ver estructura abajo)
recurrentebooleanNoSi el evento se repite
recurrenciaobjectCondObligatorio si recurrente = true
entidad_schemastringNoSchema de la entidad vinculada
entidad_tipostringNoTipo de entidad vinculada
entidad_idintNoID de la entidad vinculada

Estructura de audiencia[]:

CampoTipoDescripcion
tipostringUSUARIO, GRUPO, SUCURSAL, EMPRESA
ref_idintID de usuario o grupo (null para SUCURSAL/EMPRESA)
ref_schemastringSchema de sucursal como filtro adicional (opcional)

Estructura de recurrencia:

CampoTipoDescripcion
frecuenciastringDIARIA, SEMANAL, MENSUAL, ANUAL
intervalointCada N unidades (default: 1)
dias_semanaarrayEnteros 0-6 (0=Dom), solo para SEMANAL
fecha_findateHasta cuando se repite (null = indefinido)

PUT /api/agenda/eventos/{id}

Actualiza un evento existente. Solo el creador puede ejecutar esta accion. Si se modifican campos de recurrencia, se recalculan las instancias futuras a partir de hoy.


DELETE /api/agenda/eventos/{id}

Eliminacion logica (soft delete). Solo el creador puede eliminar. Marca eliminado = true y registra fecha_eliminacion.


POST /api/agenda/eventos/{id}/visto

Marca un evento de sistema como visto para el usuario autenticado. Crea un registro en evento_lectura con leido_en = NOW(). Solo aplicable a eventos con origen = SISTEMA.


POST /api/agenda/eventos/{id}/comentarios

Agrega un comentario al evento. Disponible para cualquier usuario con visibilidad sobre el evento.


POST /api/agenda/eventos/{id}/adjuntos

Sube un archivo adjunto al evento. Solo el creador puede agregar adjuntos.


POST /api/agenda/sistema/eventos

Uso exclusivo de modulos del sistema (no expuesto al usuario). Crea un evento de tipo SISTEMA.

CampoTipoReqDescripcion
titulostringSiTitulo descriptivo
modulo_origenstringSiIdentificador del modulo (ctacte, crm, etc.)
prioridadstringSiPrioridad del evento
accion_urlstringSiRuta de navegacion al origen
entidad_schemastringSiSchema de la entidad (nivel sucursal o public)
entidad_tipostringSiTipo de entidad
entidad_idintSiID de la entidad
audienciaarraySiTargets de visibilidad
descripcionstringNoDetalle adicional
etiquetasarrayNoTags para filtrado

Este endpoint aplica la regla de idempotencia: verifica si ya existe un evento activo (no visto) para la misma tripla (modulo_origen, entidad_schema, entidad_tipo, entidad_id). Si existe, no inserta uno nuevo.


Esquema de Base de Datos

Todas las tablas pertenecen al schema public.


Tabla: eventos

CampoTipoConstraintsDescripcion
idbigintPK, NOT NULLIdentificador unico
titulovarchar(255)NOT NULLTitulo del evento
tipovarchar(20)NOT NULLTAREA, AVISO, REUNION, SISTEMA
estadovarchar(20)NOT NULL, DEFAULT 'PENDIENTE'PENDIENTE, EN_PROGRESO, COMPLETADO, CANCELADO, VISTO
prioridadvarchar(20)NOT NULL, DEFAULT 'MEDIA'BAJA, MEDIA, ALTA, CRITICA
origenvarchar(10)NOT NULLMANUAL, SISTEMA
fecha_iniciotimestampNOT NULLFecha y hora de inicio
todo_el_diabooleanNOT NULL, DEFAULT falseSi ocupa todo el dia
fecha_fintimestampNULLFin del evento; obligatorio para tipo REUNION
descripciontextNULLTexto libre descriptivo
colorvarchar(7)NULLColor hex (#RRGGBB)
etiquetastext[]NULLTags libres como array de texto
schema_contextovarchar(50)NULLSchema de sucursal donde se creo el evento
modulo_origenvarchar(50)NULLModulo del sistema para eventos SISTEMA
accion_urlvarchar(500)NULLRuta del frontend al origen del evento
entidad_schemavarchar(50)NULLSchema de la entidad vinculada
entidad_tipovarchar(50)NULLTipo de entidad vinculada (FACTURA, CLIENTE, etc.)
entidad_idbigintNULLID de la entidad vinculada
recurrencia_idbigintFK evento_recurrencia.id, NULLConfig de recurrencia
evento_padre_idbigintFK eventos.id, NULLEvento padre (instancia de serie recurrente)
usuario_creador_idintNOT NULL, FK usuarios.idUsuario que creo el evento
fecha_creaciontimestampNOT NULL, DEFAULT NOW()Auditoria: creacion
fecha_modificaciontimestampNOT NULL, DEFAULT NOW()Auditoria: ultima modificacion
eliminadobooleanNOT NULL, DEFAULT falseSoft delete
fecha_eliminaciontimestampNULLTimestamp del borrado logico

Check constraints:

  • estado IN ('PENDIENTE','EN_PROGRESO','COMPLETADO','CANCELADO','VISTO')
  • tipo IN ('TAREA','AVISO','REUNION','SISTEMA')
  • prioridad IN ('BAJA','MEDIA','ALTA','CRITICA')
  • origen IN ('MANUAL','SISTEMA')
  • color ~* '^#[0-9A-F]{6}$' (cuando no es NULL)

Tabla: evento_audiencia

Cada fila representa un target de visibilidad para un evento. La union de todas las filas de un evento define quienes pueden verlo.

CampoTipoConstraintsDescripcion
idbigintPK, NOT NULLIdentificador unico
evento_idbigintNOT NULL, FK eventos.idEvento al que pertenece
tipovarchar(15)NOT NULLUSUARIO, GRUPO, SUCURSAL, EMPRESA
ref_idintNULLID de usuario o grupo; NULL para SUCURSAL y EMPRESA
ref_schemavarchar(50)NULLSchema de sucursal como filtro adicional

Nota sobre ref_schema: cuando se combina con tipo GRUPO o USUARIO, actua como filtro AND: el evento es visible para los miembros del grupo (o el usuario) que ademas pertenezcan a la sucursal indicada. La pertenencia a una sucursal se determina comparando ref_schema con el schema del usuario en el sistema central, nivelado al nivel sucursal.


Tabla: evento_recurrencia

CampoTipoConstraintsDescripcion
idbigintPK, NOT NULLIdentificador unico
frecuenciavarchar(10)NOT NULLDIARIA, SEMANAL, MENSUAL, ANUAL
intervalointNOT NULL, DEFAULT 1Repetir cada N unidades
dias_semanaint[]NULLPara SEMANAL: array de 0 (Dom) a 6 (Sab)
fecha_findateNULLHasta cuando se genera la serie; NULL = indefinido

Tabla: evento_adjunto

CampoTipoConstraintsDescripcion
idbigintPK, NOT NULLIdentificador unico
evento_idbigintNOT NULL, FK eventos.idEvento al que pertenece
nombrevarchar(255)NOT NULLNombre original del archivo
rutavarchar(500)NOT NULLRuta o clave de almacenamiento
tipo_mimevarchar(100)NOT NULLTipo MIME del archivo
fecha_creaciontimestampNOT NULL, DEFAULT NOW()Auditoria

Tabla: evento_comentario

CampoTipoConstraintsDescripcion
idbigintPK, NOT NULLIdentificador unico
evento_idbigintNOT NULL, FK eventos.idEvento al que pertenece
usuario_idintNOT NULL, FK usuarios.idAutor del comentario
textotextNOT NULLContenido del comentario
fecha_creaciontimestampNOT NULL, DEFAULT NOW()Auditoria
eliminadobooleanNOT NULL, DEFAULT falseSoft delete

Tabla: evento_lectura

Registra cuando un usuario marco como visto un evento de sistema. Una fila por par (evento, usuario).

CampoTipoConstraintsDescripcion
idbigintPK, NOT NULLIdentificador unico
evento_idbigintNOT NULL, FK eventos.idEvento leido
usuario_idintNOT NULL, FK usuarios.idUsuario que lo leyo
leido_entimestampNOT NULL, DEFAULT NOW()Timestamp de la lectura

Constraint de unicidad: UNIQUE (evento_id, usuario_id) — evita doble marcado.


Indexes

TablaColumnasTipoProposito
eventos(usuario_creador_id)B-treeConsultas por creador
eventos(fecha_inicio, eliminado)B-treeVista de calendario por rango de fechas
eventos(origen, estado, eliminado)B-treeFiltro de eventos activos de sistema
eventos(entidad_schema, entidad_tipo, entidad_id)B-treeIdempotencia de emisiones de sistema
evento_audiencia(evento_id)B-treeCarga de audiencia de un evento
evento_audiencia(tipo, ref_id)B-treeBusqueda inversa: eventos de un usuario o grupo
evento_audiencia(ref_schema)B-treeBusqueda por sucursal
evento_lectura(evento_id, usuario_id)B-tree UNIQUEVerificacion de lectura y constraint
evento_lectura(usuario_id)B-treeTodos los eventos leidos por un usuario

Diagramas

ERD — Modelo de entidades

mermaid
erDiagram
    eventos {
        bigint id PK
        varchar titulo
        varchar tipo
        varchar estado
        varchar prioridad
        varchar origen
        timestamp fecha_inicio
        boolean todo_el_dia
        timestamp fecha_fin
        text descripcion
        varchar color
        varchar schema_contexto
        varchar modulo_origen
        varchar accion_url
        varchar entidad_schema
        varchar entidad_tipo
        bigint entidad_id
        bigint recurrencia_id FK
        bigint evento_padre_id FK
        int usuario_creador_id FK
        timestamp fecha_creacion
        boolean eliminado
    }

    evento_audiencia {
        bigint id PK
        bigint evento_id FK
        varchar tipo
        int ref_id
        varchar ref_schema
    }

    evento_recurrencia {
        bigint id PK
        varchar frecuencia
        int intervalo
        int[] dias_semana
        date fecha_fin
    }

    evento_adjunto {
        bigint id PK
        bigint evento_id FK
        varchar nombre
        varchar ruta
        varchar tipo_mime
    }

    evento_comentario {
        bigint id PK
        bigint evento_id FK
        int usuario_id FK
        text texto
        timestamp fecha_creacion
    }

    evento_lectura {
        bigint id PK
        bigint evento_id FK
        int usuario_id FK
        timestamp leido_en
    }

    eventos ||--o{ evento_audiencia : "tiene audiencia"
    eventos ||--o| evento_recurrencia : "configura recurrencia"
    eventos ||--o{ evento_adjunto : "tiene adjuntos"
    eventos ||--o{ evento_comentario : "tiene comentarios"
    eventos ||--o{ evento_lectura : "registra lecturas"
    eventos }o--o| eventos : "instancia de (evento_padre_id)"

Diagrama de estados — Ciclo de vida del evento

mermaid
stateDiagram-v2
    [*] --> PENDIENTE : Creacion

    PENDIENTE --> EN_PROGRESO : Marcar en progreso (solo TAREA)
    PENDIENTE --> COMPLETADO : Completar (TAREA o AVISO)
    PENDIENTE --> CANCELADO : Cancelar (solo creador)

    EN_PROGRESO --> COMPLETADO : Completar
    EN_PROGRESO --> CANCELADO : Cancelar (solo creador)

    PENDIENTE --> VISTO : Marcar como visto (solo SISTEMA)

    COMPLETADO --> [*]
    CANCELADO --> [*]
    VISTO --> [*]

    note right of VISTO : Solo para origen=SISTEMA.<br/>Estado individual por usuario.<br/>Registra en evento_lectura.
    note right of COMPLETADO : El evento permanece visible<br/>con estado completado.<br/>No se elimina.

Flowchart — Algoritmo de resolucion de visibilidad

Determina si el usuario U puede ver el evento E.

mermaid
flowchart TD
    Start(["Usuario U consulta evento E"]) --> EsCreador{"U es el creador?"}

    EsCreador -->|Si| PuedeVer(["Puede ver"])
    EsCreador -->|No| EstaEliminado{"Evento eliminado?"}

    EstaEliminado -->|Si| NoPuedeVer(["No puede ver"])
    EstaEliminado -->|No| CargarAudiencia["Cargar filas de evento_audiencia"]

    CargarAudiencia --> IterarFilas{"Para cada fila de audiencia"}

    IterarFilas --> TipoEmpresa{"tipo = EMPRESA?"}
    TipoEmpresa -->|Si| PuedeVer

    TipoEmpresa -->|No| TipoSucursal{"tipo = SUCURSAL?"}
    TipoSucursal -->|Si| CheckSchema{"ref_schema =<br/>schema_sucursal(U)?"}
    CheckSchema -->|Si| PuedeVer
    CheckSchema -->|No| SiguienteFila["Siguiente fila"]

    TipoSucursal -->|No| TipoGrupo{"tipo = GRUPO?"}
    TipoGrupo -->|Si| CheckGrupo{"U es miembro<br/>del grupo ref_id?"}
    CheckGrupo -->|No| SiguienteFila
    CheckGrupo -->|Si| TieneRefSchema{"ref_schema<br/>definido?"}
    TieneRefSchema -->|No| PuedeVer
    TieneRefSchema -->|Si| CheckSchemaGrupo{"ref_schema =<br/>schema_sucursal(U)?"}
    CheckSchemaGrupo -->|Si| PuedeVer
    CheckSchemaGrupo -->|No| SiguienteFila

    TipoGrupo -->|No| TipoUsuario{"tipo = USUARIO?"}
    TipoUsuario -->|Si| CheckUsuario{"ref_id = U.id?"}
    CheckUsuario -->|No| SiguienteFila
    CheckUsuario -->|Si| TieneRefSchemaU{"ref_schema<br/>definido?"}
    TieneRefSchemaU -->|No| PuedeVer
    TieneRefSchemaU -->|Si| CheckSchemaUsuario{"ref_schema =<br/>schema_sucursal(U)?"}
    CheckSchemaUsuario -->|Si| PuedeVer
    CheckSchemaUsuario -->|No| SiguienteFila

    TipoUsuario -->|No| SiguienteFila
    SiguienteFila --> MasFilas{"Hay mas filas?"}
    MasFilas -->|Si| IterarFilas
    MasFilas -->|No| NoPuedeVer

Secuencia — Creacion de evento manual con recurrencia

mermaid
sequenceDiagram
    participant U as Usuario
    participant API as AgendaController
    participant S as AgendaService
    participant RS as RecurrenciaService
    participant M as EventoModel
    participant DB as public schema

    U->>API: POST /api/agenda/eventos
    API->>S: crearEvento(DTO, usuarioId)

    S->>S: validar tipo y campos requeridos
    S->>S: verificar fecha_fin si tipo=REUNION

    alt recurrente = true
        S->>RS: generarInstancias(recurrenciaConfig, fechaInicio)
        RS-->>S: lista de fechas de instancias
    end

    S->>M: insertarEvento(datos)
    M->>DB: INSERT INTO eventos
    DB-->>M: id del evento creado

    S->>M: insertarAudiencia(eventoId, audienciaDTO[])
    M->>DB: INSERT INTO evento_audiencia (N filas)

    alt recurrente = true
        loop Para cada instancia futura
            S->>M: insertarInstancia(fechaInstancia, eventoId)
            M->>DB: INSERT INTO eventos (evento_padre_id = eventoId)
        end
    end

    S-->>API: EventoDTO
    API-->>U: 201 Created

Secuencia — Emision de evento de sistema desde un modulo

mermaid
sequenceDiagram
    participant MOD as Modulo del sistema
    participant AS as AgendaService
    participant M as EventoModel
    participant AM as AudienciaModel
    participant DB as public schema

    MOD->>AS: emitirEventoSistema(payload)

    AS->>M: buscarEventoActivoSistema(modulo, entidadSchema, entidadTipo, entidadId)
    M->>DB: SELECT FROM eventos WHERE origen=SISTEMA AND eliminado=false...
    DB-->>M: resultado

    alt Evento activo ya existe (idempotencia)
        AS-->>MOD: EventoDTO existente (sin insertar)
    else No existe evento activo
        AS->>M: insertarEvento(datosEvento)
        M->>DB: INSERT INTO eventos (origen=SISTEMA)
        DB-->>M: id del evento creado

        AS->>AM: resolverAudiencia(audienciaConfig)
        AM->>DB: INSERT INTO evento_audiencia (N filas)

        AS-->>MOD: EventoDTO creado
    end

Secuencia — Marcado de lectura de evento de sistema

mermaid
sequenceDiagram
    participant U as Usuario
    participant API as AgendaController
    participant S as AgendaService
    participant M as EventoModel
    participant LM as LecturaModel
    participant DB as public schema

    U->>API: POST /api/agenda/eventos/{id}/visto
    API->>S: marcarComoVisto(eventoId, usuarioId)

    S->>M: obtenerEvento(eventoId)
    M->>DB: SELECT FROM eventos WHERE id = ?
    DB-->>M: evento

    alt Evento no existe o no visible para el usuario
        S-->>API: 404 Not Found
        API-->>U: 404
    else Evento no es de origen SISTEMA
        S-->>API: 422 Unprocessable
        API-->>U: 422 Solo eventos de sistema pueden marcarse como vistos
    else Evento de sistema, usuario tiene visibilidad
        S->>LM: registrarLectura(eventoId, usuarioId)
        LM->>DB: INSERT INTO evento_lectura ON CONFLICT DO NOTHING
        DB-->>LM: ok

        S-->>API: ok
        API-->>U: 200 OK
    end

Algoritmo de Generacion de Instancias (Recurrencia)

El RecurrenciaService genera las fechas de instancias futuras en memoria y delega la insercion al EventoModel.

FrecuenciaLogica de generacion
DIARIAfecha_inicio + (n * intervalo) dias, donde n = 1, 2, 3, ...
SEMANALSiguiente ocurrencia de los dias_semana seleccionados, avanzando intervalo semanas
MENSUALMismo dia del mes que fecha_inicio, avanzando intervalo meses
ANUALMismo dia y mes que fecha_inicio, avanzando intervalo años

Limites de generacion:

  • Si fecha_fin esta definida: se generan instancias hasta esa fecha inclusive
  • Si fecha_fin es NULL: se generan instancias para los proximos 2 anos como horizonte operativo; el scheduler puede extender el horizonte periodicamente

Resolucion del Schema de Sucursal de un Usuario

Para comparar ref_schema de la audiencia con el schema al que pertenece un usuario, el sistema usa la siguiente logica de normalizacion:

schema_asignado_usuario: "suc0001caja001"
  --> extraer nivel sucursal
  --> resultado: "suc0001"

schema_asignado_usuario: "suc0001"
  --> ya es nivel sucursal
  --> resultado: "suc0001"

schema_asignado_usuario: "public"
  --> empresa sin sucursales
  --> resultado: "public"

La funcion equivalente ya existe en el backend como ConnectionUtils::extractSucursalSchema().


Validaciones

Validacion estructural (Validator / Middleware)

CampoRegla
tituloRequerido, string, max 255 caracteres
tipoRequerido, enum: TAREA, AVISO, REUNION
prioridadRequerido, enum: BAJA, MEDIA, ALTA, CRITICA
fecha_inicioRequerido, datetime valido
todo_el_diaRequerido, boolean
colorOpcional, regex ^#[0-9A-Fa-f]{6}$
audiencia[].tipoEnum: USUARIO, GRUPO, SUCURSAL, EMPRESA
recurrencia.frecuenciaEnum: DIARIA, SEMANAL, MENSUAL, ANUAL
recurrencia.intervaloEntero positivo mayor a 0
recurrencia.dias_semana[]Enteros 0-6 (solo si frecuencia = SEMANAL)

Validacion de negocio (Service / Domain)

ReglaDetalle
Fecha de fin obligatoria para REUNIONSi tipo = REUNION y fecha_fin es NULL, rechazar con 422
Fecha de fin posterior a fecha de iniciofecha_fin > fecha_inicio cuando ambas estan presentes
Solo el creador edita y eliminaVerificar usuario_creador_id = usuario_actual; rechazar con 403 si no coincide
tipo = SISTEMA no permitido via POST manualEl endpoint POST /eventos no acepta tipo = SISTEMA; ese tipo es exclusivo del endpoint interno
Idempotencia de eventos de sistemaVerificar existencia de evento activo por (modulo_origen, entidad_schema, entidad_tipo, entidad_id) antes de insertar
Referencia polimórfica nunca a nivel cajaSi entidad_schema tiene formato sucXXXXcajaXXXX, normalizarlo a sucXXXX
marcado como visto solo para SISTEMASi origen != SISTEMA, rechazar la operacion con 422

Puntos de Integracion

ModuloIntegracionDescripcion
Usuarios (public)Lecturausuario_creador_id y usuario_id en comentarios/lecturas referencian la view usuarios
Grupos (public)Lecturaref_id en audiencia con tipo = GRUPO referencia la tabla grupos
CtaCteEmisorLlama al endpoint interno para emitir eventos de vencimiento
CRMEmisorLlama al endpoint interno para emitir recordatorios de seguimiento
MembresiasEmisorLlama al endpoint interno para emitir vencimientos de membresía
ComprasEmisorLlama al endpoint interno para emitir vencimientos de facturas de proveedor
StockEmisorLlama al endpoint interno para emitir alertas de stock minimo
TesoreriaEmisorLlama al endpoint interno para emitir vencimientos de cheques
ConnectionUtilsUtilextractSucursalSchema() para normalizar el schema de usuario al comparar con ref_schema

Consideraciones de Performance

CasoEstrategia
Carga del calendario por rango de fechasIndex compuesto (fecha_inicio, eliminado) cubre el filtro principal
Resolucion de visibilidad por usuarioJoin con evento_audiencia filtrado por tipo y ref_id; usar IN con los grupos del usuario
Eventos de sistema no vistosIndex (origen, estado, eliminado) para filtrar rapidamente eventos SISTEMA pendientes
Idempotencia en emision masivaIndex unico sobre (modulo_origen, entidad_schema, entidad_tipo, entidad_id)
Instancias de eventos recurrentes a futuroGeneracion por horizonte de 2 anos; evitar generar instancias infinitas en memoria

Seguridad y Auditoria

AspectoImplementacion
AccesoSolo usuarios autenticados (JWT). La agenda siempre usa la conexion oficial
VisibilidadEl servicio filtra activamente los eventos por audiencia; nunca retorna eventos fuera del alcance del usuario
ModificacionVerificacion de usuario_creador_id en toda operacion de escritura sobre eventos MANUAL
Soft deleteLos eventos no se eliminan fisicamente; eliminado = true con fecha_eliminacion
Auditoriafecha_creacion y fecha_modificacion en eventos; fecha_creacion en comentarios y adjuntos; leido_en en lecturas
Modo pruebaLa agenda no participa del patron dual-database; la conexion siempre apunta a bautista (oficial), aunque el contexto general del request este en modo prueba