Ir al contenido

Vacaciones

El módulo cubre el flujo de autoservicio de vacaciones del empleado autenticado: consultar su historial de solicitudes, ver su saldo (simple y detallado por período), revisar el kardex de movimientos, calcular días antes de solicitar, crear una solicitud, cancelarla mientras está pendiente y descargar el comprobante en PDF. También incluye un endpoint de calendario para ver qué compañeros tienen vacaciones o permisos aprobados vigentes.

Acciones cubiertas:

  • Ver historial y estado de mis solicitudes de vacaciones.
  • Ver saldo disponible (resumen simple y detalle por período).
  • Ver kardex de movimientos (cargos, ajustes, pagos).
  • Calcular días hábiles/feriados antes de enviar una solicitud.
  • Crear y cancelar una solicitud de vacaciones.
  • Descargar el comprobante PDF de una solicitud.
  • Ver solicitudes aprobadas de otros compañeros (calendario por empresa/área/departamento).

Prerrequisitos:

  • El usuario debe tener información organizacional activa (cargo, departamento). Sin ella, el backend rechaza la mayoría de los endpoints de este módulo con un error genérico (ver Convenciones).
  • El tipo de contrato del usuario debe incluir beneficios de vacaciones. Si no los incluye, varios endpoints de lectura devuelven saldo/listas vacías en vez de error (ver “Estados vacíos” en Lógica de cliente), y crear una solicitud o calcular días devuelve 403.
  • El bloque de saldo detallado por período (balance-annual) está condicionado en el portal de referencia a la acción de perfil de seguridad PORTAL_VISUALIZACION_BALANCE_ANUAL. La app debe validar el perfil de seguridad del usuario antes de mostrarlo (ver Convenciones → Permisos y visibilidad).
  • El selector de tipo de vacaciones (Normal/Adicional) está condicionado a la acción PORTAL_SOLICITUD_VACACIONES_POR_TIPO. Sin ese permiso, el tipo se fija siempre en “Mixto” y no se muestra el selector.
sequenceDiagram
    participant App as App móvil
    participant API as th-core-api

    Note over App,API: Carga inicial de la pantalla de vacaciones
    App->>API: GET /me/time-off/requests/all/vacations
    API-->>App: historial de solicitudes propias
    App->>API: GET /me/time-off/kardex
    API-->>App: currentBalance + movimientos
    App->>API: GET /me/time-off/vacation-agreement-balance
    API-->>App: saldo simple (asignado/usado/disponible)
    App->>API: GET /me/time-off/balance-annual
    API-->>App: saldo detallado por período (si el usuario tiene permiso)
    App->>API: GET /me/time-off/requests/approved?scope=department
    API-->>App: aprobadas vigentes de compañeros (calendario)

    Note over App,API: Usuario abre el formulario Solicitar vacaciones
    App->>API: POST /me/time-off/requests/calculate-days
    API-->>App: días hábiles, fin de semana, feriados, fecha de retorno
    Note over App: se repite en cada cambio de fecha —<br/>Solicitar queda deshabilitado hasta tener una respuesta válida

    App->>API: POST /me/time-off/requests/vacations
    API-->>App: solicitud creada (status = P, pendiente de aprobación)
    App->>API: GET /me/time-off/requests/all/vacations
    API-->>App: refresco del historial

    Note over App,API: Cancelar una solicitud pendiente
    App->>API: DELETE /me/time-off/requests/vacations/:id
    API-->>App: confirmación
    App->>API: GET /me/time-off/requests/all/vacations
    API-->>App: refresco del historial

    Note over App,API: Opcional, desde cualquier solicitud del historial
    App->>API: GET /me/vacation/generate?vacationId=:id
    API-->>App: url del PDF generado

Historial completo de solicitudes de vacaciones del usuario autenticado (todos los estados).

No recibe query params.

Response — array de:

