# com.cdsoftware.appointment

Documentación técnica del plugin iDempiere 12 para gestión reutilizable de citas, servicios, empleados, horarios, disponibilidad, generación de slots y validación centralizada desde UI o API.

# Visión General

Resumen funcional y arquitectura general del plugin de citas.

# Resumen del Plugin

El plugin `com.cdsoftware.appointment` agrega a iDempiere 12 un módulo reutilizable para gestionar citas, servicios, empleados, horarios, días no disponibles y disponibilidad de agenda.

> **Idea clave:** la disponibilidad no depende solo de la interfaz. La validación queda centralizada en backend para proteger tanto las citas creadas desde ventanas iDempiere como las creadas desde REST/API.

## Objetivo

El objetivo es permitir que una organización configure servicios agendables, defina qué empleados pueden atender cada servicio, establezca horarios de atención y bloquee días o rangos no disponibles. Con esa información, el sistema genera slots disponibles y valida cada cita antes de guardarla.

## Alcance funcional

<table id="bkmrk-%C3%81reaqu%C3%A9-cubreconfigu" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Área</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Qué cubre</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">**Configuración**</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Parámetros generales por organización, límites de anticipación, reglas de aprobación e intervalos de slots.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">**Servicios**</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Servicios agendables con precio, duración, intervalo, capacidad y requerimiento de aprobación.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">**Empleados**</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Personas o usuarios disponibles para atender citas, vinculados a terceros y opcionalmente a usuarios iDempiere.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">**Disponibilidad**</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Horarios semanales, días libres de empleado, días libres de organización y validación de conflictos.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">**Citas**</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Registro principal de la cita con cliente, empleado, servicio, fecha, hora, duración, precio, estado y slot seleccionado.</td></tr></tbody></table>

## Principios de diseño

- El plugin es genérico y no depende del proyecto IEA.
- La creación desde REST/API usa la tabla estándar `CDS_Appointment`.
- Las reglas críticas se validan en eventos de modelo, no solo en callouts.
- Los modelos generados `I_CDS_*` y `X_CDS_*` son la fuente de constantes de tabla, columnas y valores.
- El campo `R_Request_ID` puede existir como referencia manual, pero el código del plugin no depende de solicitudes.

# Arquitectura y Componentes

La arquitectura del plugin separa la definición de modelos, la disponibilidad, la experiencia de ventana iDempiere y la validación obligatoria antes de guardar citas.

## Componentes principales

<table id="bkmrk-componenteresponsabi" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Componente</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Responsabilidad</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;">`AppointmentAvailabilityEngine`</td><td style="border:1px solid #d8dee9;padding:12px;">Centraliza cálculo de disponibilidad, validación de horarios, días libres, conflictos y generación de slots.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`AppointmentModelValidator`</td><td style="border:1px solid #d8dee9;padding:12px;">Valida citas antes de crear o modificar registros, incluyendo consistencia del slot y conflictos de agenda.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CalloutAppointment`</td><td style="border:1px solid #d8dee9;padding:12px;">Apoya la captura en ventana iDempiere copiando precio, duración, empleado y horas desde servicio o slot.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`GenerateAppointmentSlots`</td><td style="border:1px solid #d8dee9;padding:12px;">Genera registros `CDS_AppointmentSlot` para un empleado, servicio y fecha específicos.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`ReopenAppointment`</td><td style="border:1px solid #d8dee9;padding:12px;">Permite reabrir una cita procesada de forma controlada.</td></tr></tbody></table>

## Flujo general

1. El usuario configura servicios, empleados, capacidades, horarios y días no disponibles.
2. Desde una cita se selecciona empleado, servicio y fecha.
3. El proceso de generación crea slots disponibles para esa combinación.
4. El usuario selecciona un slot y el callout copia datos a la cita.
5. Al guardar, el evento valida disponibilidad, consistencia y conflictos.
6. Los clientes REST/API pueden crear la cita directamente y reciben la misma validación por evento.

<p class="callout info">**Nota:** los callouts mejoran la experiencia de captura, pero no son la capa de seguridad. La regla final vive en `AppointmentModelValidator`.</p>

