Roles de Pago
Resumen
Sección titulada «Resumen»El módulo cubre la visualización de roles de pago del usuario autenticado y su firma electrónica: consultar el rol del período actual, revisar el historial de totales por rango de fechas, descargar el comprobante en PDF y firmar el rol (conforme o no conforme) mediante aceptación simple o mediante código QR/OTP enviado por correo. También incluye una vista resumida de bonos anuales (décimo tercero, décimo cuarto y utilidades).
Acciones cubiertas:
- Ver el rol de pago de un mes/año específico, con el detalle de ingresos, egresos y provisiones.
- Ver el historial de totales de nómina por rango de fechas.
- Ver y descargar roles precargados como archivo (tenants tipo Argentina, donde el rol no se calcula sino que se sube como PDF).
- Descargar el comprobante en PDF del rol de un período.
- Firmar el rol de pago: aceptación simple (SA) o firma con código QR/OTP (HD), incluyendo la variante “no conforme”.
- Ver el resumen de bonos anuales (décimo tercero, décimo cuarto, utilidades) y descargar su comprobante.
Prerrequisitos:
-
El tipo de sistema de nómina del tenant (
Payroll_System_Type, expuesto por backend víaTenantParameters) determina qué vista aplica:calculated,integration-tableeintegration-ws→ vista estructurada (ingresos/egresos/provisiones), usanGET /me/payrollyGET /me/payroll/history.uploaded→ vista de archivos cargados (roles precargados como PDF), usaGET /me/payroll/uploadedyGET /me/payroll/uploaded/history.
Un mismo tenant usa un solo tipo; la app debe pedir este dato a través del endpoint de parámetros de tenant (fuera de alcance de este documento) y renderizar la vista correspondiente.
-
La firma está habilitada o no por tenant (
GET /me/payroll/signature-config). Sienabled = false, no se muestra ninguna acción de firma y la descarga usa directamente la URL del archivo (url/fileUrlde las respuestas), sin pasar porgenerate. -
El tipo de firma (
SignatureConfigDto.type) puede serSA,HD,VD,VDBIO,M-VD,M-VDBIOoVDSTAMP. Este documento cubre en detalle sóloSA(aceptación simple) yHD(código QR/OTP por correo), que son los únicos tipos con flujo de referencia implementado en el portal web. Los demás valores existen en el contrato pero no tienen flujo de cliente verificado — tratarlos como no soportados hasta confirmar con backend.
sequenceDiagram
participant App as App móvil
participant API as th-core-api
Note over App,API: Carga inicial de la pantalla de Roles de Pago
App->>API: GET /me/payroll/signature-config
API-->>App: enabled + type del tenant
App->>API: GET /me/payroll?month=:month&year=:year
API-->>App: rol del período seleccionado (o 404 si no hay rol cerrado)
App->>API: GET /me/payroll/history?startDate=:startDate&endDate=:endDate
API-->>App: totales mensuales del rango + signatureEnabled
Note over App: Sólo si Payroll_System_Type = uploaded
App->>API: GET /me/payroll/uploaded/history?startDate=:startDate&endDate=:endDate
API-->>App: archivos cargados del rango, con isSigned por archivo
Note over App,API: Descargar comprobante PDF
App->>API: GET /me/payroll/generate?year=:year&month=:month
API-->>App: url firmada del PDF (S3, expira en 1 hora)
Note over App,API: Firmar - aceptación simple (SA)
App->>API: POST /me/payroll/:year/:month/sign
API-->>App: 201 confirmacion de firma
App->>API: GET /me/payroll?month=:month&year=:year
API-->>App: refresco con signatureStatus = SIGNED o NOT_CONFORM
Note over App,API: Firmar - código QR/OTP (HD)
App->>API: POST /me/payroll/:year/:month/qr-sign/init
API-->>App: status V, código de 6 digitos enviado por correo
App->>API: POST /me/payroll/:year/:month/qr-sign/verify
API-->>App: status CS si el codigo es correcto, la firma queda registrada
App->>API: GET /me/payroll?month=:month&year=:year
API-->>App: refresco con signatureStatus actualizado
Note over App,API: Firmar no conforme sobre un archivo cargado, sin OTP
App->>API: POST /me/payroll/:year/:month/qr-sign/non-conform
API-->>App: status CS, firma no conforme registrada de una vez
Note over App,API: Bonos anuales, opcional
App->>API: GET /me/payroll/bonuses?year=:year
API-->>App: totales de decimo tercero, decimo cuarto y utilidades
App->>API: GET /me/payroll/generate/bonuses/:bonusType?year=:year
API-->>App: url del comprobante del bono
Endpoints
Sección titulada «Endpoints»GET /me/payroll
Sección titulada «GET /me/payroll»Rol de pago del usuario autenticado para un período específico. Sólo retorna datos de roles cerrados y publicados.
Query params:
| Param | Tipo | Obligatorio | Descripción |
|---|---|---|---|
month |
number | Sí | Mes del rol de pago (1-12). |
year |
number | Sí | Año del rol de pago. |
Response:
| Campo | Tipo | Descripción |
|---|---|---|
username |
string | Usuario del rol. |
year, month |
number | Período del rol. |
periodId, payrollId |
number | Identificadores internos del período y del rol. |
income |
array | Rubros de ingreso (id, name, value, additionalValue, observation, detailObservation). |
expense |
array | Rubros de egreso, misma forma que income. |
provisions |
array | Rubros de provisión (p. ej. décimo tercero prorrateado), misma forma que income. |
incomeTotals, expenseTotals, provisionsTotals |
object | { totalValue, totalAdditionalValue, itemCount } por categoría. |
signatureStatus |
enum PayrollSignatureStatusEnum |
PENDING | SIGNED | NOT_CONFORM. |
signatureDate |
date | null | Fecha de la firma. |
signatureType |
string | null | SA, HD, VD, VDBIO, etc. |
requiresSignature |
boolean | Si el tenant exige firma para este rol. |
observation |
string | null | Observación del usuario al firmar (relevante sobre todo en NOT_CONFORM). |
{ "username": "jperez", "year": 2026, "month": 6, "periodId": 1, "payrollId": 5, "income": [ { "id": 1, "name": "Sueldo Básico", "value": 500.0, "additionalValue": 0.0, "observation": null, "detailObservation": null } ], "expense": [ { "id": 15, "name": "IESS", "value": 47.25, "additionalValue": 0.0, "observation": null, "detailObservation": null } ], "provisions": [ { "id": 30, "name": "Décimo Tercer Sueldo", "value": 41.67, "additionalValue": 0.0, "observation": null, "detailObservation": null } ], "incomeTotals": { "totalValue": 500.0, "totalAdditionalValue": 0.0, "itemCount": 1 }, "expenseTotals": { "totalValue": 47.25, "totalAdditionalValue": 0.0, "itemCount": 1 }, "provisionsTotals": { "totalValue": 41.67, "totalAdditionalValue": 0.0, "itemCount": 1 }, "signatureStatus": "PENDING", "signatureDate": null, "signatureType": null, "requiresSignature": true, "observation": null}Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
No existe un rol de pago cerrado y publicado para ese mes/año. Tratar como estado vacío (“sin datos para este período”), no como error genérico. |
400 |
Parámetros inválidos (month/year no numéricos). |
GET /me/payroll/history
Sección titulada «GET /me/payroll/history»Totales de nómina del usuario autenticado en un rango de fechas, desglosados mes a mes. Incluye información de firma por mes.
Query params:
| Param | Tipo | Obligatorio | Descripción |
|---|---|---|---|
startDate |
string | Sí | Fecha de inicio del rango (YYYY-MM-DD). |
endDate |
string | Sí | Fecha de fin del rango (YYYY-MM-DD). |
Response:
| Campo | Tipo | Descripción |
|---|---|---|
username |
string | Usuario consultado. |
startDate, endDate |
string | Rango consultado. |
totalPayrollCount |
number | Cantidad de roles encontrados en el rango. |
monthlyTotals |
array | Un ítem por mes: year, month, periodId, payrollId, payrollDate, incomeTotals, expenseTotals, provisionsTotals, signatureStatus, signatureDate, signatureType, requiresSignature, observation. |
consolidatedTotals |
object | { totalIncome, totalExpense, totalProvisions } de todo el rango. |
signatureEnabled |
boolean | Si la firma está habilitada para el tenant — úsalo para decidir si mostrar la columna/acción de firma en la tabla de historial. |
{ "username": "jperez", "startDate": "2026-01-01", "endDate": "2026-06-30", "totalPayrollCount": 6, "monthlyTotals": [ { "year": 2026, "month": 6, "periodId": 1, "payrollId": 5, "payrollDate": "2026-06-30", "incomeTotals": { "totalValue": 500.0, "totalAdditionalValue": 0.0, "itemCount": 1 }, "expenseTotals": { "totalValue": 47.25, "totalAdditionalValue": 0.0, "itemCount": 1 }, "provisionsTotals": { "totalValue": 41.67, "totalAdditionalValue": 0.0, "itemCount": 1 }, "signatureStatus": "SIGNED", "signatureDate": "2026-06-30T14:30:00.000Z", "signatureType": "SA", "requiresSignature": true, "observation": "Conforme con el pago recibido" } ], "consolidatedTotals": { "totalIncome": 3000.0, "totalExpense": 283.5, "totalProvisions": 250.02 }, "signatureEnabled": true}Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
No hay roles cerrados y publicados en el rango. Tratar como estado vacío. |
400 |
startDate/endDate inválidos. |
GET /me/payroll/uploaded
Sección titulada «GET /me/payroll/uploaded»Roles de pago precargados como archivo para un período específico (tenants tipo Argentina, donde el rol no se calcula sino que se sube en PDF). Un mismo período puede tener varios archivos por tipo (SUELDO, AGUINALDO, quincenas, etc.).
Query params:
| Param | Tipo | Obligatorio | Descripción |
|---|---|---|---|
month |
number | Sí | Mes del rol (1-12). |
year |
number | Sí | Año del rol. |
Response — array de:
| Campo | Tipo | Enum | Descripción |
|---|---|---|---|
type |
string | SUELDO | AGUINALDO | 1 QUINCENA | 2 QUINCENA | OTROS |
Tipo de rol cargado. |
name |
string | — | Identificador del empleado usado en el archivo legacy. |
url |
string | — | URL directa del PDF cargado (sin firma estampada). |
year, month |
number | — | Período del archivo. |
isSigned |
boolean | — | Si el archivo ya fue firmado/aceptado, calculado por backend. |
signatureDate |
date | null | — | Fecha de la firma. |
[ { "type": "SUELDO", "name": "12345678", "url": "https://twiins.twiinshrm.com/talento_upload/roles/202601/uuid-file-identifier.pdf", "year": 2026, "month": 1, "isSigned": true, "signatureDate": "2026-02-08T15:06:29.000Z" }]Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
No hay roles cargados para ese período. El portal de referencia lo trata como lista vacía, no como error visible al usuario. |
400 |
Parámetros inválidos. |
GET /me/payroll/uploaded/history
Sección titulada «GET /me/payroll/uploaded/history»Historial completo de archivos de roles de pago cargados en un rango de fechas, con paginación y filtro opcional por estado de firma.
Query params:
| Param | Tipo | Obligatorio | Descripción |
|---|---|---|---|
startDate |
string | Sí | Fecha de inicio (YYYY-MM-DD). |
endDate |
string | Sí | Fecha de fin (YYYY-MM-DD). |
signed |
boolean | No | true = sólo firmados, false = sólo sin firmar, omitir = todos. |
page |
number | No | Página, base 1. Default 1. |
limit |
number | No | Registros por página. Default 10. |
Response:
{ "items": [ { "type": "SUELDO", "name": "12345678", "url": "https://twiins.twiinshrm.com/talento_upload/roles/202601/uuid.pdf", "year": 2026, "month": 1, "isSigned": true, "signatureDate": "2026-02-08T15:06:29.000Z" } ], "meta": { "totalItems": 24, "itemCount": 10, "itemsPerPage": 10, "totalPages": 3, "currentPage": 1 }}Ver la convención de paginación en Convenciones.
Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
No hay archivos cargados en el rango. |
400 |
Parámetros inválidos. |
GET /me/payroll/generate
Sección titulada «GET /me/payroll/generate»Genera y devuelve la URL del comprobante en PDF de un rol de pago, a través del servicio legacy. Sirve tanto para roles calculados (Ecuador, Perú) como para roles precargados (Argentina) — el backend resuelve internamente cuál de los dos escenarios aplica.
Query params:
| Param | Tipo | Obligatorio | Descripción |
|---|---|---|---|
year |
number | Sí | Año del rol. |
month |
number | Sí | Mes del rol (1-12). |
cutoff |
string | No | Sólo aplica a roles precargados: SUELDO, AGUINALDO, 1 QUINCENA, 2 QUINCENA, OTROS. Se ignora en roles calculados. Sin cutoff, retorna SUELDO o el primer archivo disponible. |
mode |
string | No | preview (sólo visualización, no exige firma previa) o download (default). |
Response:
{ "url": "https://s3.us-east-1.amazonaws.com/bucket/roles/202606/file-identifier.pdf?X-Amz-Algorithm=..." }url puede venir null si el servicio legacy no encontró el rol.
Si el rol precargado ya fue firmado con código QR (signatureType = HD), la URL retornada apunta al PDF con el QR ya estampado — no hace falta llamar download-qr aparte para obtenerlo.
Errores específicos:
| Código | Cuándo ocurre |
|---|---|
400 |
Error del servicio legacy, o no se encontró el rol para el cutoff solicitado. |
403 |
Rol precargado sin firmar, solicitado con mode=download y cutoff. |
401 |
Token inválido. |
500 |
Error interno. |
GET /me/payroll/signature-config
Sección titulada «GET /me/payroll/signature-config»Configuración de firma del tenant: si está habilitada, con qué tipo y si genera el PDF por el sistema propio.
Sin query params.
Response (SignatureConfigDto):
| Campo | Tipo | Descripción |
|---|---|---|
enabled |
boolean | Si la firma de roles está habilitada para el tenant. |
type |
enum | SA (aceptación simple), HD (QR/OTP), VD, VDBIO, M-VD, M-VDBIO, VDSTAMP. Ver nota en Resumen sobre alcance. |
generateTwiinsPdf |
boolean | Si el PDF se genera por el sistema propio en vez del legacy. |
deviceId |
string | null | Sólo relevante para firmas biométricas. |
{ "enabled": true, "type": "HD", "generateTwiinsPdf": true, "deviceId": null }Sin errores específicos adicionales a los comunes.
GET /me/payroll/{year}/{month}/signature
Sección titulada «GET /me/payroll/{year}/{month}/signature»Estado de firma de un rol de pago para el período indicado.
Path params: year (number), month (number, 1-12).
Response (PayrollSignatureStatusResponseDto):
{ "signatureStatus": "SIGNED", "signatureDate": "2026-06-30T14:30:00.000Z", "signatureType": "SA", "requiresSignature": true, "observation": "Conforme con el pago recibido"}Misma forma de campos que la tabla de GET /me/payroll (sección anterior).
Sin errores específicos adicionales a los comunes.
POST /me/payroll/{year}/{month}/sign
Sección titulada «POST /me/payroll/{year}/{month}/sign»Firma el rol de pago con aceptación simple (SA).
Path params: year (number), month (number, 1-12).
Body (SignPayrollDto):
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
isConform |
boolean | Sí | true = conforme, false = rechaza el rol. |
observation |
string | No (máx. 300) | Comentario del usuario. El backend no lo exige aunque isConform sea false — es el cliente quien debe pedirlo obligatorio en ese caso (ver Lógica de cliente). |
{ "isConform": false, "observation": "El valor de horas extra no coincide con lo trabajado" }Response (201):
{ "message": "Rol de pago firmado exitosamente" }Errores específicos:
| Código | Cuándo ocurre |
|---|---|
400 |
El rol ya fue firmado, o datos inválidos. |
404 |
No existe rol de pago para ese período. |
POST /me/payroll/{year}/{month}/qr-sign/init
Sección titulada «POST /me/payroll/{year}/{month}/qr-sign/init»Inicia la firma con código QR/OTP: envía un código de 6 dígitos al correo del usuario.
Path params: year (number), month (number, 1-12).
Body (InitQrSignatureDto):
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
isConform |
boolean | Sí | true = conforme, false = rechaza el rol. |
observation |
string | No (máx. 300) | Comentario. Requerido en la práctica cuando isConform = false (regla de cliente). |
documentType |
string | No (máx. 60) | Tipo de documento a firmar (SUELDO, AGUINALDO, VACACIONES, BONOS Y GRATIFICACIONES, ANEXOS GANANCIAS, etc.). Si se omite, el backend asume SUELDO. |
fileIdentifier |
string | No (máx. 250) | GUID del archivo cargado. Si viene, tiene prioridad sobre documentType. |
{ "isConform": true, "observation": "" }Response (201, QrSignatureResponseDto):
{ "status": "V", "message": "Se envió el código de verificación a su correo electrónico" }Errores específicos:
| Código | Cuándo ocurre |
|---|---|
400 |
El rol ya fue firmado, datos inválidos, o error del servicio legacy. Puede incluir status: "V" en el payload cuando el legacy ya había enviado un código antes — la app debe tratarlo como éxito informativo (ver Lógica de cliente), no como fallo. |
404 |
No existe rol de pago para ese período. |
POST /me/payroll/{year}/{month}/qr-sign/non-conform
Sección titulada «POST /me/payroll/{year}/{month}/qr-sign/non-conform»Firma el rol como no conforme sin pedir código OTP. Aplica a roles precargados (HD) cuando ya se conoce el documentType/fileIdentifier del archivo.
Path params: year (number), month (number, 1-12).
Body (NonConformQrSignatureDto):
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
observation |
string | Sí (máx. 300) | Motivo de la disconformidad. A diferencia de sign e init, aquí el backend sí lo exige (@IsNotEmpty). |
documentType |
string | No (máx. 60) | Igual que en qr-sign/init. |
fileIdentifier |
string | No (máx. 250) | Igual que en qr-sign/init. |
{ "observation": "No estoy conforme con los valores del rol de pagos", "documentType": "SUELDO" }Response (201, QrSignatureResponseDto): misma forma que qr-sign/init.
Errores específicos:
| Código | Cuándo ocurre |
|---|---|
400 |
El rol ya fue firmado, datos inválidos, o error del servicio legacy. |
404 |
No existe rol de pago para ese período. |
POST /me/payroll/{year}/{month}/qr-sign/verify
Sección titulada «POST /me/payroll/{year}/{month}/qr-sign/verify»Verifica el código de 6 dígitos enviado por correo. Si es correcto, completa la firma.
Path params: year (number), month (number, 1-12).
Body (VerifyQrCodeDto):
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
code |
string | Sí | Código de exactamente 6 dígitos. |
documentType |
string | No (máx. 60) | Debe coincidir con el usado en qr-sign/init. |
fileIdentifier |
string | No (máx. 250) | Debe coincidir con el usado en qr-sign/init. |
{ "code": "482913" }Response (200, QrSignatureResponseDto):
{ "status": "CS", "message": "Firma registrada exitosamente" }En caso de código incorrecto:
{ "status": "CE", "message": "Código incorrecto", "attemptsRemaining": 2 }Errores específicos:
| Código | Cuándo ocurre |
|---|---|
400 |
Código inválido, o se alcanzó el máximo de 3 intentos (attemptsRemaining: 0). |
404 |
No existe rol de pago para ese período, o no se inició el proceso de código. |
GET /me/payroll/{year}/{month}/download-qr
Sección titulada «GET /me/payroll/{year}/{month}/download-qr»Descarga el PDF del rol de pago con el código QR de firma embebido. Sólo retorna archivo si el rol ya fue firmado con HD.
Path params: year (number), month (number, 1-12).
Response: binario application/pdf (Content-Disposition: attachment; filename="rol-pago-{username}-{year}-{month}-qr.pdf").
Errores específicos:
| Código | Cuándo ocurre |
|---|---|
400 |
El rol no está firmado, o no fue firmado con código QR. |
404 |
No existe rol de pago para ese período. |
GET /me/payroll/bonuses
Sección titulada «GET /me/payroll/bonuses»Totales de los tres bonos anuales (décimo tercero, décimo cuarto, utilidades) del usuario autenticado.
Query params:
| Param | Tipo | Obligatorio | Descripción |
|---|---|---|---|
year |
number | Sí | Año de los bonos. |
Response — array de:
| Campo | Tipo | Enum | Descripción |
|---|---|---|---|
type |
string | thirdBonus | fourthBonus | utilityBonus |
Tipo de bono. |
workDays |
number | — | Días trabajados considerados en el cálculo. |
totalBonus |
number | — | Monto total del bono. |
observation |
string | null | — | Observación del cálculo. |
startDate, endDate |
date | null | — | Rango considerado. |
fileUrl |
string | null | — | URL del comprobante si ya fue generado antes; si viene null, generarlo con el endpoint siguiente. |
[ { "type": "thirdBonus", "workDays": 365, "totalBonus": 500.0, "observation": "Décimo tercer sueldo", "startDate": "2025-01-01", "endDate": "2025-12-31", "fileUrl": null }, { "type": "utilityBonus", "workDays": 365, "totalBonus": 320.5, "observation": null, "startDate": null, "endDate": null, "fileUrl": null }]Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
No hay datos de bonos anuales para el año solicitado. |
400 |
Parámetros inválidos. |
GET /me/payroll/generate/bonuses/{bonusType}
Sección titulada «GET /me/payroll/generate/bonuses/{bonusType}»Genera y devuelve la URL del comprobante en PDF de un bono anual específico.
Path param: bonusType (thirdBonus | fourthBonus | utilityBonus).
Query params:
| Param | Tipo | Obligatorio | Descripción |
|---|---|---|---|
year |
number | Sí | Año del bono. |
Response:
{ "url": "https://s3.us-east-1.amazonaws.com/bucket/upload_twiins/tenant/roles/bono-utilidades-2025.pdf?..." }Errores específicos:
| Código | Cuándo ocurre |
|---|---|
400 |
Tipo de bono o año inválido. |
404 |
No se encontró el reporte para esos parámetros. |
500 |
Error interno. |
Lógica de cliente
Sección titulada «Lógica de cliente»Selector de período
Sección titulada «Selector de período»- El período por defecto al abrir la pantalla es el último mes cerrado: si el mes actual es enero, se preselecciona diciembre del año anterior; en cualquier otro caso, el mes anterior al actual del mismo año.
- Las opciones de año van desde 2015 hasta el máximo año cerrado (igual regla que el mes). Las opciones de mes se limitan a los meses ya cerrados del año seleccionado — nunca se ofrece el mes en curso.
- Cambiar año o mes dispara de nuevo
GET /me/payroll(yGET /me/payroll/uploadedsi el tenant es de tipouploaded).
Vista estructurada vs. archivos cargados
Sección titulada «Vista estructurada vs. archivos cargados»- La app debe conocer el
Payroll_System_Typedel tenant antes de decidir qué endpoints llamar:calculated/integration-table/integration-ws→ vista de ingresos/egresos (GET /me/payroll,GET /me/payroll/history);uploaded→ vista de archivos (GET /me/payroll/uploaded,GET /me/payroll/uploaded/history). - En un tenant
uploaded, sisignatureEnabled/signature-config.enabledesfalse, la descarga usa directamente el campourldel archivo (abrir en el visor / navegador) y no se llama agenerate.
Cuándo mostrar la acción “Firmar”
Sección titulada «Cuándo mostrar la acción “Firmar”»- Mostrar la acción sólo si
signatureStatus === "PENDING"ysignature-config.enabled === true. Con cualquier otrosignatureStatus(SIGNED,NOT_CONFORM), la acción se reemplaza por el badge de estado. - El selector de método de firma (Simple vs. QR) sólo tiene sentido si el tenant expusiera más de un tipo disponible. Hoy
signature-configsiempre devuelve un únicotypepor tenant, así que en la práctica no hay selección: la app aplica directamente el método indicado portype(SA→ formulario simple,HD→ formulario QR).
Badge de estado de firma (PayrollSignatureStatusEnum)
Sección titulada «Badge de estado de firma (PayrollSignatureStatusEnum)»| Valor | Significado | Texto sugerido | Color sugerido |
|---|---|---|---|
PENDING |
Pendiente de firma | Pendiente | warning / amarillo |
SIGNED |
Firmado, conforme | Firmado | success / verde |
NOT_CONFORM |
Firmado, no conforme | No conforme | error / rojo |
Cuando signatureStatus === "NOT_CONFORM" y hay observation, mostrarla junto al badge (p. ej. en un UAlert de error) — es el motivo de disconformidad que el usuario registró al firmar.
Formulario de firma (conforme / no conforme)
Sección titulada «Formulario de firma (conforme / no conforme)»- Selección binaria: Conforme (default) o No conforme. El campo de observación se muestra siempre, pero sólo es obligatorio en cliente cuando se elige “No conforme” (mínimo un carácter no vacío) — el backend acepta
observationvacío u omitido incluso conisConform: false, salvo enqr-sign/non-conform, donde sí lo exige. - Enviar
observation: null(no cadena vacía) cuando es conforme y el usuario no escribió nada, para mantener consistencia con lo que ya persiste el backend.
Máquina de estados de la firma QR (HD)
Sección titulada «Máquina de estados de la firma QR (HD)»Fases del flujo en el cliente: init → verify → success / error.
init: se pide conformidad + observación y se llamaPOST .../qr-sign/init. Sistatus === "V", pasar averify. Si la respuesta de error traestatus: "V"(el legacy ya había enviado un código antes), tratarlo igual que un envío exitoso y pasar averify— no es un fallo.verify: el usuario ingresa el código de 6 dígitos y se llamaPOST .../qr-sign/verify. Máximo 3 intentos. Cada error traeattemptsRemaining; cuando llega a0, deshabilitar el input y el botón “Verificar”, y mostrar sólo la opción “Reenviar código” (que vuelve a llamarqr-sign/init, reiniciando el contador a 3).success:status === "CS"en la verificación. Mostrar confirmación y refrescar los datos del rol (GET /me/payrolloGET /me/payroll/historysegún la vista activa) para reflejar el nuevosignatureStatus.error: errores críticos (red, 5xx) — mostrar el error y permitir reintentar desdeinit.
No conforme sin OTP (sólo roles precargados)
Sección titulada «No conforme sin OTP (sólo roles precargados)»Regla no evidente y verificada contra el código del portal: cuando el usuario marca “No conforme” en el paso init y la app conoce el archivo específico que está firmando (tiene documentType o fileIdentifier — esto sólo ocurre al firmar un rol precargado, uploaded), el cliente omite el flujo de OTP por completo y llama directamente a POST .../qr-sign/non-conform. Para roles calculados (vista estructurada, sin documentType/fileIdentifier), la disconformidad sí pasa por el flujo normal de código (qr-sign/init → qr-sign/verify), igual que la conformidad.
Descarga de PDF
Sección titulada «Descarga de PDF»- Con firma habilitada: usar siempre
GET /me/payroll/generate(conmode=downloadpor defecto). El backend ya decide si el PDF debe llevar el QR estampado según elsignatureTyperegistrado. - Con firma deshabilitada: usar directamente el campo
url(vista estructurada) ofileUrl/urldel archivo (vista de archivos cargados) — no llamar agenerate. - Para previsualizar un rol precargado antes de firmarlo (p. ej. abrirlo en un visor dentro del flujo de firma), pedirlo con
mode=previewy elcutoffcorrespondiente altypedel archivo — conmode=downloadel backend rechaza la descarga de un archivo aún sin firmar (403).
Tipos de documento (documentType) en la firma QR
Sección titulada «Tipos de documento (documentType) en la firma QR»Valores observados en las descripciones del DTO (campo de texto libre, máx. 60 caracteres, no es un enum cerrado en el backend): SUELDO, AGUINALDO, VACACIONES, BONOS Y GRATIFICACIONES, ANEXOS GANANCIAS. Si se omite, el backend asume SUELDO. Para roles precargados, usar el type del archivo (UploadedPayrollFileResponseDto.type); para roles calculados, omitir el campo.
Tipo de rol cargado (UploadedPayrollFileResponseDto.type)
Sección titulada «Tipo de rol cargado (UploadedPayrollFileResponseDto.type)»| Valor | Significado |
|---|---|
SUELDO |
Salario mensual. |
AGUINALDO |
Bono anual complementario (SAC). |
1 QUINCENA |
Primera quincena del mes. |
2 QUINCENA |
Segunda quincena del mes. |
OTROS |
Otros tipos de pago. |
Estado de respuesta de la firma QR (QrSignatureResponseDto.status)
Sección titulada «Estado de respuesta de la firma QR (QrSignatureResponseDto.status)»| Valor | Significado |
|---|---|
V |
Verificado / código enviado — pasar a la pantalla de ingreso de código. |
CS |
Firma correcta — proceso completado. |
CE |
Código erróneo — mostrar error inline y descontar un intento. |
NV |
Proceso no válido o expirado. |
I |
Reinicializado (se alcanzó el máximo de intentos); en la práctica el backend reporta esto como attemptsRemaining: 0, no como un status distinto a manejar aparte. |
Estados vacíos y casos borde
Sección titulada «Estados vacíos y casos borde»- Sin rol de pago para el período (
GET /me/payroll→404): mostrar tarjeta vacía (“No hay datos para este período”), no un error genérico. - Sin roles cargados para el período/rango (
uploaded,uploaded/history→404): tratar como lista vacía, no como error visible. - Firma deshabilitada para el tenant: ocultar toda acción de firma; la columna de estado de firma en las tablas de historial tampoco se muestra.
- Intento de descarga de un rol precargado sin firmar con
mode=download:403— la app debe haber ocultado o deshabilitado la acción de descarga directa para archivos no firmados y ofrecer en su lugar “Firmar” o, si necesita previsualizarlo, pedirlo conmode=preview. - Bonos sin comprobante generado todavía (
fileUrl: nullenGET /me/payroll/bonuses): mostrar el bono igual, con la acción “Descargar” apuntando aGET /me/payroll/generate/bonuses/{bonusType}bajo demanda.
Los errores genéricos (401, formato de paginación, etc.) siguen la convención documentada en Convenciones.