Campo Tipo Descripción
id number Id de la solicitud.
requestDate date Fecha en que se creó la solicitud.
startDate date Fecha de inicio del descanso.
endDate date Fecha de fin del descanso.
incorporationDate date Fecha de reincorporación (primer día hábil tras el descanso).
assignedDays number Días de saldo descontados por la solicitud.
observation string Observación ingresada al crear la solicitud.
status enum VacationRequestStatusEnum Ver tabla de estados en Lógica de cliente.
[
{
"id": 8931,
"requestDate": "2026-06-20T15:04:00.000Z",
"startDate": "2026-07-06T00:00:00.000Z",
"endDate": "2026-07-10T00:00:00.000Z",
"incorporationDate": "2026-07-13T00:00:00.000Z",
"assignedDays": 5,
"observation": "Viaje familiar",
"status": "A"
},
{
"id": 8936,
"requestDate": "2026-07-14T09:12:00.000Z",
"startDate": "2026-08-03T00:00:00.000Z",
"endDate": "2026-08-04T00:00:00.000Z",
"incorporationDate": "2026-08-05T00:00:00.000Z",
"assignedDays": 2,
"observation": "",
"status": "P"
}
]

Sin errores específicos adicionales a los comunes.

Kardex de movimientos de saldo del usuario: cargos por solicitudes aprobadas, ajustes, pagos y el saldo acumulado tras cada movimiento.

No recibe query params.

Response:

Campo Tipo Descripción
currentBalance number Saldo vigente al momento de la consulta.
items KardexItemDto[] Movimientos ordenados, ver tabla siguiente.

KardexItemDto:

Campo Tipo Obligatorio Descripción
date date Fecha del movimiento.
type enum KardexItemType Tipo de movimiento (ver tabla de enum abajo).
days number Días del movimiento (negativo = descuento).
balance number Saldo acumulado después de este movimiento.
description string No Sólo en algunos tipos (p. ej. observación de un ajuste).
requestId string No Sólo si type = APPROVED_REQUEST.
requestDate, startDate, endDate, incorporationDate date No Sólo si type = APPROVED_REQUEST.
status string No Estado de la solicitud asociada, si aplica.
observation string No Observación de la solicitud o del ajuste.
adjustmentMovement 'INCREASE' | 'DECREASE' | 'NEUTRAL' No Sólo en ADJUSTMENT / VACATION_CHARGE_ADJUSTMENT: efecto sobre el saldo según el signo de days.
year number No Sólo en type = VACATION_YEAR.
schema object No Sólo en type = VACATION_YEAR: { normalWorkingDays, normalWeekendDays, additionalWorkingDays, additionalWeekendDays }.
{
"currentBalance": 12,
"items": [
{
"date": "2026-01-01T00:00:00.000Z",
"type": "VACATION_YEAR",
"days": 15,
"balance": 15,
"year": 2026,
"schema": {
"normalWorkingDays": 15,
"normalWeekendDays": 0,
"additionalWorkingDays": 0,
"additionalWeekendDays": 0
}
},
{
"date": "2026-07-06T00:00:00.000Z",
"type": "APPROVED_REQUEST",
"days": -5,
"balance": 10,
"requestId": "8931",
"requestDate": "2026-06-20T15:04:00.000Z",
"startDate": "2026-07-06T00:00:00.000Z",
"endDate": "2026-07-10T00:00:00.000Z",
"incorporationDate": "2026-07-13T00:00:00.000Z",
"status": "A",
"observation": "Viaje familiar"
},
{
"date": "2026-07-15T00:00:00.000Z",
"type": "ADJUSTMENT",
"days": 2,
"balance": 12,
"observation": "Ajuste manual por RRHH",
"adjustmentMovement": "INCREASE"
}
]
}

Si el contrato del usuario no incluye beneficios de vacaciones, la respuesta es { "currentBalance": 0, "items": [] } (200, no error). Sin errores específicos adicionales a los comunes.

Detalle del saldo agrupado por período (año de vacaciones), con el desglose de días ganados, usados y ajustes de cada período. Es más granular que kardex pero su implementación es específica por esquema de vacaciones del tenant/país.

No recibe query params.

Response: { "items": [...] }.

Cada ítem trae un discriminador type con valores observados: BALANCE_PERIOD, BALANCE_WITHOUT_PERIOD, APPROVED_REQUEST, ADJUSTMENT, VACATION_CHARGE_ADJUSTMENT, PROPORTION_DAYS. Los períodos (BALANCE_PERIOD / BALANCE_WITHOUT_PERIOD) traen un array items anidado con el detalle de solicitudes y ajustes de ese período.