# Flujo de Configuración y Cita

Esta página describe el flujo objetivo para preparar una agenda y poder crear citas válidas. El flujo parte de una configuración general, continúa con servicios y empleados, y termina con la creación de una cita validada por backend.

<p class="callout warning">**Importante:** parte de este flujo representa el comportamiento objetivo. Algunas reglas todavía deben implementarse en el modelo y en `AppointmentModelValidator` para que sean obligatorias tanto desde la UI como desde REST/API.</p>

## Flujo objetivo

<div drawio-diagram="178"><img src="https://docs.primware.net/uploads/images/drawio/2026-05/yoFttEn6HSRcxnol-drawing-1-1779470586.png" alt=""/></div>

## Secuencia funcional

1. Crear una configuración de agenda por organización o por alcance operativo.
2. Definir reglas generales como anticipación mínima, anticipación máxima, regla de asignación y política de aprobación.
3. Definir el horario macro de la agenda. Por ejemplo: lunes a viernes de 8:00 a 17:00.
4. Relacionar cada servicio con una sola configuración de agenda.
5. Configurar cada servicio con duración, precio, intervalo de slot y si requiere aprobación.
6. Configurar empleados y definir qué servicios puede atender cada uno.
7. Definir horario laboral y días libres del empleado.
8. Crear la cita seleccionando empleado, servicio, fecha y slot.
9. Validar primero la configuración de agenda y luego las reglas del empleado.
10. Guardar la cita como pendiente o aprobada según las reglas de aprobación.

## Jerarquía de disponibilidad esperada

<table id="bkmrk-nivelreglaprioridadc" style="width: 100%; border-collapse: collapse; margin: 12px 0 20px 0;"><thead><tr><th style="border: 1px solid #d8dee9; padding: 12px; background: #eef2f7; text-align: left;">Nivel</th><th style="border: 1px solid #d8dee9; padding: 12px; background: #eef2f7; text-align: left;">Regla</th><th style="border: 1px solid #d8dee9; padding: 12px; background: #eef2f7; text-align: left;">Prioridad</th></tr></thead><tbody><tr><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">**Configuración de agenda**</td><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">Define el marco general de operación: anticipación, días permitidos, horarios macro y aprobación.</td><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">Debe estar por encima del empleado. Si la agenda general está cerrada, ningún empleado puede abrir disponibilidad.</td></tr><tr><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">**Servicio**</td><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">Define duración, precio, intervalo y si requiere aprobación.</td><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">Debe resolver qué configuración aplica.</td></tr><tr><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">**Empleado**</td><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">Define servicios que puede atender, horario propio y bloqueos personales.</td><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">Solo puede reducir o especializar la disponibilidad de la configuración, no ampliarla.</td></tr><tr><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">**Cita**</td><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">Consume empleado, servicio, configuración, slot y reglas de conflicto.</td><td style="border: 1px solid #d8dee9; padding: 12px; vertical-align: top;">Debe ser validada por evento antes de guardarse.</td></tr></tbody></table>

## Reglas pendientes para completar el flujo

- Relacionar cada servicio con una configuración de agenda.
- Validar que un servicio no pertenezca a más de una configuración.
- Agregar horario macro de configuración, similar al horario del empleado.
- Validar anticipación mínima y máxima desde la configuración.
- Aplicar la política de aprobación automática o forzada en `BeforeNew`.
- Hacer que los días libres de organización puedan aplicar por configuración cuando corresponda.

# Modelo de Datos

Tablas, modelos generados y relaciones principales del módulo.

# Tablas y Modelo de Datos

El 2Pack del plugin define las tablas del módulo y genera modelos Java `I_CDS_*` y `X_CDS_*`. El código usa esos modelos como fuente de nombres de tabla, columnas y valores de referencia.

## Tablas principales

