Vacaciones
Resumen
Sección titulada «Resumen»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 seguridadPORTAL_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
Endpoints
Sección titulada «Endpoints»GET /me/time-off/requests/all/vacations
Sección titulada «GET /me/time-off/requests/all/vacations»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.
GET /me/time-off/kardex
Sección titulada «GET /me/time-off/kardex»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 | Sí | Fecha del movimiento. |
type |
enum KardexItemType |
Sí | Tipo de movimiento (ver tabla de enum abajo). |
days |
number | Sí | Días del movimiento (negativo = descuento). |
balance |
number | Sí | 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.
GET /me/time-off/balance-annual
Sección titulada «GET /me/time-off/balance-annual»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.
GET /me/time-off/vacation-agreement-balance
Sección titulada «GET /me/time-off/vacation-agreement-balance»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).
GET /me/time-off/requests/approved
Sección titulada «GET /me/time-off/requests/approved»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.
POST /me/time-off/requests/calculate-days
Sección titulada «POST /me/time-off/requests/calculate-days»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 | Sí | Fecha de inicio del rango a calcular. |
endDate |
date | Sí | 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. |
POST /me/time-off/requests/vacations
Sección titulada «POST /me/time-off/requests/vacations»Crea una solicitud de vacaciones para el usuario autenticado.
Body (MeCreateVacationRequestDto):
| Campo | Tipo | Obligatorio | Enum | Descripción |
|---|---|---|---|---|
startDate |
date | Sí | — | Fecha de inicio. |
endDate |
date | Sí | — | Fecha de fin (no puede ser menor a startDate). |
observation |
string | No | — | Motivo/observación libre. |
daysType |
string | Sí | 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."). |
DELETE /me/time-off/requests/vacations/{id}
Sección titulada «DELETE /me/time-off/requests/vacations/{id}»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. |
GET /me/vacation/generate (comprobante PDF)
Sección titulada «GET /me/vacation/generate (comprobante PDF)»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.
Lógica de cliente
Sección titulada «Lógica de cliente»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.
Tipo de días (daysType)
Sección titulada «Tipo de días (daysType)»| 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) |
Alcance del calendario de aprobadas (scope)
Sección titulada «Alcance del calendario de aprobadas (scope)»| 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). |
Qué mostrar/ocultar según estado
Sección titulada «Qué mostrar/ocultar según estado»- Botón “Solicitar” del formulario: deshabilitado hasta que
calculate-daysresponda exitosamente para el rango de fechas seleccionado. Cada cambio destartDateoendDatedebe 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 aPORTAL_SOLICITUD_VACACIONES_POR_TIPO. Si no lo tiene, no mostrar el campo y enviar siempredaysType: "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 endpointDELETE). - 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 aPORTAL_VISUALIZACION_BALANCE_ANUAL; si la llamada devuelve400, tratarlo como “no disponible para este tenant” y ocultar la sección en vez de mostrar un error.
Validaciones previas a cada acción
Sección titulada «Validaciones previas a cada acción»- 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 con400.- No enviar
usernameen el body decalculate-daysni derequests/vacationsen el flujo self-service — ese campo es sólo para solicitar en nombre de un reporte directo.
Estados vacíos y casos borde
Sección titulada «Estados vacíos y casos borde»- Sin beneficios de vacaciones (tipo de contrato no lo incluye):
kardexyvacation-agreement-balancedevuelven estructuras vacías/en cero (no error) — mostrar la pantalla con saldo0y sin movimientos, en vez de un estado de error.calculate-daysy crear solicitud sí devuelven403en 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/vacationsdevuelve[]— mostrar estado vacío (“Aún no tienes solicitudes de vacaciones”). balance-annualno soportado para el país/esquema del tenant: responde400. No es un error de la app; ocultar la sección.balance-annualcon mensaje informativo (p. ej. personal tipo pasantía): puede responder200conitems: []y un campomessageexplicando el motivo — si vienemessage, mostrarlo en vez de una tabla vacía sin contexto.- Calendario de aprobadas (
requests/approved) sin resultados:scope = areadevuelve[]si el usuario no tiene área asignada; tratarlo como estado vacío normal, no como error.