Capacitación
Resumen
Sección titulada «Resumen»El módulo cubre el flujo de autoservicio de capacitación del empleado autenticado: ver los cursos con evaluación activa, estudiar su contenido, rendir el quiz (por curso completo o por capítulo, según la configuración del curso), consultar la malla curricular asignada a su cargo, revisar su historial de capacitaciones y descargar certificados. También incluye la gestión de “procedimientos” (documentos de inducción/procedimiento que requieren registrar una fecha de capacitación).
Acciones cubiertas:
- Ver cursos con evaluación activa asignados al usuario.
- Ver el detalle de un curso activo (capítulos, archivos/videos, objetivo, material).
- Marcar un archivo/video de un capítulo como visto (requisito para poder rendir el quiz del capítulo).
- Rendir el quiz de un curso (evaluación general o por capítulo, según el curso) y ver el resultado.
- Ver la malla curricular (capacitaciones asignadas al cargo del usuario, con o sin evaluación).
- Ver el detalle de una capacitación de la malla curricular.
- Ver el historial de capacitaciones (evaluaciones en línea, presenciales, cursos virtuales) con filtro por tipo.
- Descargar el certificado de una capacitación del historial.
- Ver y registrar procedimientos de inducción asignados al cargo del usuario.
Prerrequisitos:
- La malla curricular (
curricular-mesh) y los procedimientos (procedures) requieren que el usuario tenga información organizacional activa (cargo). Sin ella, el backend rechaza esos endpoints con un error genérico (ver Convenciones). - La descarga de certificado (
history/certificate) depende de un servicio legacy de generación de documentos; puede fallar con400si ese servicio no responde correctamente.
sequenceDiagram
participant App as App móvil
participant API as th-core-api
Note over App,API: Tab Cursos activos
App->>API: GET /me/training/active
API-->>App: cursos con evaluación activa (o 404 si no hay ninguno)
Note over App,API: Usuario abre el detalle de un curso
App->>API: GET /me/training/active/:trainingId/evaluation/:evaluationId
API-->>App: capítulos, archivos, evaluaciones (con couldEvaluate por capítulo)
App->>API: PUT /me/training/active/file
Note over App: al abrir cada archivo/video del capítulo
API-->>App: confirmación (marca el archivo como visto)
Note over App,API: Usuario inicia el quiz (curso completo o de un capítulo)
App->>API: GET /me/training/active/:trainingId/evaluation/:evaluationId/quiz
API-->>App: preguntas y respuestas (mezcladas), datos del examen
App->>API: POST /me/training/active/:trainingId/evaluation/:evaluationId/quiz
API-->>App: resultado (approval, score, isFinalEvaluation)
App->>API: GET /me/training/active/:trainingId/evaluation/:evaluationId
API-->>App: refresco del detalle (couldEvaluate/attempts actualizados)
Note over App,API: Tab Malla curricular
App->>API: GET /me/training/curricular-mesh
API-->>App: capacitaciones asignadas al cargo del usuario
App->>API: GET /me/training/curricular-mesh/:id
Note over App: al abrir el detalle de una capacitación
API-->>App: capítulos, archivos y cargos recomendados
Note over App,API: Tab Historial
App->>API: GET /me/training/history/:type
API-->>App: historial paginado, filtrable por tipo
App->>API: GET /me/training/history/certificate/:type/:id
Note over App: acción Ver certificado, solo si hasCertificate = true
API-->>App: url del PDF generado
Note over App,API: Tab Procedimientos
App->>API: GET /me/training/procedures
API-->>App: procedimientos asignados al cargo del usuario
App->>API: GET /me/training/procedures/:id
Note over App: al abrir el detalle de un procedimiento
API-->>App: detalle completo del procedimiento
App->>API: PUT /me/training/procedures
Note over App: solo si el procedimiento aun no tiene fecha registrada
API-->>App: procedimiento actualizado
Endpoints
Sección titulada «Endpoints»GET /me/training/active
Sección titulada «GET /me/training/active»Cursos con evaluación activa asignados al usuario autenticado (usuario o cargo).
Query params:
| Param | Tipo | Obligatorio | Default | Descripción |
|---|---|---|---|---|
page |
number | No | 1 |
Página a consultar. |
limit |
number | No | 5 |
Elementos por página. |
Response — paginado, cada item es una evaluación de curso:
| Campo | Tipo | Descripción |
|---|---|---|
id |
number | Id de la evaluación (evaluationId en el resto de los endpoints). |
trainingId |
number | Id del curso (virtualTraining.id). |
referenceType |
enum VirtualTrainingEvaluationReferenceTypeEnum |
C Capacitación, S Selección, I Inducción. |
referenceId |
number | Id de referencia del proceso origen (-1 si no aplica). |
userType |
enum | U asignado por usuario, C asignado por cargo. |
directedTo |
enum | C quienes lo requiere su cargo, E usuarios específicos, T todos. |
studentStartDate / studentEndDate |
date | Período habilitado para estudiar el contenido. |
finalValidityPeriod |
S / N |
Si S, la vigencia final del curso aplica reglas adicionales (no relevantes para mobile). |
examStartDate / examEndDate |
date | Período habilitado para rendir el quiz. |
examQuestionCount |
number | Cantidad de preguntas del examen. |
examDurationMinutes |
number | Duración máxima del examen, en minutos. |
examAttempts |
number | Intentos máximos permitidos. |
minimumScorePercentage |
number | Nota mínima de aprobación (%). |
passApproach |
S / N |
Si S, la nota mínima real es minimumScorePercentage + 1 (ver Lógica de cliente). |
questionWeight |
S / N |
Si S, las preguntas del examen se distribuyen proporcionalmente por capítulo. |
chapterEvaluation |
S / N |
Si S, el curso se evalúa por capítulo (generalEvaluation = false); si N, es una única evaluación general. |
orderedResponses |
A / O |
A = respuestas mezcladas aleatoriamente, O = orden fijo. |
virtualTraining |
object | { id, subcategoryId, certificateFormatId, trainingId, name, description, objective, archive, image, link }. |
userEvaluations |
array | Evaluaciones del usuario para este curso — ver tabla siguiente. |
userEvaluations[]:
| Campo | Tipo | Descripción |
|---|---|---|
evaluationId |
number | Id de la evaluación. |
studyStartDate / studyEndDate |
date | null | Fechas de estudio del usuario. |
studyStatus |
string | A = activo. |
evaluationDate |
date | null | Fecha en que rindió el quiz. |
correctQuestionsCount / incorrectQuestionsCount / unansweredQuestionsCount |
number | null | Detalle de respuestas del último intento. |
result |
number | null | Nota obtenida (%). |
numberOfTries |
number | Intentos ya usados. |
approval |
S / N / null |
null = aún no rindió el quiz. |
{ "items": [ { "id": 4521, "trainingId": 363, "referenceType": "C", "referenceId": -1, "userType": "U", "directedTo": "T", "studentStartDate": "2026-06-01", "studentEndDate": "2026-08-31", "finalValidityPeriod": "S", "examStartDate": "2026-06-01", "examEndDate": "2026-08-31", "examQuestionCount": 10, "examDurationMinutes": 30, "examAttempts": 3, "minimumScorePercentage": 70, "passApproach": "S", "questionWeight": "N", "chapterEvaluation": "N", "orderedResponses": "A", "virtualTraining": { "id": 363, "subcategoryId": 62, "certificateFormatId": 1, "trainingId": "SSA-05", "name": "PLAN DE EMERGENCIAS", "description": "", "objective": "Dar a conocer a los colaboradores el plan de emergencia anual de la compañía.", "archive": "", "image": "", "link": "" }, "userEvaluations": [ { "evaluationId": 4521, "studyStartDate": "2026-07-10T14:02:00.000Z", "studyEndDate": null, "studyStatus": "A", "evaluationDate": null, "correctQuestionsCount": null, "incorrectQuestionsCount": null, "unansweredQuestionsCount": null, "result": null, "token": null, "numberOfTries": 0, "approval": null } ] } ], "meta": { "totalItems": 1, "itemCount": 1, "itemsPerPage": 5, "totalPages": 1, "currentPage": 1 }}Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
El usuario no tiene evaluaciones de capacitación activas ("No se encontraron capacitaciones activas"). Tratar como estado vacío, no como error de red. |
GET /me/training/active/:trainingId/evaluation/:evaluationId
Sección titulada «GET /me/training/active/:trainingId/evaluation/:evaluationId»Detalle completo de un curso activo: capítulos con su contenido/archivos y las evaluaciones asociadas.
Path params: trainingId (number), evaluationId (number).
Response:
| Campo | Tipo | Descripción |
|---|---|---|
id / trainingId / name / description / objective / archive / image / link |
— | Datos del curso (trainingId aquí es el código string, ej. "SSA"). |
status |
string | Estado del curso (A activo). |
chapters |
TrainingChapterDto[] |
Ver tabla siguiente. |
evaluations |
TrainingEvaluationDto[] |
Ver tabla siguiente. |
chapters[]:
| Campo | Tipo | Descripción |
|---|---|---|
trainingId |
number | Id del curso. |
id |
number | Id del capítulo. |
name |
string | Nombre del capítulo. |
couldEvaluate |
boolean | true sólo si el capítulo tiene archivos y el usuario vio todos. Habilita el botón “Rendir quiz del capítulo” cuando chapterEvaluation = S. Un capítulo sin archivos nunca queda en true (ver Lógica de cliente). |
numberOfTries |
number | Intentos ya usados en el quiz de este capítulo. |
text |
string | Contenido HTML del capítulo (sanitizado). |
userChapterEvaluations |
array | Historial de intentos del capítulo: { evaluationDate, attempts, result, approval, status }. |
files |
ChapterFileDto[] |
Ver tabla siguiente. |
chapters[].files[]:
| Campo | Tipo | Descripción |
|---|---|---|
chapterId |
number | Id del capítulo dueño. |
id |
number | Id del archivo. |
type |
D | V | U | A |
Documento / Video / URL / Audio. |
name |
string | Nombre visible. |
file |
string | Ruta interna del archivo (si aplica; usar junto al repositorio de storage para resolver la URL). |
link |
string | URL externa directa (ej. YouTube); si viene vacío, usar file. |
order |
number | Orden de presentación. |
userViewed |
boolean | Si el usuario ya vio este archivo (userViews.length > 0). |
userViews |
array | { viewedDate }[], historial de vistas. |
evaluations[]:
| Campo | Tipo | Descripción |
|---|---|---|
id |
number | Id de la evaluación (evaluationId). |
trainingId |
number | Id del curso. |
studentStartDate / studentEndDate / examStartDate / examEndDate |
date | Ver tabla de GET active. |
ValidityPeriod |
string | (nótese la mayúscula inicial, viene así del backend) Vigencia final. |
examDurationMinutes / examAttempts / minimumScorePercentage |
number | Ver tabla de GET active. |
chapterEvaluation |
S / N |
Igual que en GET active. |
generalEvaluation |
boolean | true cuando chapterEvaluation = N (una sola evaluación para todo el curso). Calculado por el backend. |
numberOfTries |
number | Intentos usados por el usuario (tomado de userEvaluations[0]). |
userEvaluations |
array | Igual forma que en GET active, más status. |
{ "id": 363, "trainingId": "SSA", "name": "SALUD PREVENTIVA VOLUMEN 1", "description": "PREVENCIÓN DE ENFERMEDADES CARDIOVASCULARES", "objective": "", "archive": "", "image": "", "link": "", "status": "A", "chapters": [ { "trainingId": 363, "id": 144, "name": "MODULO 1. PREVENCIÓN DE ACCIDENTE CEREBRO VASCULAR", "couldEvaluate": true, "numberOfTries": 0, "text": "", "userChapterEvaluations": [], "files": [ { "chapterId": 144, "id": 179, "type": "U", "name": "PREVENCIÓN DE ENFERMEDADES CARDIOVASCULARES", "file": "", "link": "https://www.youtube.com/watch?v=V263s5BCjsE", "order": 0, "userViewed": true, "userViews": [{ "viewedDate": "2026-07-10T14:05:00.000Z" }] } ] } ], "evaluations": [ { "id": 4521, "trainingId": 363, "studentStartDate": "2026-06-01", "studentEndDate": "2026-08-31", "ValidityPeriod": "S", "examStartDate": "2026-06-01", "examEndDate": "2026-08-31", "examDurationMinutes": 30, "examAttempts": 3, "minimumScorePercentage": 70, "chapterEvaluation": "N", "generalEvaluation": true, "numberOfTries": 0, "userEvaluations": [] } ]}Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
El curso/evaluación no existe ("Capacitación no encontrada"). |
400 |
El período de estudio y de examen de la evaluación consultada ya finalizaron ("El período de estudio y evaluación ha finalizado"). Sólo aplica si ambas fechas (studentEndDate y examEndDate) están definidas y ya pasaron. |
GET /me/training/active/:trainingId/evaluation/:evaluationId/quiz
Sección titulada «GET /me/training/active/:trainingId/evaluation/:evaluationId/quiz»Genera el set de preguntas para rendir el quiz (mezcladas y, opcionalmente, filtradas por capítulo). Se debe llamar cada vez que el usuario abre el quiz — las preguntas y el orden de las respuestas cambian en cada llamada.
Path params: trainingId (number), evaluationId (number).
Query params:
| Param | Tipo | Obligatorio | Descripción |
|---|---|---|---|
chapterId |
number | Sólo si chapterEvaluation = S |
Capítulo a evaluar. Si el curso evalúa por capítulo y no se envía, el backend responde 400. Omitir si chapterEvaluation = N (evaluación general). |
Response:
| Campo | Tipo | Descripción |
|---|---|---|
questionsAndAnswers |
QuizQuestionDto[] |
Preguntas a mostrar, ya mezcladas/filtradas/limitadas por el backend. |
evaluationComplete |
object | Metadatos del examen: { id, trainingId, studentStartDate, studentEndDate, finalValidityPeriod, examStartDate, examEndDate, examQuestionCount, examDurationMinutes, examAttempts, minimumScorePercentage, chapterEvaluation }. |
questionsAndAnswers[]:
| Campo | Tipo | Descripción |
|---|---|---|
id |
number | Id de la pregunta. |
type |
enum VirtualTrainingQuestionTypeEnum |
SINGLE (una respuesta), MULTIPLE (varias), OPEN (texto libre). |
text |
string | Enunciado. |
file |
string | Archivo/imagen asociado a la pregunta (puede venir vacío). |
answers |
QuizAnswerDto[] |
{ questionId, id, text, type: "TEXT", image } — nunca incluye cuál es la correcta. |
{ "questionsAndAnswers": [ { "id": 1293, "type": "SINGLE", "text": "Un accidente cerebrovascular sucede cuando el flujo de sangre al cerebro se detiene por pocos segundos", "file": "", "answers": [ { "questionId": 1293, "id": 3631, "text": "VERDADERO", "type": "TEXT", "image": "" }, { "questionId": 1293, "id": 3632, "text": "FALSO", "type": "TEXT", "image": "" } ] } ], "evaluationComplete": { "id": 683, "trainingId": 273, "studentStartDate": "2026-06-01", "studentEndDate": "2026-08-31", "finalValidityPeriod": "YES", "examStartDate": "2026-06-01", "examEndDate": "2026-08-31", "examQuestionCount": 10, "examDurationMinutes": 30, "examAttempts": 3, "minimumScorePercentage": 70, "chapterEvaluation": "NOT" }}Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
La evaluación no existe, o el usuario no está asignado a ella ("Evaluation not found" / "Evaluation user not found"). |
400 |
Fuera del período de examen: "Evaluation has not started yet" o "Evaluation has ended" (según examStartDate/examEndDate). |
400 |
El usuario ya agotó los intentos permitidos ("You have reached the maximum number of attempts"). Comparar numberOfTries vs examAttempts en el cliente para deshabilitar la acción antes de llamar. |
400 |
chapterEvaluation = S y no se envió chapterId ("Chapter ID is required"). |
POST /me/training/active/:trainingId/evaluation/:evaluationId/quiz
Sección titulada «POST /me/training/active/:trainingId/evaluation/:evaluationId/quiz»Envía las respuestas del usuario y calcula el resultado. Usa los mismos path params y el mismo query param opcional chapterId que el GET — debe coincidir con el quiz que se le mostró al usuario (mismo capítulo o evaluación general).
Body (SaveUserEvaluationQuizDto):
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
answers |
Record<number, string> |
Sí | Mapa questionId -> respuesta. Para SINGLE/OPEN: el valor como string ("3631" o el texto libre). Para MULTIPLE: ids separados por coma ("3631, 3632"). Preguntas sin respuesta: no es necesario omitirlas, pero si se envían vacías el backend las normaliza a "-1" (no contestada). |
Response (objeto plano, sin envoltorio de paginación):
| Campo | Tipo | Descripción |
|---|---|---|
approval |
A / N |
A = aprobado, N = no aprobado (para el capítulo o la evaluación general, según el modo). |
status |
A / F |
A = activo/pendiente (le quedan intentos y no aprobó), F = finalizado. |
result |
number | Nota obtenida (%), sobre las preguntas respondidas (no sobre el total del capítulo). |
totalCorrectQuestions / totalIncorrectQuestions / totalUnansweredQuestions |
number | Conteo del intento. En evaluación por capítulo, son los acumulados de todos los capítulos ya presentados, no sólo el actual. |
isFinalEvaluation |
boolean | true cuando ya no hay más capítulos pendientes de evaluar (o el curso no es por capítulos). Usar para decidir si mostrar el resultado como definitivo del curso o como resultado parcial de un capítulo. |
{ "approval": "A", "status": "F", "result": 90, "totalCorrectQuestions": 9, "totalIncorrectQuestions": 1, "totalUnansweredQuestions": 0, "isFinalEvaluation": true}Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
Evaluación no encontrada, o (en modo por capítulo) el chapterId no pertenece al curso ("Chapter not valid"). |
400 |
Mismas validaciones de fechas/intentos que el GET del quiz (fuera de período, intentos agotados). |
400 |
answers contiene un questionId que no pertenece a esta evaluación, o un id de respuesta que no pertenece a la pregunta. |
PUT /me/training/active/file
Sección titulada «PUT /me/training/active/file»Marca un archivo/video de un capítulo como visto por el usuario. Debe llamarse cada vez que el usuario abre el material de un archivo (antes o al mismo tiempo que se abre el enlace), no sólo la primera vez — es idempotente.
Body (CreateRegisterUserViewFileDto):
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
trainingId |
number | Sí | Id del curso. |
evaluationId |
number | Sí | Id de la evaluación del curso. |
chapterId |
number | Sí | Id del capítulo dueño del archivo. |
fileId |
number | Sí | Id del archivo marcado como visto. |
Response: true (boolean).
Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
El curso/evaluación (trainingId/evaluationId) no existe para este usuario — el endpoint valida esto internamente antes de registrar la vista, reutilizando la misma validación que GET active/:trainingId/evaluation/:evaluationId. |
400 |
El período de estudio y evaluación del curso ya finalizó (misma regla que el GET de detalle). |
GET /me/training/curricular-mesh
Sección titulada «GET /me/training/curricular-mesh»Malla curricular: capacitaciones (con o sin evaluación formal) asignadas al cargo del usuario autenticado.
Query params:
| Param | Tipo | Obligatorio | Default | Descripción |
|---|---|---|---|---|
page |
number | No | 1 |
Página a consultar. |
limit |
number | No | 5 |
Elementos por página. |
Response — paginado:
| Campo | Tipo | Descripción |
|---|---|---|
id |
number | Id de la capacitación. |
name / description / objective / archive / image / link |
— | Datos del curso. |
modality |
enum TrainingModalityEnum |
PRESENTIAL si link está vacío, E_LEARNING en caso contrario. Calculado por el backend, no persistido. |
subcategory |
object | { name, category: { name, type: { name, description } } }. |
trainingDetails |
object | Ver tabla siguiente — resumen del avance del usuario en esta capacitación. |
trainingDetails:
| Campo | Tipo | Descripción |
|---|---|---|
trainingId |
number | Id de la capacitación. |
approvalStatus |
enum UserHistoryTrainingApprovalStatusEnum |
APPROVED, NOT_APPROVED, PENDING, NOT_REQUIRED. Ver tabla de estados en Lógica de cliente. |
type |
string | Tipo de capacitación. |
score |
number | Nota obtenida (si aplica). |
registrationDate |
date | Fecha de inscripción/registro. |
{ "items": [ { "id": 363, "name": "PLAN DE EMERGENCIAS", "description": "", "objective": "Dar a conocer a los colaboradores el plan de emergencia anual de la compañía.", "archive": "", "image": "", "link": "", "modality": "PRESENTIAL", "subcategory": { "name": "SEGURIDAD Y SALUD", "category": { "name": "GESTION DE TALENTO HUMANO", "type": { "name": "GENERAL" } } }, "trainingDetails": { "trainingId": 363, "approvalStatus": "PENDING", "type": "GENERAL", "score": 0, "registrationDate": "2026-06-01T00:00:00.000Z" } } ], "meta": { "totalItems": 1, "itemCount": 1, "itemsPerPage": 5, "totalPages": 1, "currentPage": 1 }}Sin errores específicos adicionales a los comunes (requiere información organizacional, ver Prerrequisitos).
GET /me/training/curricular-mesh/:id
Sección titulada «GET /me/training/curricular-mesh/:id»Detalle de una capacitación de la malla curricular: capítulos con su contenido y cargos recomendados.
Path param: id (number).
Response:
| Campo | Tipo | Descripción |
|---|---|---|
id |
number | Id de la capacitación. |
trainingId |
string | Código string del curso. |
name / description / link |
— | Datos del curso. |
modality |
enum TrainingModalityEnum |
PRESENTIAL si no tiene link ni capítulos; E_LEARNING en caso contrario. |
trainingPositions |
string[] | Nombres de los cargos recomendados, ya limitados a un máximo de 3 y sin duplicados (el backend deduplica por nombre de cargo). |
countRemainingPositions |
number | Cantidad de cargos adicionales no incluidos en trainingPositions (para un texto tipo “+N cargos más”). |
chapters |
ChapterDto[] |
{ trainingId, id, name, text, content } — text viene sanitizado (HTML seguro). |
{ "id": 363, "trainingId": "SSA-05", "name": "PLAN DE EMERGENCIAS", "description": "", "modality": "PRESENTIAL", "link": "", "trainingPositions": ["Analista de Nómina", "Analista de RRHH", "Asistente Administrativo"], "countRemainingPositions": 4, "chapters": [ { "trainingId": 363, "id": 144, "name": "MODULO 1. PROTOCOLO DE EVACUACIÓN", "text": "<p>Contenido del capítulo...</p>", "content": "" } ]}Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
La capacitación no existe ("Capacitación no encontrada"). |
GET /me/training/history/:type
Sección titulada «GET /me/training/history/:type»Historial de capacitaciones del usuario, filtrable por tipo.
Query params:
| Param | Tipo | Obligatorio | Default | Descripción |
|---|---|---|---|---|
page |
number | No | 1 |
Página a consultar. |
limit |
number | No | 5 |
Elementos por página. |
Response — paginado:
| Campo | Tipo | Descripción |
|---|---|---|
approvalStatus |
enum UserHistoryTrainingApprovalStatusEnum |
Ver tabla de estados en Lógica de cliente. |
score |
number | Nota obtenida. |
registrationDate |
date | Fecha de registro/rendición. |
name |
string | Nombre de la capacitación. |
code |
number | Id interno de la capacitación (curso/evaluación/capacitación real, según referenceType). |
description |
string | Descripción. |
type |
string | Tipo de capacitación (catálogo). |
category / subcategory |
string | Categoría/subcategoría. |
hasCertificate |
boolean | true sólo si la capacitación tiene formato de certificado configurado y el usuario está APPROVED. Controla si se muestra la acción “Ver certificado”. |
referenceId |
number | Id a usar en history/certificate/:type/:id. |
referenceType |
EV | CA | CU |
Tipo real de referencia (código corto del backend, no confundir con el :type de la URL). |
realTrainingEvaluation |
S | N | U | null |
Sólo para referenceType = CA: cuantitativa/abierta/cualitativa. |
{ "items": [ { "approvalStatus": "APPROVED", "score": 90, "registrationDate": "2026-06-15", "name": "SALUD PREVENTIVA VOLUMEN 1", "code": 273, "description": "PREVENCIÓN DE ENFERMEDADES CARDIOVASCULARES", "type": "GENERAL", "category": "GESTION DE TALENTO HUMANO", "subcategory": "GESTION DE TALENTO HUMANO", "hasCertificate": true, "referenceId": 683, "referenceType": "EV" } ], "meta": { "totalItems": 1, "itemCount": 1, "itemsPerPage": 5, "totalPages": 1, "currentPage": 1 }}Errores específicos:
| Código | Cuándo ocurre |
|---|---|
400 |
:type no coincide con ninguna clave del enum ("Invalid type: {valor enviado}"). |
GET /me/training/history/certificate/:type/:id
Sección titulada «GET /me/training/history/certificate/:type/:id»Genera y devuelve la URL del certificado PDF de una capacitación del historial.
Path params:
| Param | Tipo | Descripción |
|---|---|---|
type |
string | Misma convención de clave de enum que en history/:type — usar referenceType del ítem del historial mapeado a su clave (EV → EVALUATION, CA → REAL_TRAINING). No usar ALL aquí. |
id |
number | referenceId del ítem del historial. |
Response: { "url": "https://.../certificate.pdf" } (URL prefirmada de S3, expira en 1 hora).
{ "url": "https://<bucket>.s3.amazonaws.com/capacitacion/certificadosgenerados/jperez-683-Certificado_SSA.pdf?X-Amz-Expires=3600&..."}Errores específicos:
| Código | Cuándo ocurre |
|---|---|
400 |
El servicio legacy de generación de certificados falló ("Failed to generate training certificate: ..."). Tratar como error transitorio; reintentar u ofrecer soporte, no asumir que el certificado no existe. |
GET /me/training/procedures
Sección titulada «GET /me/training/procedures»Procedimientos de inducción asignados al cargo del usuario autenticado, con indicador de si ya fueron registrados.
Query params:
| Param | Tipo | Obligatorio | Default | Descripción |
|---|---|---|---|---|
page |
number | No | 1 |
Página a consultar. |
limit |
number | No | 10 |
Elementos por página. |
Response — paginado:
| Campo | Tipo | Descripción |
|---|---|---|
id |
number | Id del procedimiento. |
identifier |
string | Código identificador del procedimiento. |
name |
string | Nombre del procedimiento. |
userName |
string | Nombre del dueño (fullName del usuario asociado, o cadena vacía). |
inductionDate |
date | null | Fecha en que el usuario registró haber recibido la inducción. null = pendiente. |
url |
string | URL del material del procedimiento. |
hasNote |
boolean | true si el usuario ya registró una fecha de inducción (tiene al menos un userProcedure). |
{ "items": [ { "id": 51, "identifier": "PROC-INDUC-05", "name": "PROTOCOLO DE SEGURIDAD EN PLANTA", "userName": "Juan Pérez", "inductionDate": null, "url": "https://cdn.twiinshrm.com/procedures/proc-induc-05.pdf", "hasNote": false } ], "meta": { "totalItems": 1, "itemCount": 1, "itemsPerPage": 10, "totalPages": 1, "currentPage": 1 }}Sin errores específicos adicionales a los comunes (requiere información organizacional, ver Prerrequisitos).
GET /me/training/procedures/:id
Sección titulada «GET /me/training/procedures/:id»Detalle completo de un procedimiento, incluyendo datos del dueño y período de vigencia.
Path param: id (number).
Response:
| Campo | Tipo | Descripción |
|---|---|---|
id / identifier / name / description |
— | Datos del procedimiento. |
user |
object | null | { fullName, position, avatar } del dueño/responsable. |
startDate / endDate |
date | Período de vigencia del procedimiento. |
modificationDate |
date | Última fecha de modificación. |
inductionDate |
date | null | Fecha registrada por el usuario (null = pendiente). |
url |
string | URL del material. |
isEvaluated |
boolean | true si el usuario ya registró la inducción (mismo criterio que hasNote en el listado). |
{ "id": 51, "identifier": "PROC-INDUC-05", "name": "PROTOCOLO DE SEGURIDAD EN PLANTA", "description": "Procedimiento de seguridad para personal de planta.", "user": { "fullName": "María Rojas", "position": "Jefe de Seguridad Industrial", "avatar": "https://cdn.twiinshrm.com/avatars/mrojas.png" }, "startDate": "2026-01-01T00:00:00.000Z", "endDate": "2026-12-31T00:00:00.000Z", "modificationDate": "2026-06-01T00:00:00.000Z", "inductionDate": null, "url": "https://cdn.twiinshrm.com/procedures/proc-induc-05.pdf", "isEvaluated": false}Sin errores específicos adicionales a los comunes.
PUT /me/training/procedures
Sección titulada «PUT /me/training/procedures»Registra la fecha en que el usuario recibió la inducción de un procedimiento. Sólo puede registrarse una vez.
Body (UpdateInductionDateProcedureDto):
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
id |
number | Sí | Id del procedimiento. |
inductionDate |
date | Sí | Fecha en que el usuario recibió la inducción. El portal de referencia la envía como YYYY-MM-DD. |
Response: el procedimiento actualizado, misma forma que GET /me/training/procedures/:id.
Errores específicos:
| Código | Cuándo ocurre |
|---|---|
404 |
El procedimiento no existe ("Procedure not found"). |
400 |
El usuario ya había registrado una fecha de inducción para este procedimiento ("Induction date procedure found"). El cliente debe evitar este caso ocultando la acción cuando hasNote/isEvaluated = true (ver Lógica de cliente). |
GET /training-type
Sección titulada «GET /training-type»Catálogo global de tipos de capacitación (no es un endpoint /me, no requiere contexto organizacional). Se consulta al abrir la pantalla de historial.
No recibe query params.
Response — array de:
| Campo | Tipo | Descripción |
|---|---|---|
id |
number | Id del tipo. |
name |
string | Nombre. |
description |
string | Descripción. |
status |
enum TrainingStatusEnum |
A activo, I inactivo, X eliminado. |
[ { "id": 1, "name": "GENERAL", "description": "Capacitación general", "status": "A" }]Sin errores específicos adicionales a los comunes.
Lógica de cliente
Sección titulada «Lógica de cliente»Estados de aprobación (UserHistoryTrainingApprovalStatusEnum)
Sección titulada «Estados de aprobación (UserHistoryTrainingApprovalStatusEnum)»| Valor | Significado backend | Texto sugerido | Color sugerido |
|---|---|---|---|
APPROVED |
Aprobado | Aprobado | success / verde |
NOT_APPROVED |
No aprobado | No aprobado | error / rojo |
PENDING |
En proceso / aún no rinde | En progreso | warning / amarillo o info / azul |
NOT_REQUIRED |
No requiere evaluación (curso CU completado) |
Realizado | info / azul |
Para score: si approvalStatus = NOT_REQUIRED, mostrar “No aplica” en vez de un porcentaje; si score es null/undefined, mostrar “–”.
Tipo de pregunta (VirtualTrainingQuestionTypeEnum)
Sección titulada «Tipo de pregunta (VirtualTrainingQuestionTypeEnum)»| Valor | Significado | Interacción sugerida |
|---|---|---|
SINGLE |
Una sola respuesta correcta | Selección única (radio); al marcar otra opción se reemplaza la anterior. |
MULTIPLE |
Una o más respuestas correctas | Selección múltiple (checkbox). |
OPEN |
Respuesta de texto libre | Campo de texto; no se corrige automáticamente (no cuenta para correctas/incorrectas). |
Modalidad (TrainingModalityEnum, calculada por backend)
Sección titulada «Modalidad (TrainingModalityEnum, calculada por backend)»| Valor | Significado |
|---|---|
PRESENTIAL |
Sin link (y, en el detalle de malla curricular, sin capítulos). |
E_LEARNING |
Tiene link o capítulos. |
Tipo de referencia de capacitación (TrainingReferenceTypeEnum)
Sección titulada «Tipo de referencia de capacitación (TrainingReferenceTypeEnum)»Código corto (referenceType) |
Clave de enum (usar en :type de la URL) |
Significado |
|---|---|---|
EV |
EVALUATION |
Curso en línea con evaluación formal. |
CA |
REAL_TRAINING |
Capacitación presencial/ejecutada. |
CU |
VIRTUAL_TRAINING |
Curso en línea sin evaluación formal. |
| — | ALL |
Sólo válido para history/:type (todos los tipos); no usar en history/certificate. |
Qué mostrar/ocultar según estado
Sección titulada «Qué mostrar/ocultar según estado»- Botón “Rendir quiz del capítulo”: deshabilitado mientras
chapter.couldEvaluate = false. Un capítulo sin archivos nunca se habilita — si el curso tiene un capítulo sin material y evaluación por capítulo, esa evaluación queda inalcanzable desde el cliente; tratarlo como contenido mal configurado, no como bug de la app. - Botón “Rendir quiz del curso” (evaluación general): mostrar en el footer del detalle del curso sólo cuando
evaluation.generalEvaluation === true; deshabilitar cuandoattempts >= examAttempts. - Acción “Ver certificado” en el historial: mostrar sólo si
hasCertificate === true. - Botón “Registrar” en el detalle de un procedimiento: mostrar sólo si
hasNote/isEvaluated === false. Si ya está registrado, el footer debe ofrecer sólo “Cerrar”. - Selector de fecha de inducción: no permitir fecha vacía; habilitar “Guardar” recién cuando el usuario eligió una fecha.
Validaciones previas a cada acción
Sección titulada «Validaciones previas a cada acción»- Antes de habilitar “Rendir quiz” (curso o capítulo):
numberOfTries < examAttemptsy la fecha actual dentro de[examStartDate, examEndDate]. El backend valida lo mismo, pero repetir la validación en cliente evita un roundtrip con400. - Antes de armar el body del
POSTdel quiz: recorrer todas las preguntas mostradas y construiranswerscon el formato esperado por tipo (ver tabla del endpointPOST .../quiz); no enviar ids que no vinieron en elGETde ese mismo quiz — un intento de reenviar unquestionIdobsoleto de un intento anterior responde400. - Antes de abrir un archivo del capítulo, llamar
PUT /me/training/active/file— el detalle del curso no reflejacouldEvaluate = truehasta que todos los archivos del capítulo estén marcados como vistos. PUT /me/training/procedures: sólo enviar sihasNote/isEvaluated === false; reenviar sobre un procedimiento ya registrado siempre responde400.
Estados vacíos y casos borde
Sección titulada «Estados vacíos y casos borde»- Sin cursos activos:
GET /me/training/activeresponde404(no200con array vacío). El cliente debe interceptar ese404específico de este endpoint y mostrarlo como estado vacío (“No tienes capacitaciones activas”), no como error genérico. - Sin capacitaciones en la malla curricular / sin procedimientos / sin historial: estos tres sí siguen la convención estándar,
200conitems: []— mostrar estado vacío normal. - Curso vencido: si
GET active/:trainingId/evaluation/:evaluationIdresponde400con el mensaje de período finalizado, no reintentar automáticamente; mostrar el curso como no disponible y sugerir volver al listado (donde ya no debería aparecer en la próxima carga, dado que las fechas están vencidas). - Reprobó y se resetean los archivos vistos: después de un
POSTde quiz fallido que dispara el reseteo de archivos (ver caution en ese endpoint), refrescar el detalle del curso (GET active/:trainingId/evaluation/:evaluationId) para reflejarcouldEvaluate = falseyuserViewed = falseen los archivos afectados, en vez de asumir que el estado anterior sigue vigente. - Evaluación general vs. por capítulo: no asumir un único flujo — leer
chapterEvaluation/generalEvaluationde cada evaluación antes de decidir si se muestra un botón de quiz por capítulo o uno solo para todo el curso.