<table id="bkmrk-tablausocds_appointm" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Tabla</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Uso</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentConfig`</td><td style="border:1px solid #d8dee9;padding:12px;">Configuración organizacional de agenda, reglas de asignación, aprobación, límites e intervalos.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentService`</td><td style="border:1px solid #d8dee9;padding:12px;">Servicios agendables con precio, duración, intervalo de slot, aprobación requerida y capacidad.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentEmployee`</td><td style="border:1px solid #d8dee9;padding:12px;">Empleado disponible para citas, vinculado a `C_BPartner` y opcionalmente a `AD_User`.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_EmployeeService`</td><td style="border:1px solid #d8dee9;padding:12px;">Relación empleado-servicio con posibles overrides de precio y duración.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_EmployeeScheduleLine`</td><td style="border:1px solid #d8dee9;padding:12px;">Bloques semanales de disponibilidad del empleado por día y rango horario.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_EmployeeDayOff`</td><td style="border:1px solid #d8dee9;padding:12px;">Bloqueos de empleado por día completo o rango horario, incluyendo recurrencia anual.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_OrgDayOff`</td><td style="border:1px solid #d8dee9;padding:12px;">Bloqueos de organización por día completo o rango horario. Incluye `CDS_AppointmentConfig_ID`, aunque el motor actual todavía no filtra por configuración.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentSlot`</td><td style="border:1px solid #d8dee9;padding:12px;">Slots generados para selección desde la ventana iDempiere y consulta desde API.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_Appointment`</td><td style="border:1px solid #d8dee9;padding:12px;">Registro principal de cita con cliente, empleado, servicio, fecha, hora, precio, duración, estado y slot.</td></tr></tbody></table>

## Relaciones clave actuales

- `CDS_Appointment` referencia cliente, empleado, servicio y opcionalmente slot.
- `CDS_AppointmentSlot` se genera para un empleado, servicio, fecha y rango horario.
- `CDS_EmployeeService` determina si un empleado puede prestar un servicio.
- `CDS_EmployeeScheduleLine` define cuándo puede atender el empleado.
- `CDS_EmployeeDayOff` y `CDS_OrgDayOff` bloquean disponibilidad.

## Tabla `CDS_AppointmentSlot`

`CDS_AppointmentSlot` almacena horarios generados por usuario para que la UI o un cliente API pueda listar opciones disponibles después de ejecutar `GenerateAppointmentSlots`.

<table id="bkmrk-columnausofiltro-api" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Columna</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Uso</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Filtro API recomendado</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentSlot_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Identificador del slot seleccionado.</td><td style="border:1px solid #d8dee9;padding:12px;">Usar al copiar el slot hacia `CDS_Appointment.CDS_AppointmentSlot_ID`.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`AD_Org_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Organización del slot.</td><td style="border:1px solid #d8dee9;padding:12px;">Filtrar por organización activa del usuario.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`AD_User_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Usuario que generó el slot.</td><td style="border:1px solid #d8dee9;padding:12px;">Filtrar por usuario actual para no mezclar slots temporales de otros usuarios.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_Appointment_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Cita desde la cual se generó el slot, si aplica.</td><td style="border:1px solid #d8dee9;padding:12px;">Filtrar cuando los slots se generaron desde una cita específica.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentEmployee_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Empleado disponible para atender el slot.</td><td style="border:1px solid #d8dee9;padding:12px;">Filtrar por empleado seleccionado.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentService_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Servicio del slot.</td><td style="border:1px solid #d8dee9;padding:12px;">Filtrar por servicio seleccionado.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`SlotDate`</td><td style="border:1px solid #d8dee9;padding:12px;">Fecha del slot normalizada al inicio del día.</td><td style="border:1px solid #d8dee9;padding:12px;">Filtrar por fecha de la cita.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`StartDateTime`</td><td style="border:1px solid #d8dee9;padding:12px;">Inicio exacto del horario disponible.</td><td style="border:1px solid #d8dee9;padding:12px;">Ordenar ascendente o filtrar por rango horario.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`EndDateTime`</td><td style="border:1px solid #d8dee9;padding:12px;">Fin exacto del horario disponible.</td><td style="border:1px solid #d8dee9;padding:12px;">Usar junto con `StartDateTime` para mostrar rango.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`DisplayName`</td><td style="border:1px solid #d8dee9;padding:12px;">Etiqueta legible del horario.</td><td style="border:1px solid #d8dee9;padding:12px;">Mostrar como texto de selección.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`IsAvailable`</td><td style="border:1px solid #d8dee9;padding:12px;">Indica si el slot sigue disponible.</td><td style="border:1px solid #d8dee9;padding:12px;">Filtrar `IsAvailable='Y'`.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`IsActive`</td><td style="border:1px solid #d8dee9;padding:12px;">Control estándar de registro activo.</td><td style="border:1px solid #d8dee9;padding:12px;">Filtrar `IsActive='Y'`.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`AvailableEmployees`</td><td style="border:1px solid #d8dee9;padding:12px;">Cantidad de empleados disponibles para el rango. En el flujo actual por empleado suele quedar en `1`.</td><td style="border:1px solid #d8dee9;padding:12px;">Usar solo como referencia informativa.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`Price`</td><td style="border:1px solid #d8dee9;padding:12px;">Precio efectivo del slot.</td><td style="border:1px solid #d8dee9;padding:12px;">Copiar a la cita si se selecciona el slot.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`DurationMinutes`</td><td style="border:1px solid #d8dee9;padding:12px;">Duración efectiva del slot.</td><td style="border:1px solid #d8dee9;padding:12px;">Copiar a la cita si se selecciona el slot.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`AssignedByRule`</td><td style="border:1px solid #d8dee9;padding:12px;">Indica si el empleado fue asignado por regla automática.</td><td style="border:1px solid #d8dee9;padding:12px;">Actualmente informativo en el flujo por empleado.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`AssignmentRuleUsed`</td><td style="border:1px solid #d8dee9;padding:12px;">Regla de asignación usada al generar el slot.</td><td style="border:1px solid #d8dee9;padding:12px;">Usar para auditoría o depuración.</td></tr></tbody></table>

<p class="callout info">**Consulta típica API:** después de generar slots, listar `CDS_AppointmentSlot` filtrando por `AD_User_ID`, `CDS_AppointmentEmployee_ID`, `CDS_AppointmentService_ID`, `SlotDate`, `IsActive='Y'` e `IsAvailable='Y'`, ordenando por `StartDateTime`.</p>

## Relación configuración-servicio pendiente

El flujo objetivo requiere que cada servicio resuelva una configuración de agenda. Esto permite saber qué reglas de anticipación, horarios macro, días cerrados y aprobación aplican cuando se crea una cita.

<table id="bkmrk-necesidadopci%C3%B3n-reco" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Necesidad</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Opción recomendada</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Validación requerida</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Servicio pertenece a una configuración</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Agregar `CDS_AppointmentConfig_ID` en `CDS_AppointmentService`.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">El servicio debe tener una configuración activa cuando se use para generar slots o crear citas.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Un servicio no puede tener reglas ambiguas</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Si se usa tabla intermedia, imponer unicidad por servicio activo.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Bloquear más de una configuración activa por servicio.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Días libres por configuración</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Usar el campo existente `CDS_AppointmentConfig_ID` de `CDS_OrgDayOff`.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">El motor debe filtrar bloqueos por organización y por configuración del servicio.</td></tr></tbody></table>

## Horario macro de configuración pendiente

Actualmente solo existe horario de empleado. Para cumplir el flujo objetivo se recomienda agregar una tabla de horario macro, por ejemplo `CDS_AppointmentConfigScheduleLine`, con una estructura similar a `CDS_EmployeeScheduleLine`.

<table id="bkmrk-campo-sugeridousocds" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Campo sugerido</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Uso</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentConfig_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Configuración de agenda a la que pertenece el horario macro.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`WeekDay`</td><td style="border:1px solid #d8dee9;padding:12px;">Día de la semana permitido.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`StartTime` / `EndTime`</td><td style="border:1px solid #d8dee9;padding:12px;">Rango horario general de atención.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`ValidFrom` / `ValidTo`</td><td style="border:1px solid #d8dee9;padding:12px;">Vigencia opcional del horario.</td></tr></tbody></table>

<p class="callout warning">**Importante:** si el diccionario cambia, se deben regenerar los modelos antes de ajustar el código Java.</p>

# Lógica Backend

Motor de disponibilidad, callouts, procesos y validaciones por evento.

# Motor de Disponibilidad

`AppointmentAvailabilityEngine` concentra la lógica que decide si una cita o un slot es válido para un empleado, servicio y fecha determinados.

## Responsabilidades actuales

- Cargar una configuración activa por organización con fallback a organización `0`.
- Leer duración, precio e intervalo del servicio.
- Aplicar overrides de precio y duración por empleado-servicio.
- Validar que el empleado pueda prestar el servicio.
- Validar días libres de la organización.
- Validar días libres del empleado.
- Validar cobertura del horario semanal del empleado.
- Detectar conflictos con citas existentes pendientes o aprobadas.
- Aplicar límites diarios por empleado cuando correspondan.
- Crear slots persistentes para la UI de iDempiere.

## Regla de conflicto

Una cita se considera en conflicto cuando otra cita activa del mismo empleado se cruza con el rango nuevo y tiene estado pendiente o aprobado.

```
existing.StartDateTime < newEnd
AND existing.EndDateTime > newStart
AND existing.Status IN ('PE', 'AP')
AND existing.IsActive = 'Y'
```

## Generación de slots

La generación actual es específica por empleado. El proceso no genera para todos los empleados ni autoasigna el recurso; parte de una selección explícita de empleado, servicio y fecha.

<p class="callout info">**Nota:** esta decisión hace que el flujo de UI sea más claro: primero se elige quién atiende, luego qué servicio presta, después se generan horarios disponibles.</p>

## Reglas actuales vs reglas esperadas

<table id="bkmrk-reglaestado-actualco" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Regla</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Estado actual</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Comportamiento esperado</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Empleado presta servicio</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Implementado con `CDS_EmployeeService`.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Se mantiene como regla obligatoria.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Día libre de organización</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Implementado por organización. Aunque `CDS_OrgDayOff` tiene `CDS_AppointmentConfig_ID`, el motor actual no filtra por configuración.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Debe filtrar por configuración cuando el servicio resuelva una configuración específica.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Día libre de empleado</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Implementado con `CDS_EmployeeDayOff`.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Se mantiene como bloqueo específico del empleado.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Horario del empleado</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Implementado con `CDS_EmployeeScheduleLine`.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Debe validarse después del horario macro de la configuración.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Horario macro de agenda</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Pendiente. No existe aún una tabla equivalente a horario de configuración.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Debe definir los días y rangos generales permitidos, por encima del horario del empleado.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Anticipación mínima</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Pendiente. Existe `MinAdvanceHours`, pero no se valida todavía.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Debe bloquear citas creadas con menos horas de anticipación que las configuradas.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Anticipación máxima</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Pendiente. Existe `MaxAdvanceDays`, pero no se valida todavía.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Debe bloquear citas demasiado futuras.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Conflictos con citas existentes</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Implementado para estados `Pending` y `Approved`.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Se mantiene como validación obligatoria.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Aprobación automática o forzada</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Pendiente. Existen campos de configuración y servicio, pero no se aplican en el evento.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Debe resolverse en `BeforeNew` usando configuración y servicio.</td></tr></tbody></table>

## Roadmap del motor

- Resolver la configuración desde el servicio seleccionado, no solo desde la organización.
- Agregar validación de horario macro de configuración antes del horario del empleado.
- Aplicar `MinAdvanceHours` y `MaxAdvanceDays` al generar slots y al guardar citas.
- Filtrar días libres de organización por configuración cuando exista relación.
- Aplicar reglas de aprobación en el evento antes de validar el estado final de la cita.

# Callouts y Flujo de UI

`CalloutAppointment` ayuda al usuario a capturar una cita desde iDempiere sin reemplazar la validación final del backend.

## Comportamiento de callouts

<table id="bkmrk-campocomportamientoc" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Campo</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Comportamiento</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentService_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Copia precio y duración efectivos. Si existe empleado seleccionado, usa override de `CDS_EmployeeService`. Limpia slot y horas, pero no limpia empleado.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`AppointmentDate`</td><td style="border:1px solid #d8dee9;padding:12px;">Limpia slot, hora inicial y hora final. No limpia empleado ni servicio.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentSlot_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Copia empleado, hora inicial, hora final, precio y duración desde el slot seleccionado.</td></tr></tbody></table>

## Flujo recomendado en ventana

1. Seleccionar el empleado que atenderá la cita.
2. Seleccionar el servicio filtrado para ese empleado.
3. Seleccionar la fecha de la cita.
4. Ejecutar el proceso de generación de slots.
5. Seleccionar el slot generado.
6. Guardar la cita.

<p class="callout warning">**Importante:** si un usuario modifica manualmente horas, precio o duración, el evento de modelo vuelve a validar la consistencia antes de guardar.</p>

# Procesos y Eventos

El plugin mantiene pocos procesos activos y concentra las reglas críticas en eventos de modelo.

## Procesos activos

<table id="bkmrk-procesousogenerateap" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Proceso</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Uso</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;">`GenerateAppointmentSlots`</td><td style="border:1px solid #d8dee9;padding:12px;">Genera registros `CDS_AppointmentSlot` para el empleado, servicio y fecha seleccionados. Puede resolver valores desde la cita actual cuando se ejecuta desde la ventana.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`ReopenAppointment`</td><td style="border:1px solid #d8dee9;padding:12px;">Reabre una cita procesada cambiándola a pendiente y limpiando `Processed` mediante un contexto controlado.</td></tr></tbody></table>

## Proceso `GenerateAppointmentSlots`

Este proceso se usa para crear horarios disponibles persistentes que luego pueden consultarse desde la ventana iDempiere o desde la API leyendo la tabla `CDS_AppointmentSlot`.

<table id="bkmrk-par%C3%A1metrorequeridous" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Parámetro</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Requerido</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Uso</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;">`AD_Org_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Sí, directo o resuelto desde la cita</td><td style="border:1px solid #d8dee9;padding:12px;">Organización para cargar configuración y validar disponibilidad.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentEmployee_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Sí</td><td style="border:1px solid #d8dee9;padding:12px;">Empleado para el cual se generan slots.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentService_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Sí</td><td style="border:1px solid #d8dee9;padding:12px;">Servicio que define duración, precio e intervalo del slot.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`AppointmentDate`</td><td style="border:1px solid #d8dee9;padding:12px;">Sí</td><td style="border:1px solid #d8dee9;padding:12px;">Fecha para la cual se generan horarios disponibles.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_Appointment_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Opcional</td><td style="border:1px solid #d8dee9;padding:12px;">Si se ejecuta desde una cita, permite resolver organización, empleado, servicio y fecha desde el registro actual.</td></tr></tbody></table>