{
"items": [
{
"type": "BALANCE_PERIOD",
"startYear": 2025,
"endYear": 2026,
"totalEarnedDays": 15,
"earnedAdditionalDays": 0,
"discountedEnjoyedDays": 5,
"usedWeekendDays": 0,
"discountedPendingDays": 10,
"items": [
{
"type": "APPROVED_REQUEST",
"requestId": 8931,
"startDate": "2026-07-06T00:00:00.000Z",
"endDate": "2026-07-10T00:00:00.000Z",
"assignedDays": 5,
"workDays": 5,
"weekendDays": 0
}
]
}
]
}

Errores específicos:

Código Cuándo ocurre
400 El esquema de vacaciones del país/tenant no implementa cálculo anual ("El cálculo anual de balance de vacaciones no es aplicable para esta estrategia."). Debe tratarse como “sección no disponible para este tenant”, no como error genérico.

Para algunos tipos de personal (p. ej. pasantías) la respuesta puede venir como { "items": [], "message": "El tipo de personal no registra vacaciones (Pasante)" } con status 200.

Saldo resumido para tenants con un esquema de “convenio de vacaciones” (asignado / usado / disponible del año actual). Es un cálculo más simple que balance-annual, útil como saldo por defecto cuando no necesitas el desglose por período.

No recibe query params.

Response:

Campo Tipo Descripción
assignedDays number Total de días asignados por convenio.
usedDays number Días ya usados en el año en curso.
availableDays number Días disponibles restantes.
year number Año al que corresponde el cálculo.
{
"assignedDays": 5,
"usedDays": 2,
"availableDays": 3,
"year": 2026
}

Si el contrato del usuario no incluye beneficios de vacaciones, la respuesta es { "assignedDays": 0, "usedDays": 0, "availableDays": 0, "year": 2026 } (200, no error).

Solicitudes aprobadas y vigentes a la fecha actual, de otros usuarios, dentro de un alcance (empresa/área/departamento). Sirve para un calendario tipo “quién está fuera hoy”. Mezcla vacaciones y permisos aprobados en una sola lista.

Query params:

Param Tipo Obligatorio Enum Descripción
scope string No company (default) | area | department Alcance del listado: toda la empresa permitida, mi área o mi departamento.

Response — array de:

Campo Tipo Descripción
type enum 'H' | 'D' Unidad de la solicitud (Horas/Días) — no indica si es vacación o permiso, ver nota abajo.
date date Fecha de fin de la solicitud (usada para ordenar).
request.startDate / request.endDate date Rango de la solicitud.
request.status string Estado de la solicitud subyacente.
request.user.username string Usuario de la solicitud.
request.user.fullName string Nombre completo.
request.user.avatar string URL del avatar.
request.user.position.name string Cargo.
request.user.department.name string Departamento.
request.user.department.company.name string Empresa.
[
{
"type": "D",
"date": "2026-07-18T00:00:00.000Z",
"request": {
"startDate": "2026-07-14T00:00:00.000Z",
"endDate": "2026-07-18T00:00:00.000Z",
"status": "A",
"user": {
"avatar": "https://cdn.twiinshrm.com/avatars/mrojas.png",
"fullName": "María Rojas",
"username": "mrojas",
"position": { "name": "Analista de Nómina" },
"department": {
"name": "Recursos Humanos",
"company": { "name": "Twiins Ecuador" }
}
}
}
}
]

Sin errores específicos adicionales a los comunes.

Calcula el desglose de días (hábiles, fin de semana, feriados) y la fecha de reincorporación para un rango de fechas. Debe llamarse antes de crear la solicitud — el formulario de referencia lo dispara automáticamente cada vez que cambia la fecha de inicio o fin.

Body:

Campo Tipo Obligatorio Descripción
startDate date Fecha de inicio del rango a calcular.
endDate date Fecha de fin del rango a calcular.
username string No Sólo para solicitar en nombre de un reporte directo (fuera del alcance self-service de este documento). Omitir en el flujo normal de la app.

Response:

Campo Tipo Descripción
assignedDays number Total de días hábiles asignados (los que descuentan saldo).
weekendsDays number Total de días de fin de semana dentro del rango.
holidays number Total de feriados dentro del rango.
totalDaysRequested number Total de días solicitados (desde el inicio hasta el día previo al retorno).
returnDate date Fecha de reincorporación (primer día hábil después del descanso).
{
"assignedDays": 5,
"weekendsDays": 2,
"holidays": 0,
"totalDaysRequested": 7,
"returnDate": "2026-07-13T00:00:00.000Z"
}

