Skip to content

Plataforma técnica · Orchestrator

Permission Service

Remuneraciones Permisos Licencias

PermissionService administra solicitudes de permisos, licencias médicas y otras ausencias justificadas. Cada solicitud tiene un tipo (que define la regla de pago), un estado en máquina de transiciones, flags de comportamiento (goce de sueldo, parcialidad por horas, recuperable, descuenta vacaciones) y respaldo documental opcional.

El service aplica reglas de coherencia importantes: si la solicitud es parcial, exige hora_desde y hora_hasta con hora_hasta > hora_desde; si no es parcial, fuerza las horas a null para evitar inconsistencias. Si es recuperable, exige que la fecha_recuperacion sea posterior al fin del permiso.

  • Directoryorchestrator/src/domain/permissions/
    • PermissionService.ts
    • PermissionRepository.ts
    • types.ts

Tabla: remuneraciones.permisos. Vista enriquecida usa join contra empleados y departamentos.

MétodoFirmaResultado
getById(ctx, id) => Promise<ServiceResult<PermissionRow>>Devuelve fila enriquecida o notFound("Permiso", id).
list(ctx, filter: PermissionListFilter) => Promise<ServiceResult<...>>scope === "list" usa findAll(filter) con totales y summary; sin scope devuelve listRecent(pool).
create(ctx, data: PermissionInput) => Promise<ServiceResult<void>>Aplica defaults y reglas de parcialidad.
update(ctx, id, data: PermissionUpdateInput) => Promise<ServiceResult<void>>Update parcial con merge contra el estado actual.
delete(ctx, id) => Promise<ServiceResult<void>>notFound("Permiso", id) si no afecta filas.
type PermissionStatus =
| "SOLICITADO" | "APROBADO" | "RECHAZADO" | "CANCELADO" | "USADO";
interface PermissionInput {
empleado_id: string; // obligatorio
fecha_desde: string; // obligatorio (YYYY-MM-DD)
fecha_hasta: string; // obligatorio
tipo_permiso: string; // obligatorio (catálogo libre: MEDICO, MATRIMONIO, ...)
motivo: string; // obligatorio
hora_desde?: string | null;
hora_hasta?: string | null;
subtipo_permiso?: string | null;
es_parcial?: boolean;
estado?: PermissionStatus; // default SOLICITADO
requiere_documento?: boolean;
documento_adjunto_url?: string | null;
numero_documento?: string | null;
es_con_goce_sueldo?: boolean; // default true
descuenta_vacaciones?: boolean;
es_recuperable?: boolean;
fecha_recuperacion?: string | null;
comentario_aprobacion?: string | null;
observaciones?: string | null;
}
CampoRegla
empleado_id, fecha_desde, fecha_hasta, tipo_permiso, motivoObligatorios. validateRequired.
estado (opcional)["SOLICITADO", "APROBADO", "RECHAZADO", "CANCELADO", "USADO"].
fecha_hasta >= fecha_desde"La fecha hasta debe ser mayor o igual a fecha desde".
fecha_recuperacion > fecha_hastaSolo si es_recuperable && fecha_recuperacion.
es_parcial = truehora_desde && hora_hasta"Los permisos parciales requieren hora desde y hora hasta".
es_parcial = truehora_hasta > hora_desde"La hora hasta debe ser mayor que la hora desde".
es_parcial = falseForzar hora_desde = null y hora_hasta = null.

Antes de persistir, el service completa:

{
estado: data.estado || "SOLICITADO",
es_parcial: !!data.es_parcial,
requiere_documento: !!data.requiere_documento,
es_con_goce_sueldo: data.es_con_goce_sueldo !== undefined ? !!data.es_con_goce_sueldo : true,
descuenta_vacaciones: !!data.descuenta_vacaciones,
es_recuperable: !!data.es_recuperable,
created_by: ctx.userId,
}

es_con_goce_sueldo es el único default true; el resto de los flags son false si no se envían.

update lee la fila actual con findById y la usa como base para resolver los campos que no vienen en el data. Lógica clave:

const has = (k) => Object.prototype.hasOwnProperty.call(data, k);
const finalParcial = has("es_parcial") ? !!data.es_parcial : current.es_parcial;
let finalHoraDesde = has("hora_desde") ? (data.hora_desde ?? "") : null;
let finalHoraHasta = has("hora_hasta") ? (data.hora_hasta ?? "") : null;
if (!finalParcial) { finalHoraDesde = ""; finalHoraHasta = ""; }

Si la fila pasa de es_parcial=true a false, las horas se vacían explícitamente. La validación de fechas se hace con merge de data + current para evitar inconsistencias parciales.

EstadoSignificado
SOLICITADOPendiente de aprobación. No impacta asistencia ni payroll.
APROBADOAprobado por RRHH/jefatura. Impacta la grilla de AttendanceService y, según flags, los cálculos de payroll.
RECHAZADONegado. No impacta.
CANCELADOAnulado por el solicitante. No impacta.
USADOAprobado y consumido por el periodo liquidado. Estado final.
FlagEfecto en payroll
es_con_goce_sueldo = trueMantiene remuneración. No descuenta.
es_con_goce_sueldo = falseDescuenta proporcional al periodo afectado.
es_parcial = trueDescuenta sólo las horas indicadas, no el día completo.
descuenta_vacaciones = trueRebaja saldo de feriado (política empresa).
es_recuperable = trueEl trabajador recupera las horas en fecha_recuperacion. No descuenta si se cumple.
requiere_documento = trueExige documento_adjunto_url antes de aprobar (regla de UI).
MensajeCuándo
"<campo> es requerido"Falta alguno de los 5 obligatorios.
"Estado inválido: <valor>"estado fuera del enum.
"La fecha hasta debe ser mayor o igual a fecha desde"Rango invertido.
"La fecha de recuperación debe ser posterior a la fecha hasta"Recuperable con fecha mal puesta.
"Los permisos parciales requieren hora desde y hora hasta"es_parcial=true sin horas.
"La hora hasta debe ser mayor que la hora desde"Horas invertidas.
notFound("Permiso", id)update o delete con id inexistente.
ServiceCómo consume
AttendanceServiceCuando un permiso pasa a APROBADO, la grilla del mes refleja tipo_ausencia en los días afectados.
PayrollEngineLee permisos aprobados del periodo para aplicar descuento por días sin goce, mantener pago en con goce, o calcular subsidio en licencia médica.
VacationServiceSi descuenta_vacaciones=true, el saldo de feriado se rebaja al aprobarse el permiso.