Antes de generar nuevos slots, el proceso desactiva slots previos activos del usuario actual. Si hay una cita, desactiva los slots asociados a esa cita. Si no hay cita, desactiva slots del mismo usuario, empleado, servicio y fecha.

<p class="callout info">**Uso recomendado:** desde la ventana de cita se selecciona empleado, servicio y fecha; luego se ejecuta `GenerateAppointmentSlots`; finalmente se selecciona un registro de `CDS_AppointmentSlot`.</p>

## Proceso `ReopenAppointment`

Este proceso reabre una cita procesada para permitir correcciones administrativas controladas.

<table id="bkmrk-par%C3%A1metrorequeridous-1" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Parámetro</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Requerido</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Uso</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_Appointment_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Sí, directo o resuelto desde el registro actual</td><td style="border:1px solid #d8dee9;padding:12px;">Cita procesada que se desea reabrir.</td></tr></tbody></table>

Si la cita ya está abierta, el proceso informa que no requiere reapertura. Si está procesada, activa un contexto temporal `#CDS_ReopenAppointment`, coloca `Processed=N` y cambia el estado a `Pending`.

## Validación por evento actual

`AppointmentModelValidator` se ejecuta antes de crear o cambiar una cita. Para citas pendientes o aprobadas valida los datos mínimos y la disponibilidad real.