Errores específicos:

Código Cuándo ocurre
400 endDate anterior a startDate ("La fecha de inicio no puede ser mayor que la fecha fin").
403 El tipo de contrato del usuario no incluye beneficios de vacaciones.

Crea una solicitud de vacaciones para el usuario autenticado.

Body (MeCreateVacationRequestDto):

Campo Tipo Obligatorio Enum Descripción
startDate date Fecha de inicio.
endDate date Fecha de fin (no puede ser menor a startDate).
observation string No Motivo/observación libre.
daysType string N Normal, A Adicional, M Mixto El DTO lo exige aunque el swagger documente un default; si el tenant no habilita el selector de tipo, enviar siempre M.
isHalfDayStart string No (default N) S / N Sólo relevante si el esquema del tenant permite medios días.
isHalfDayEnd string No (default N) S / N Ídem, para el día de fin.
username string No Sólo para solicitar en nombre de un reporte directo (fuera de alcance). Omitir en self-service.

Response (MeCreateVacationResponseDto):

{
"vacationRequestResult": {
"username": "jperez",
"id": 8937,
"requestDate": "2026-07-16T18:20:00.000Z",
"startDate": "2026-08-03T00:00:00.000Z",
"endDate": "2026-08-04T00:00:00.000Z",
"incorporationDate": "2026-08-05T00:00:00.000Z",
"daysType": "M",
"balanceDays": 8,
"totalDays": 2,
"assignedDays": 2,
"workDays": 2,
"weekendDays": 0,
"holidayDays": 0,
"observation": "",
"status": "P",
"paymentDays": 0,
"isHalfDayStart": "N",
"isHalfDayEnd": "N",
"user": {
"username": "jperez",
"fullName": "Juan Pérez",
"mail": "jperez@twiinshrm.com",
"avatar": "https://cdn.twiinshrm.com/avatars/jperez.png"
}
},
"vacationRequestDays": [
{
"requestId": 8937,
"day": 3,
"month": 8,
"year": 2026,
"date": "2026-08-03T00:00:00.000Z",
"isAssigned": "S",
"isWorkDay": "S",
"isWeekend": "N",
"isHoliday": "N",
"isHalfDay": "N"
},
{
"requestId": 8937,
"day": 4,
"month": 8,
"year": 2026,
"date": "2026-08-04T00:00:00.000Z",
"isAssigned": "S",
"isWorkDay": "S",
"isWeekend": "N",
"isHoliday": "N",
"isHalfDay": "N"
}
],
"approverWorkflowRequest": [
{
"id": 4521,
"username": "jperez",
"alternateUser": "",
"type": "VACATIONS",
"approverOrder": 1,
"status": "S",
"assignmentDate": "2026-07-16T18:20:00.000Z",
"approvalDate": null,
"observation": null,
"result": "",
"userApprover": {
"username": "lgomez",
"fullName": "Laura Gómez"
}
}
]
}

vacationRequestResult.user y approverWorkflowRequest[].userApprover traen además datos de posición organizacional (position, supervisor*, etc.) que normalmente no son relevantes para la UI de mobile — se omiten en el ejemplo por brevedad, pero existen en la respuesta real.

Errores específicos:

Código Cuándo ocurre
400 endDate anterior a startDate.
403 El tipo de contrato del usuario no incluye beneficios de vacaciones ("El tipo de contrato no incluye beneficios de vacaciones.").

Cancela una solicitud de vacaciones propia. Marca la solicitud como CANCELLED y cancela cualquier paso de aprobación pendiente asociado.

Path param: id (number, id de la solicitud).

Response: sin cuerpo relevante (confirmación de la cancelación).

Errores específicos:

Código Cuándo ocurre
404 La solicitud no existe o no pertenece al usuario autenticado.

Genera y devuelve la URL del comprobante PDF de una solicitud de vacaciones (servicio legacy). Se ofrece desde el historial, para cualquier solicitud, sin importar su estado.

Query param: vacationId (number, requerido).

Response: { "url": "https://.../SolicitudVacacion_8936.pdf" } (url puede venir null).

Errores específicos: 400 si falla la generación en el servicio legacy.

Estados de una solicitud (VacationRequestStatusEnum)