- Cliente requerido.
- Empleado requerido.
- Servicio requerido.
- Fecha de cita requerida.
- Hora inicial y final requeridas.
- Hora inicial menor que hora final.
- Fecha de cita igual al día de la hora inicial.
- Duración mayor que cero.
- Precio no negativo.
- Empleado habilitado para prestar el servicio.
- Slot consistente con empleado, servicio, horas, duración y precio.
- Sin bloqueo por día libre de organización o empleado.
- Horario cubierto por agenda del empleado.
- Sin conflicto con otra cita activa pendiente o aprobada.

## Estados y procesamiento actual

- Si el estado viene vacío, se asigna `Draft`.
- Los estados finales marcan `Processed = true`.
- Los estados no finales limpian `Processed`.
- Una cita procesada no se puede modificar salvo mediante reapertura controlada.

## Roadmap de validaciones en eventos

Las siguientes reglas deben agregarse al evento para que el comportamiento sea consistente desde ventanas iDempiere y desde REST/API.

<table id="bkmrk-validaci%C3%B3n-pendiente" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Validación pendiente</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Momento sugerido</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Regla esperada</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Resolver configuración desde servicio</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">`BeforeNew` y `BeforeChange`</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">El servicio debe indicar qué configuración de agenda aplica. La cita usa esa configuración para anticipación, horario macro y aprobación.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Servicio con una sola configuración</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Al guardar servicio o relación configuración-servicio</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Un servicio no debe estar asociado a más de una configuración activa.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Anticipación mínima</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">`BeforeNew` y generación de slots</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Si `StartDateTime` está antes de ahora + `MinAdvanceHours`, la cita o slot debe bloquearse.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Anticipación máxima</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">`BeforeNew` y generación de slots</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Si `StartDateTime` supera hoy + `MaxAdvanceDays`, la cita o slot debe bloquearse.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Horario macro de configuración</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">`BeforeNew`, `BeforeChange` y generación de slots</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">El horario de agenda general debe contener el rango solicitado antes de evaluar el horario del empleado.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Forzar aprobación</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">`BeforeNew`</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Si la configuración tiene `ForceApproval=Y`, la cita debe quedar pendiente aunque el servicio permita aprobación automática.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Autoaprobar si el servicio lo permite</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">`BeforeNew`</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Si `AutoApproveIfServiceAllows=Y` y el servicio no requiere aprobación, la cita puede quedar `Approved`.</td></tr></tbody></table>