Sección titulada «Estados de una solicitud (VacationRequestStatusEnum)»
Valor Significado backend Texto sugerido Color sugerido
P Pendiente Pendiente warning / amarillo
A Aprobado Aprobada success / verde
N No aprobado Rechazada error / rojo
C Cancelado Cancelada error / rojo
H Histórico Histórico info / azul
X Eliminado — (excluir de contadores y listados)

Al calcular contadores de estado (por ejemplo un resumen “Pendientes: 2, Aprobadas: 5…”), excluir siempre las solicitudes en X (eliminado) — no representan una solicitud visible para el usuario.

Valor Significado
N Normal
A Adicional
M Mixto (default cuando el selector está oculto)

Medios días (isHalfDayStart / isHalfDayEnd)

Sección titulada «Medios días (isHalfDayStart / isHalfDayEnd)»
Valor Significado
S Sí, medio día
N No (default)
Valor Significado
company Toda la empresa permitida (default).
area Sólo mi área.
department Sólo mi departamento.

Tipos de movimiento de kardex (KardexItemType)

Sección titulada «Tipos de movimiento de kardex (KardexItemType)»
Valor Descripción
INITIAL_BALANCE Saldo inicial cargado para el usuario.
VACATION_YEAR Asignación anual de días (apertura de período).
APPROVED_REQUEST Descuento por una solicitud aprobada.
ADJUSTMENT Ajuste manual de saldo (incremento o decremento).
VACATION_CHARGE_ADJUSTMENT Ajuste de cargo de vacaciones.
PAYMENT Pago/liquidación de días.
PROPORTION_DAYS Días proporcionales (p. ej. por ingreso a mitad de período).
  • Botón “Solicitar” del formulario: deshabilitado hasta que calculate-days responda exitosamente para el rango de fechas seleccionado. Cada cambio de startDate o endDate debe disparar una nueva llamada; mientras está en curso, mostrar el formulario en estado de carga.
  • Selector de tipo de vacaciones (daysType): mostrar sólo si el usuario tiene el permiso equivalente a PORTAL_SOLICITUD_VACACIONES_POR_TIPO. Si no lo tiene, no mostrar el campo y enviar siempre daysType: "M".
  • Checkboxes de medio día: mostrar sólo si el esquema de vacaciones del tenant los permite. Si no aplican, omitir los campos (el backend asume N).
  • Acción “Cancelar” en el historial: mostrar únicamente cuando status === "P" (ver caution en el endpoint DELETE).
  • Acción “Generar comprobante”: siempre disponible en el historial, sin depender del estado.
  • Bloque de saldo detallado (balance-annual): mostrar sólo si el usuario tiene el permiso equivalente a PORTAL_VISUALIZACION_BALANCE_ANUAL; si la llamada devuelve 400, tratarlo como “no disponible para este tenant” y ocultar la sección en vez de mostrar un error.
  • Antes de habilitar “Solicitar”: rango de fechas seleccionado + respuesta OK de calculate-days.
  • endDate >= startDate: válidalo en el cliente para evitar el roundtrip, aunque el backend también lo rechaza con 400.
  • No enviar username en el body de calculate-days ni de requests/vacations en el flujo self-service — ese campo es sólo para solicitar en nombre de un reporte directo.
  • Sin beneficios de vacaciones (tipo de contrato no lo incluye): kardex y vacation-agreement-balance devuelven estructuras vacías/en cero (no error) — mostrar la pantalla con saldo 0 y sin movimientos, en vez de un estado de error. calculate-days y crear solicitud sí devuelven 403 en este caso: ocultar o deshabilitar la acción “Solicitar” si ya se sabe por otra vía que el usuario no tiene el beneficio.
  • Sin solicitudes: requests/all/vacations devuelve [] — mostrar estado vacío (“Aún no tienes solicitudes de vacaciones”).
  • balance-annual no soportado para el país/esquema del tenant: responde 400. No es un error de la app; ocultar la sección.
  • balance-annual con mensaje informativo (p. ej. personal tipo pasantía): puede responder 200 con items: [] y un campo message explicando el motivo — si viene message, mostrarlo en vez de una tabla vacía sin contexto.
  • Calendario de aprobadas (requests/approved) sin resultados: scope = area devuelve [] si el usuario no tiene área asignada; tratarlo como estado vacío normal, no como error.