## Roadmap de workflows y correos

Además de las validaciones, falta definir workflows y plantillas de correo para notificar al cliente cuando cambie el estado de una cita.

<table id="bkmrk-flujo-pendientedispa" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Flujo pendiente</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Disparo sugerido</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Resultado esperado</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Notificar cita aprobada</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Cambio de estado a `Approved`.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Enviar correo al cliente con fecha, hora, servicio y empleado.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Notificar cita cancelada</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Cambio de estado a `Cancelled`.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Enviar correo al cliente con el motivo de cancelación cuando exista.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Notificar reprogramación</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Cambio de fecha, hora o empleado.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Enviar correo con el nuevo horario confirmado.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Recordatorio de cita</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Proceso programado antes de `StartDateTime`.</td><td style="border:1px solid #d8dee9;padding:12px;vertical-align:top;">Enviar recordatorio al cliente según configuración.</td></tr></tbody></table>

También falta crear las plantillas de correo, definir destinatarios, configurar remitente, parametrizar cuándo enviar cada notificación y evitar reenvíos duplicados con campos de control si aplica.

## Orden recomendado de validación

1. Resolver servicio, empleado y organización.
2. Resolver configuración de agenda desde el servicio.
3. Aplicar reglas de anticipación mínima y máxima.
4. Validar horario macro de configuración.
5. Validar días libres de organización vinculados a la configuración.
6. Validar relación empleado-servicio.
7. Validar horario y días libres del empleado.
8. Validar slot seleccionado.
9. Validar conflictos con citas existentes.
10. Resolver estado de aprobación: pendiente o aprobada.

# Operación e Integración

Uso desde iDempiere, REST/API, instalación y 2Pack.

# Uso desde REST/API

Los clientes externos pueden crear citas directamente en `CDS_Appointment` mediante la API REST estándar de iDempiere. No se requiere un proceso custom para crear la cita.

> **Idea clave:** REST/API y ventana iDempiere comparten la misma seguridad funcional porque el evento de modelo valida el registro antes de guardarlo.

## Flujo recomendado

1. Consultar servicios activos en `CDS_AppointmentService`.
2. Consultar empleados activos y sus relaciones en `CDS_EmployeeService`.
3. Consultar o calcular disponibilidad usando la misma información de horarios y días libres.
4. Crear un registro `CDS_Appointment` con cliente, empleado, servicio, fecha, hora inicial, hora final, precio, duración y estado.
5. Dejar que `AppointmentModelValidator` valide consistencia, disponibilidad y conflictos.

## Campos importantes

<table id="bkmrk-campousoc_bpartner_i" style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Campo</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Uso</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;">`C_BPartner_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Cliente o tercero de la cita.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentEmployee_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Empleado que atiende.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`CDS_AppointmentService_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Servicio solicitado.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`AppointmentDate`</td><td style="border:1px solid #d8dee9;padding:12px;">Día operativo de la cita.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`StartDateTime` / `EndDateTime`</td><td style="border:1px solid #d8dee9;padding:12px;">Rango exacto de atención.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`Price` / `DurationMinutes`</td><td style="border:1px solid #d8dee9;padding:12px;">Valores efectivos de la cita, validados contra servicio/slot cuando aplique.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`R_Request_ID`</td><td style="border:1px solid #d8dee9;padding:12px;">Referencia opcional/manual si una implementación quiere vincular una solicitud. El código del plugin no depende de este campo.</td></tr></tbody></table>

# Instalación y 2Pack

La instalación requiere desplegar el bundle OSGi del plugin y aplicar el 2Pack correspondiente en iDempiere 12.

## 2Packs incluidos

<table id="bkmrk-archivouso2pack_1.0." style="width:100%;border-collapse:collapse;margin:12px 0 20px 0;"><thead><tr><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Archivo</th><th style="border:1px solid #d8dee9;padding:12px;background:#eef2f7;text-align:left;">Uso</th></tr></thead><tbody><tr><td style="border:1px solid #d8dee9;padding:12px;">`2Pack_1.0.0.zip`</td><td style="border:1px solid #d8dee9;padding:12px;">Paquete inicial del diccionario de aplicación.</td></tr><tr><td style="border:1px solid #d8dee9;padding:12px;">`2Pack_1.0.1.zip`</td><td style="border:1px solid #d8dee9;padding:12px;">Paquete actualizado con traducciones `es_CO` para campos, elementos, menús, ventanas, pestañas, procesos, parámetros, referencias y valores de lista.</td></tr></tbody></table>

## Pasos de instalación

1. Compilar y desplegar el bundle `com.cdsoftware.appointment` en iDempiere 12.
2. Aplicar el 2Pack más reciente desde `com.cdsoftware.appointment/META-INF`.
3. Reiniciar o refrescar iDempiere para activar servicios OSGi, factories, anotaciones, callouts, procesos y eventos.
4. Configurar `CDS_AppointmentConfig` para la organización.
5. Crear servicios en `CDS_AppointmentService`.
6. Crear empleados en `CDS_AppointmentEmployee`.
7. Relacionar empleados con servicios en `CDS_EmployeeService`.
8. Configurar horarios semanales en `CDS_EmployeeScheduleLine`.
9. Configurar días no disponibles en `CDS_OrgDayOff` o `CDS_EmployeeDayOff`.
10. Probar la generación de slots y creación de citas desde la ventana.

<p class="callout warning">**Importante:** después de aplicar cambios de diccionario que modifiquen columnas o referencias, se deben regenerar modelos y revisar que el código siga usando las constantes generadas.</p>