Ir al contenido

Desempeño

El módulo cubre el flujo de autoservicio de evaluación de desempeño del usuario autenticado: ver las campañas activas en las que participa (como autoevaluado, jefe, par, cliente interno o evaluador de un colaborador), responder los formularios que le corresponden, guardar avances parciales, enviar la evaluación final, y consultar su historial de evaluaciones junto con el informe de resultados y el feedback recibido.

Acciones cubiertas:

  • Ver campañas de evaluación activas en las que el usuario participa (como evaluado o como evaluador).
  • Ver el detalle de una campaña (secciones habilitadas, tipo de evaluación).
  • Ver la lista de personas que el usuario debe evaluar dentro de una campaña (autoevaluación, jefe, pares, colaboradores a cargo, cliente interno, según cómo esté diseñada la campaña).
  • Responder el formulario de evaluación (funciones, competencias, condiciones organizacionales, filosofía, valores, potencial, comentarios) y guardarlo de forma parcial o final.
  • Ver el historial de evaluaciones propias, filtrado por año.
  • Ver el informe final de una evaluación terminada y el feedback publicado por el jefe.
  • Descargar el informe final como PDF (resumen ejecutivo o informe completo, con secciones opcionales, en español o inglés).
  • Acusar recibo del feedback recibido.

Prerrequisitos:

  • El usuario debe estar autenticado y el x-tenant-id debe coincidir con su tenant (ver Convenciones).
  • Guardar progreso (PATCH .../save), ver historial y ver el informe requieren que el tenant tenga configurados sus parámetros generales — el backend rechaza estas llamadas si el tenant no los tiene (RequiresTenantParameters).
  • Las campañas tipo EM (Evaluación de MRL) quedan excluidas del listado de personas a evaluar (GET /me/evaluation/{id}/type/{abbreviation}), sin importar qué abbreviation se pida — el backend las filtra explícitamente. Si una campaña EM aparece en GET /me/evaluation, la app no podrá construir su roster con este flujo.
sequenceDiagram
    participant App as App móvil
    participant API as th-core-api

    Note over App,API: Lista de campañas activas
    App->>API: GET /me/evaluation
    API-->>App: campañas activas del usuario

    Note over App,API: Usuario abre una campaña
    App->>API: GET /me/evaluation/:id/details
    API-->>App: detalle + evaluationType.abbreviation + secciones habilitadas

    Note over App: abbreviation = ED (Desempeño) o similar -> ir al roster<br/>abbreviation = OB (Objetivos) -> flujo fuera de alcance
    App->>API: GET /me/evaluation/:id/type/:abbreviation
    API-->>App: personas a evaluar (autoevaluación, jefe, pares, colaboradores) con estado y accion requerida

    Note over App,API: Usuario elige a quién evaluar y empieza el formulario
    App->>API: GET /me/evaluation/content/all
    API-->>App: contenido dinámico del formulario según la configuración de la campaña

    Note over App,API: El usuario guarda avance en cualquier momento
    App->>API: PATCH /me/evaluation/:evaluationId/username/:usernameEvaluated/save
    Note over App: metadata.isFinal = false -> guardado parcial

    Note over App,API: Al completar todas las secciones, enviar
    App->>API: PATCH /me/evaluation/:evaluationId/username/:usernameEvaluated/save
    Note over App: metadata.isFinal = true -> envío final
    App->>API: GET /me/evaluation/:id/type/:abbreviation
    API-->>App: refresco del roster

    Note over App,API: Historial y resultados
    App->>API: GET /me/evaluation/history/years
    API-->>App: años con evaluaciones
    App->>API: GET /me/evaluation/history
    API-->>App: historial de evaluaciones propias

    Note over App,API: Si la evaluación terminada permite ver informe
    App->>API: GET /me/evaluation/:evaluationId/report
    API-->>App: informe final, incluye feedback del jefe si fue publicado

    Note over App,API: Descargar el informe como PDF (opcional)
    App->>API: GET /me/evaluation/:evaluationId/report/pdf?mode=&hide=&lang=
    Note over API: arma el HTML con los datos del informe<br/>y lo renderiza el Worker th-pdfs
    API-->>App: binario application/pdf (no JSON)

    Note over App,API: Si el jefe publicó feedback y aún no fue confirmado
    App->>API: PATCH /me/evaluation/:evaluationId/report/feedback/acknowledge
    API-->>App: confirmación

Campañas de evaluación activas en las que participa el usuario autenticado (como evaluado o como evaluador de alguien más).

Query params:

Param Tipo Obligatorio Enum Descripción
abbreviation string No EvaluationTypeAbbreviationEnum: PP, ED, PO, OB, EM Filtra a un solo tipo de evaluación.

Response — array de:

Campo Tipo Descripción
id number Id de la campaña de evaluación.
name string Nombre de la campaña.
evaluationTypeId number Id del tipo de evaluación.
evaluationType array [{ id, abbreviation }] — ver enum de tipos en Lógica de cliente.
periodId / periodMonth / periodYear number Período al que pertenece la campaña.
startDate / endDate date Vigencia de la campaña.
form string Código de formato interno ('1' horizontal, '2' vertical, '3' escala por conducta) — no confundir con el format de texto (HORIZONTAL/VERTICAL) que trae el informe final.
status enum EvaluationStatusEnum Estado de la campaña (ver tabla en Lógica de cliente).
maxScaleValue number Valor máximo de la escala de calificación.
instructions string | null Instrucciones HTML de la campaña, si las tiene.
hasResponsibilitiesConfigured boolean El tenant tiene habilitado el cumplimiento de responsabilidades para esta campaña.
hasFunction, hasObjective, hasCompetency, hasCondition, hasPhilosophy, hasValue, hasPotential, hasQuestions, hasResponses enum 'S' | 'N' Qué secciones tiene habilitadas la campaña. La app no necesita usarlos para armar el formulario — GET /me/evaluation/content/all ya devuelve sólo las secciones que aplican — pero sirven para mostrar un resumen rápido en la card.
weightCompetency, weightCondition, weightFunction, weightObjective, weightPhilosophy, weightValue, weightPotential, weightQuestions, weightResponses number Peso de cada sección en el puntaje final. Uso informativo; el cálculo lo hace el backend.
[
{
"id": 4102,
"name": "Evaluación de Desempeño 2026 - Semestre 1",
"evaluationTypeId": 2,
"evaluationType": [{ "id": 2, "abbreviation": "ED" }],
"periodId": 18,
"periodMonth": 6,
"periodYear": 2026,
"startDate": "2026-06-01T00:00:00.000Z",
"endDate": "2026-07-31T23:59:59.000Z",
"form": "1",
"status": "A",
"maxScaleValue": 5,
"instructions": null,
"hasResponsibilitiesConfigured": false,
"hasFunction": "S",
"hasObjective": "S",
"hasCompetency": "S",
"hasCondition": "N",
"hasPhilosophy": "N",
"hasValue": "N",
"hasPotential": "N",
"hasQuestions": "N",
"hasResponses": "N"
}
]

Sin errores específicos adicionales a los comunes.

Detalle completo de una campaña puntual — se usa principalmente para leer evaluationType.abbreviation y decidir a qué pantalla navegar, y para conocer los flags de configuración antes de entrar al roster.

Path param: id (number).

Response: mismos campos que GET /me/evaluation, más:

Campo Tipo Descripción
evaluationUsers array Todas las filas de evaluación asociadas a la campaña (evaluado, evaluador, relación, estado). En autoservicio normalmente no se recorre esta lista directamente — para eso está el endpoint paginado GET /me/evaluation/{id}/type/{abbreviation} — pero el campo viene presente.
hasStrategicObjective boolean true si la campaña tiene un objetivo estratégico asociado (strategicObjectivePeriodId > -1).

Sin errores específicos adicionales a los comunes.

Lista paginada de las personas que el usuario autenticado debe evaluar dentro de la campaña, con su estado y qué acción falta. abbreviation es el evaluationType.abbreviation de la propia campaña (obtenido en /details), no un valor libre.

Query params:

Param Tipo Obligatorio Descripción
page number No Página (default 1).
limit number No Ítems por página (default 10).
includeAutoEvaluations boolean No (default false) Si es false, oculta la fila donde el evaluador es igual al evaluado (tu propia autoevaluación). El frontend de referencia siempre envía true para mostrarla junto al resto.

Response:

{
"items": [ /* ver tabla de campos abajo */ ],
"meta": {
"totalItems": 4,
"itemCount": 4,
"itemsPerPage": 10,
"totalPages": 1,
"currentPage": 1,
"pendingCount": 2
}
}

Campos de cada item (entidad casi cruda — este endpoint no tiene un DTO de respuesta formal, a diferencia del resto del módulo):

Campo Tipo Descripción
username string Usuario evaluado en esta fila.
evaluator string Usuario evaluador (el autenticado, salvo casos administrativos).
evaluationId number Id de la campaña.
relationId number Relación del evaluador respecto al evaluado — ver tabla RelationEnum en Lógica de cliente.
relation object { id, name } — nombre legible de la relación.
status enum EvaluationUserStatusEnum Estado de esta evaluación puntual (ver tabla en Lógica de cliente).
user object { username, name, lastName, avatar, position: { id, name } } — datos del evaluado.
userAutoevaluationStatus enum | null Sólo presente si relationId es 1 (Jefe) o 6 (Gerente Funcional): estado de la autoevaluación del evaluado, para saber si ya puede evaluarlo.
actionRequired object | null { key, defaultLabel } si hay una acción bloqueante antes de poder calificar — ver tabla en Lógica de cliente. null si puede evaluar directamente.
{
"items": [
{
"username": "jperez",
"evaluator": "jperez",
"evaluationId": 4102,
"relationId": 5,
"relation": { "id": 5, "name": "Autoevaluación" },
"status": "F",
"user": {
"username": "jperez",
"name": "Juan",
"lastName": "Pérez",
"avatar": "jperez.png",
"position": { "id": 31, "name": "Analista de Nómina" }
},
"userAutoevaluationStatus": null,
"actionRequired": null
},
{
"username": "mrojas",
"evaluator": "jperez",
"evaluationId": 4102,
"relationId": 3,
"relation": { "id": 3, "name": "Pares" },
"status": "A",
"user": {
"username": "mrojas",
"name": "María",
"lastName": "Rojas",
"avatar": "mrojas.png",
"position": { "id": 40, "name": "Analista de Nómina" }
},
"userAutoevaluationStatus": null,
"actionRequired": null
}
],
"meta": {
"totalItems": 2,
"itemCount": 2,
"itemsPerPage": 10,
"totalPages": 1,
"currentPage": 1,
"pendingCount": 1
}
}

Sin errores específicos adicionales a los comunes.

GET /me/evaluation/{id}/type/{abbreviation}/user

Sección titulada «GET /me/evaluation/{id}/type/{abbreviation}/user»

Variante singular del endpoint anterior: devuelve la fila de evaluación del propio usuario autenticado (evaluado = evaluador = usuario actual) para esa campaña y tipo, sin paginar.

Path params: id (number), abbreviation (EvaluationTypeAbbreviationEnum).

Response: un solo objeto con la misma forma que un item de GET /me/evaluation/{id}/type/{abbreviation}.

Errores específicos:

Código Cuándo ocurre
404 La evaluación no existe o el usuario no está autorizado a verla.

Devuelve el contenido dinámico del formulario de evaluación — funciones, objetivos, competencias, condiciones organizacionales, filosofía, valores, potencial, preguntas, responsabilidades — ya filtrado según qué secciones tiene habilitadas la campaña y la relación del evaluador. Es el endpoint que carga la pantalla de responder evaluación: una llamada por cada par evaluador → evaluado que el usuario abre desde el roster.

Query params:

Todos los params son opcionales para el framework (el backend no devuelve 400 por omitirlos), pero funcionalmente debes enviarlos siempre: el contenido se arma con ellos y, si faltan, el resultado es un error o datos incorrectos. Todos salen de la fila del roster que el usuario eligió (GET /me/evaluation/{id}/type/{abbreviation}).

Param Tipo Obligatorio Para qué / cuándo usarlo
evaluationId number Funcionalmente sí Campaña que se va a responder. Úsalo con el evaluationId de la fila del roster.
username string Funcionalmente sí Usuario evaluado (no el autenticado, salvo en autoevaluación). Úsalo con el username de la fila.
positionId number Funcionalmente sí Cargo del evaluado; determina qué funciones, competencias y objetivos se cargan. Tómalo de user.position.id de la fila.
relationship number Funcionalmente sí relationId de la fila (ver RelationEnum). Define qué secciones aplican a quien evalúa (por ejemplo, potencial sólo para 1).
userAutoevaluationStatus string No Estado de la autoevaluación del evaluado, tal cual viene en la fila del roster (A, P, F, V, C, N, R — ver Estados de la evaluación del usuario). Sólo tiene efecto con el valor F. Envíalo cuando el roster lo traiga (jefe y gerente funcional); omítelo si es null.

El único valor que cambia la respuesta es userAutoevaluationStatus = F, y sólo si además la campaña tiene activeAutomaticVerification = 'S' y relationship != 5: entonces se rellenan los resultAutoevaluation de cada ítem y general.observationAutoevaluation / secondObservationAutoevaluation. Con cualquier otro valor esos campos no vienen.

Estados de la evaluación del usuario (EvaluationUserStatusEnum):

Valor Significado
A Activa: asignada, sin finalizar (también es el estado de un guardado parcial).
P En proceso.
F Finalizada: el evaluador ya envió su evaluación.
V En validación.
C En calibración.
N Rechazada.
R Registrada: en el flujo de objetivos, el registro ya fue hecho y espera la evaluación.

Response. La forma completa es extensa y varía según qué secciones tenga la campaña. Se describe por objeto, siempre con la misma pregunta: ¿para qué sirve y cuándo lo uso?

evaluation — qué campaña es y cómo se comporta

Sección titulada «evaluation — qué campaña es y cómo se comporta»
Campo Tipo Para qué / cuándo usarlo
id / name number / string Identifican la campaña. Usa name como título de la pantalla.
evaluationType object { id, abbreviation } (PP, ED, PO…). Úsalo para ajustar textos según el tipo (por ejemplo, el período de prueba tiene recomendación de contrato).

evaluation.configuration — banderas de la campaña. Los 'S'/'N' son cadenas, no booleanos:

Campo Tipo Para qué / cuándo usarlo
hasFunction / hasObjective / hasCompetency / hasCondition / hasPhilosophy / hasValue / hasPotential / hasQuestions / hasQuestionsPerformance / hasResponses 'S' | 'N' La campaña incluye esa sección. Que sea 'S' no garantiza que haya ítems: la relación del evaluador puede no tenerla. Para decidir qué pintar usa la longitud del array en components; para el cálculo de pasos y pesos usa estas banderas.
activeisNotObserved 'S' | 'N' Si es 'S', ofrece la opción “No observado” en las escalas. Si es 'N', no la muestres: el backend ignora un -1 enviado en ese caso.
activeFlowControl 'S' | 'N' Control de flujo secuencial: con 'S' el jefe/pares no pueden evaluar hasta que el evaluado termine su autoevaluación. El roster ya lo refleja en actionRequired; aquí sólo úsalo como contexto (el backend revalida y responde 400).
activeAutomaticVerification 'S' | 'N' Si es 'S' (y se cumple la regla de Referencia de autoevaluación), puedes mostrar junto a cada ítem lo que el evaluado se calificó.
activeObjectiveScale 'S' | 'N' Indica que la campaña usa una escala propia para objetivos (scales.objectiveScale). Úsala, y no mainScale, para calificar objetivos cuando sea 'S'.
activeAttachedObjectiveFile 'S' | 'N' Si es 'S', permite adjuntar un archivo de evidencia por objetivo (objectives[].file). Muestra u oculta el control de adjuntos.
activePeriodicObjective 'S' | 'N' La campaña maneja objetivos periódicos. El backend lo expone como bandera; la app de referencia no cambia el formulario por él, y la gestión de objetivos queda fuera de este documento.
registeredUserConfig 'U' | 'J' | 'A' Quién registra los objetivos/responsabilidades: U colaborador, J jefe inmediato, A ambos. Explica por qué la sección de objetivos o responsabilidades puede llegar vacía (ver Cuándo una sección llega vacía).
format '1' | '2' | '3' Cómo se califican competencias y condiciones: 1 horizontal (nivel por conducta), 2 vertical (se elige una conducta por competencia), 3 escala por conducta. Define qué formato de save usar.
philosophyValuesVertical boolean true si Filosofía y Valores se califican en modo vertical (sólo ocurre con format = '2' y un parámetro del cliente activo). Úsalo, y no format, para decidir cómo renderizar y guardar Filosofía y Valores: con false se califican por escala aunque la campaña sea vertical.
Campo Tipo Para qué / cuándo usarlo
mainScale array Escala de funciones, competencias, condiciones, filosofía, valores y preguntas.
objectiveScale array Escala de objetivos; sólo se usa si activeObjectiveScale = 'S'.
strategyScale array Escala de estrategias; no hay una sección de estrategias en components, así que normalmente no se usa.

Cada valor es { id, idScale, valueName, shortValueName, value, valueOrder, status }: pinta valueName (o shortValueName en botones compactos), ordena por valueOrder y envía value al guardar. El backend devuelve todos los niveles activos, incluido uno con value = 0 (por ejemplo “No cumple”): es una calificación válida, no “sin responder”. “No observado” no es un nivel de la escala: es una opción aparte que la app agrega sólo si activeisNotObserved = 'S'.

Cada propiedad es un array. Una sección que llega [] no aplica para esta relación (o la campaña no la tiene): ocúltala. Esquema por sección:

Sección Ítem (campos relevantes) Para qué / cuándo usarlo
functions id, name, description, weight, result, items, resultAutoevaluation Funciones del cargo. result es la calificación ya guardada (promedio) y items cuántas calificaciones la forman; sirven para precargar la pantalla. weight es informativo. Guarda con { id, obtainedLevel }.
objectives id, name, description, indicator, periodRelations[] (meta, línea base, unidad), obtainedLevel, complianceValue, observation, file, fileCounterpart, approvalStatus, approverObservation, resultAutoevaluation, observationAutoevaluation indicator y periodRelations describen qué se mide y la meta; muéstralos como contexto. obtainedLevel / complianceValue / observation / file son lo ya guardado por este evaluador. fileCounterpart es el adjunto del otro registrante (sólo lectura). approvalStatus y approverObservation aplican al flujo de aprobación de objetivos del colaborador (rechazo del jefe con su motivo). Los objetivos usan obtainedLevel, no result.
responsibilities id, name, description, weight, obtainedLevel, complianceValue, observation, resultAutoevaluation, approvalStatus, registeredByCollaborator Responsabilidades del período. registeredByCollaborator = true indica que las registró el colaborador (relevante con registeredUserConfig = 'U'/'A'). El resto funciona como en objetivos. Guarda con { id, obtainedLevel, observation, approvalStatus }.
competencies competency { id, name, description, macroCompetency }, competencyBehaviors[] (cada una con id, name, scaleId, result, items, resultAutoevaluation), level Competencias específicas del cargo. Cada conducta es lo que se califica. Ver Contrato de result.
behaviors competencyId, competencyName, competencyDescription, competencyBehaviors[] (id, name, levelId, result, items, resultAutoevaluation) Competencias organizacionales (condiciones). Misma forma y mismo contrato de result que philosophies y values.
philosophies / values igual que behaviors Filosofía y Valores. Su modo (vertical o por escala) lo marca configuration.philosophyValuesVertical.
questions / questionsPerformance id, name, type, status, result, resultAutoevaluation Preguntas generales y de desempeño que se califican con la escala principal. result es la respuesta guardada.
potential igual que questions Preguntas de potencial. Sólo viene poblada cuando relationship = 1 (jefe inmediato); para cualquier otra relación llega [].
Contrato de result en conductas (evaluaciones verticales)
Sección titulada «Contrato de result en conductas (evaluaciones verticales)»

En formato vertical se elige una sola conducta por competencia, y el backend repite el mismo result en cada conducta de esa competencia. Interpretación:

result Significa Qué hacer en la app
0 Sin respuesta: no hay nada guardado para la competencia. No marques ninguna opción. No lo envíes al guardar.
-1 “No observado” elegido explícitamente. Marca “No observado” (sólo se ofrece si activeisNotObserved = 'S').
> 0 Es el id de la conducta elegida. Marca la conducta cuyo id coincide con result.

-1 no es un nivel de la escala. Antes de la corrección de octubre de 2026 la lectura devolvía -1 para “sin responder”; hoy -1 sólo significa “No observado” y lo sin responder es 0. En formato horizontal, result de cada conducta es el nivel (valor de escala) ya guardado para esa conducta.

Campo Tipo Para qué / cuándo usarlo
evaluator / evaluated string Eco de quién evalúa y a quién. Úsalos para el encabezado y para armar la ruta del PATCH save (usernameEvaluated).
positionId / relationship number Eco de los params. Reenvía positionId en metadata.positionId al guardar.
status string Estado de la campaña.
userAutoevaluationStatus string | null Eco del estado de autoevaluación del evaluado (ver tabla de estados). Úsalo para decidir si mostrar la referencia de autoevaluación.
managerObjectivesFinished boolean true cuando el jefe ya finalizó su evaluación de objetivos. Sólo se calcula en la autoevaluación con registeredUserConfig = 'U'; en cualquier otro caso es false. Úsalo para resaltar o bloquear los objetivos aprobados/negados por el jefe sólo cuando es true; antes de eso el estado “aprobado” es sólo un valor por defecto y no debe bloquear el borrador del colaborador.
requiresContractRecommendation boolean true en evaluaciones de período de prueba (PP) cuando la relación de quien evalúa debe responder la recomendación. Si es true, muestra y exige la pregunta “¿El contrato debe continuar? Sí/No” y envíala en general.isRecommended. Si es false, no la muestres.
totalItems object { functions, objectives, competencies, behaviors, philosophies, values, potential, responsibilities }: cantidad de ítems calificables por sección (en vertical cuenta una por competencia; en horizontal, una por conducta). Úsalo como denominador del progreso.
Campo Tipo Para qué / cuándo usarlo
observation / secondObservation string Comentarios finales ya guardados (por ejemplo logros y áreas de mejora). Precarga los campos de texto y reenvíalos en el save.
isRecommended 'S' | 'N' Respuesta guardada de “¿El contrato debe continuar?”. Sólo úsala si metadata.requiresContractRecommendation = true.
observationAutoevaluation / secondObservationAutoevaluation string Comentarios que escribió el evaluado en su autoevaluación. Sólo vienen con la regla de autoevaluación cumplida y si el evaluado escribió algo; muéstralos como referencia de sólo lectura.

objectives, functions, specificCompetencies, organizationalCompetencies, philosophy, values, potential, finalComments, responsibilities — número o null. Indican cuánto pesa cada sección en el puntaje final de esta campaña. Son informativos: el cálculo lo hace el backend; úsalos, si quieres, para mostrar la ponderación junto al título de cada sección. null indica que no hay peso configurado para esa sección.

Una sección con la bandera 'S' puede llegar [] por reglas de la relación; no lo trates como error:

Sección Llega vacía cuando…
Cualquiera La relación del evaluador no tiene configurada esa sección en la campaña.
potential relationship no es 1 (sólo el jefe inmediato evalúa potencial).
objectives Con registeredUserConfig = 'A': el evaluador no es jefe (1) ni autoevaluación (5). Con 'U': es autoevaluación → ve objetivos; es el jefe → los ve sólo cuando el colaborador ya los registró (estado R); cualquier otra relación → vacía. Con 'J': el jefe siempre los ve; en autoevaluación sólo cuando el jefe ya los registró (R); otra relación → vacía.
responsibilities Igual que objetivos, salvo que con registeredUserConfig = 'A' siempre se muestran, sin importar la relación.
{
"evaluation": {
"id": 4102,
"name": "Evaluación de Desempeño 2026 - Semestre 1",
"evaluationType": { "id": 2, "abbreviation": "ED" },
"configuration": {
"hasFunction": "S",
"hasObjective": "N",
"hasCompetency": "S",
"hasCondition": "N",
"hasPhilosophy": "N",
"hasValue": "N",
"hasPotential": "N",
"hasQuestions": "N",
"hasQuestionsPerformance": "N",
"hasResponses": "N",
"activeisNotObserved": "S",
"activeFlowControl": "S",
"activeAutomaticVerification": "S",
"activeObjectiveScale": "N",
"activeAttachedObjectiveFile": "N",
"activePeriodicObjective": "N",
"registeredUserConfig": "U",
"format": "2",
"philosophyValuesVertical": false
}
},
"scales": {
"mainScale": [
{ "id": 1, "idScale": 3, "valueName": "Insuficiente", "shortValueName": "1", "value": 1, "valueOrder": 1, "status": "A" },
{ "id": 5, "idScale": 3, "valueName": "Sobresaliente", "shortValueName": "5", "value": 5, "valueOrder": 5, "status": "A" }
],
"objectiveScale": [],
"strategyScale": []
},
"components": {
"functions": [
{ "id": 812, "name": "Procesar nómina quincenal", "description": "", "weight": 40, "result": 4, "items": 1, "resultAutoevaluation": 4 }
],
"objectives": [],
"competencies": [
{
"competency": { "id": 22, "name": "Orientación al detalle", "description": "Revisa su trabajo antes de entregarlo." },
"competencyBehaviors": [
{ "id": 443, "name": "Revisa ocasionalmente", "result": 444, "items": 1 },
{ "id": 444, "name": "Revisa siempre y detecta errores ajenos", "result": 444, "items": 1 }
]
},
{
"competency": { "id": 28, "name": "Trabajo en equipo", "description": "Colabora con otras áreas." },
"competencyBehaviors": [
{ "id": 251, "name": "Colabora si se le pide", "result": 0, "items": 0 },
{ "id": 252, "name": "Colabora de forma proactiva", "result": 0, "items": 0 }
]
}
],
"behaviors": [],
"philosophies": [],
"values": [],
"questions": [],
"questionsPerformance": [],
"potential": [],
"responsibilities": []
},
"metadata": {
"evaluator": "mrojas",
"evaluated": "jperez",
"positionId": 31,
"relationship": 3,
"status": "A",
"userAutoevaluationStatus": "F",
"managerObjectivesFinished": false,
"requiresContractRecommendation": false,
"totalItems": { "functions": 1, "objectives": 0, "competencies": 2, "behaviors": 0, "philosophies": 0, "values": 0, "potential": 0, "responsibilities": 0 }
},
"general": { "observation": "", "secondObservation": "" },
"weights": { "objectives": null, "functions": 40, "specificCompetencies": 60, "organizationalCompetencies": null, "philosophy": null, "values": null, "potential": null, "finalComments": null, "responsibilities": null }
}

En el ejemplo, la competencia 22 ya tiene la conducta 444 elegida (result: 444) y la 28 está sin responder (result: 0).

Errores:

Código Cuándo ocurre
400 This evaluation is not available due to flow control rules: '<etiqueta>'. El control de flujo (activeFlowControl) bloquea esta evaluación ahora: la autoevaluación del evaluado sigue activa (A) y quien evalúa es el jefe inmediato en una evaluación de objetivos, o cualquier otra relación (distinta de autoevaluación) cuya evaluación aún no está finalizada. <etiqueta> es el mismo texto de actionRequired del roster (Autoevaluación pendiente / Pendiente Jefe Inmediato). No debería ocurrir si la app ya ocultó el botón, pero el backend lo revalida.
404 Evaluations for evaluated user not found. No existe una fila de evaluación para ese username evaluado con el usuario autenticado como evaluador en esa campaña. Ocurre con un evaluationId inexistente, un username equivocado o cuando el usuario no es evaluador de ese par.

Tanto 400 como 404 son errores reales (no un 200 con datos vacíos): muestra un estado de error de carga con opción de volver al roster. Por defensa, verifica igualmente que evaluation?.id venga presente antes de pintar el formulario.

PATCH /me/evaluation/{evaluationId}/username/{usernameEvaluated}/save

Sección titulada «PATCH /me/evaluation/{evaluationId}/username/{usernameEvaluated}/save»

Guarda el avance de una evaluación puntual (parcial) o la envía como final.

Path params: evaluationId (number), usernameEvaluated (string — el evaluado, no necesariamente el usuario autenticado).

Body (SaveEvaluationProgressDto):

Campo Tipo Obligatorio Descripción
general.observation string No Comentario general.
general.secondObservation string No Áreas de mejora / segundo comentario.
general.isRecommended 'S' | 'N' No Recomendación de aprobación (uso administrativo).
metadata.positionId number Sí Cargo del evaluado.
metadata.isFinal boolean No (default false) false = guardado parcial (la fila queda en A/Activa). true = envío final (la fila pasa a F/Finalizada).
components.functions / .objectives / .responsibilities / .questions / .questionsPerformance / .potential array No Formato completo: [{ id, obtainedLevel }] (objetivos y responsabilidades además admiten observation, file, approvalStatus).
components.competencies / .behaviors / .philosophies / .values array No Formato completo (horizontal): [{ competency: { id, competencyBehaviors: [{ id, obtainedLevel }] } }].
components.competenciesRatings / .behaviorsRatings / .philosophiesRatings / .valuesRatings object No Formato simplificado: un mapa por sección que el backend transforma a la forma completa. La clave y el valor dependen del modo de la sección: en vertical es { [competencyId]: behaviorId } (la conducta elegida, no un nivel) y en horizontal es { [behaviorId]: obtainedLevel }. Competencias y condiciones son verticales con format = '2'; Filosofía y Valores con philosophyValuesVertical = true (ver content/all). Usa el formato simplificado o el completo para una misma sección, nunca ambos (si envías el array completo, el mapa de esa sección se ignora).
components.potentialRatings object No Mapa { [questionId]: obtainedLevel } para las preguntas de potencial.
{
"general": {
"observation": "Buen desempeño en el período, cumplió los objetivos principales.",
"secondObservation": "Mejorar tiempos de respuesta en solicitudes urgentes."
},
"components": {
"functions": [
{ "id": 812, "obtainedLevel": 4 }
],
"objectives": [],
"responsibilities": [],
"questions": [],
"questionsPerformance": []
},
"metadata": {
"positionId": 31,
"isFinal": true
}
}

Qué enviar en secciones verticales (competencias, condiciones, filosofía y valores). Lee el result de content/all (Contrato de result) y sigue esta regla para cada competencia del mapa:

Estado en la app Qué poner en el mapa
El usuario eligió una conducta { [competencyId]: behaviorId }.
El usuario eligió “No observado” { [competencyId]: -1 }. Sólo se guarda si activeisNotObserved = 'S'; en caso contrario el backend lo ignora y la competencia queda sin responder.
Sin responder (result = 0) Omite la clave.

Response: 200, sin cuerpo relevante (confirmación de guardado).

Errores específicos:

Código Cuándo ocurre
400 Falta evaluator, evaluationId, evaluated o el body data.
400 Todas las filas de evaluación de este usernameEvaluated/evaluador ya están en estado F (Finalizada) — no se puede reenviar.
404 La campaña (evaluationId) no existe.
404 No hay filas de evaluación para ese usernameEvaluated con este evaluador en esta campaña.

Historial de evaluaciones (de todo tipo) del usuario autenticado, con métricas de progreso y resultado.

Query params:

Param Tipo Obligatorio Descripción
periodId number No Filtra por período.
evaluationTypeId number No Filtra por tipo de evaluación.
year number No Filtra por año del período.
username string No Omitir en self-service — sólo se usa en la ficha de administración de un colaborador. Sin él, se consulta el usuario autenticado.

Response — array de:

Campo Tipo Descripción
evaluationId / evaluationName — Identificador y nombre de la campaña.
periodId / periodYear — Período.
evaluationType object { id, name, abbreviation }.
startDate / endDate date Vigencia.
progress number Porcentaje de avance (0-100).
completedCount / totalCount number Evaluadores que ya terminaron vs. total asignado.
score number | null Puntaje final. null si aún no hay puntaje evaluable o si es una evaluación de Líder Multiplicador (isLeadershipMultiplier).
developmentLevel object | null { id, name, color } — nivel de desarrollo (semáforo).
status string Estado de la campaña.
totalObjectives / evaluatedObjectives number Objetivos asignados vs. evaluados (fuera de alcance detallar el flujo de objetivos, pero el dato viene en el historial).
objectiveCompliance number | null % de cumplimiento promedio de los objetivos evaluados.
isPublished boolean El registro de evaluación del usuario está marcado como publicado.
isFinished boolean La evaluación llegó a 100% de avance.
canViewReport boolean Habilita el botón “Ver informe” — sólo mostrarlo si isFinished && canViewReport.
canViewObjectives boolean Fuera de alcance (módulo de objetivos).
isLeadershipMultiplier boolean Evaluación de Líder Multiplicador — no tiene score/developmentLevel global, esas columnas se muestran vacías.
[
{
"evaluationId": 3980,
"evaluationName": "Evaluación de Desempeño 2025 - Semestre 2",
"periodId": 17,
"periodYear": 2025,
"evaluationType": { "id": 2, "name": "Evaluación de Desempeño", "abbreviation": "ED" },
"startDate": "2025-12-01T00:00:00.000Z",
"endDate": "2026-01-15T23:59:59.000Z",
"progress": 100,
"completedCount": 4,
"totalCount": 4,
"score": 86.5,
"developmentLevel": { "id": 3, "name": "Cumple lo esperado", "color": "#22C55E" },
"status": "F",
"totalObjectives": 3,
"evaluatedObjectives": 3,
"objectiveCompliance": 92.1,
"isPublished": true,
"isFinished": true,
"canViewReport": true,
"canViewObjectives": true,
"isLeadershipMultiplier": false
}
]

Sin errores específicos adicionales a los comunes.

Años disponibles en el historial del usuario, para armar el filtro de año.

Sin query params en self-service (el parámetro username existe pero es sólo para la ficha de administración — omitirlo).

Response: number[], por ejemplo [2024, 2025, 2026].

Sin errores específicos adicionales a los comunes.

Informe final de una evaluación terminada: puntajes por sección, resultado consolidado, nivel de desarrollo y feedback.

Path param: evaluationId (number).

Query params:

Param Tipo Obligatorio Descripción
username string No Omitir en self-service — sin él, se devuelve el informe del usuario autenticado.
showByEvaluator boolean No (default false) Incluye el desglose de puntajes por tipo de evaluador (autoevaluación, jefe, pares, etc.) en cada sección.

Response (resumen — la forma completa es extensa y varía según qué secciones tenga habilitada la evaluación):

Campo Tipo Descripción
evaluation object Metadata: { id, name, type, period, startDate, endDate, format, isAnonymous }. format acá sí viene como texto ('HORIZONTAL' / 'VERTICAL'), a diferencia del código numérico de content/all.
evaluatedUser object { username, fullName, identification, position, department, avatar }.
configuration object Qué secciones están habilitadas (hasFunction, hasCompetency, hasBehavior, hasObjective, hasResponsibility, hasEnterpriseObjective, hasLeadershipMultiplier, hasPhilosophy, hasValue, hasPotential) y sus pesos.
keyMetrics.items array Métricas para el resumen ejecutivo: { key, name, percentage, developmentLevel, weight, isEnabled } por sección.
sections.* object Una clave por sección habilitada (functions, competencies, behaviors, objectives, responsibilities, enterpriseObjectives, specificCompetencies, organizationalCompetencies, leadershipMultiplier), cada una con su consolidated (average, percentage, items[]) y, si se pidió showByEvaluator=true, byEvaluator[].
finalResult object { score, scaleValue, pointsScore?, developmentLevel?, developmentScale?, isAccepted, acceptedDate? } — resultado consolidado.
feedback object Ver tabla de feedback más abajo.
evaluators array { evaluator, evaluatorName, relationId, relationName, status, evaluationDate, avatar } — quiénes participaron como evaluadores.

feedback (relevante para el flujo de acuse de recibo):

Campo Tipo Descripción
sentFeedback string Feedback que el jefe escribió/publicó para el evaluado.
receivedFeedback string Comentario del evaluado en respuesta (queda vacío hasta que acusa recibo).
isFeedbackPublished boolean El jefe publicó el feedback. Mostrar el bloque de feedback sólo si es true.
isFeedbackAcknowledged boolean El evaluado ya acusó recibo. Mientras sea false y isFeedbackPublished sea true, mostrar el formulario de acuse.
byEvaluator array { relationId, relationName, comments: string[] } — observaciones de cada evaluador, agrupadas por relación.
{
"evaluation": {
"id": 3980,
"name": "Evaluación de Desempeño 2025 - Semestre 2",
"type": { "id": 2, "name": "Evaluación de Desempeño", "abbreviation": "ED" },
"period": { "id": 17, "year": 2025 },
"startDate": "2025-12-01T00:00:00.000Z",
"endDate": "2026-01-15T23:59:59.000Z",
"format": "HORIZONTAL",
"isAnonymous": false
},
"evaluatedUser": {
"username": "jperez",
"fullName": "Juan Pérez",
"identification": "0102030405",
"position": { "id": 31, "name": "Analista de Nómina" },
"department": { "id": 6, "name": "Recursos Humanos" },
"avatar": "jperez.png"
},
"finalResult": {
"score": 86.5,
"scaleValue": 4,
"developmentLevel": { "id": 3, "name": "Cumple lo esperado", "color": "#22C55E" },
"isAccepted": true,
"acceptedDate": "2026-01-16T10:05:00.000Z"
},
"feedback": {
"sentFeedback": "Excelente cierre de período, sigue así en el control de tiempos.",
"receivedFeedback": "",
"isFeedbackPublished": true,
"isFeedbackAcknowledged": false
},
"evaluators": [
{ "evaluator": "jperez", "evaluatorName": "Juan Pérez", "relationId": 5, "relationName": "Autoevaluación", "status": "F", "evaluationDate": "2026-01-10T09:00:00.000Z", "avatar": "jperez.png" }
]
}

Errores específicos:

Código Cuándo ocurre
404 La evaluación no existe o el usuario autenticado no está autorizado a verla.

PATCH /me/evaluation/{evaluationId}/report/feedback/acknowledge

Sección titulada «PATCH /me/evaluation/{evaluationId}/report/feedback/acknowledge»

El empleado acusa recibo del feedback publicado por su jefe.

Path param: evaluationId (number).

Body (AcknowledgeFeedbackDto):

Campo Tipo Obligatorio Descripción
comment string No Comentario opcional del empleado sobre el feedback recibido.
{
"comment": "Gracias por el feedback, voy a trabajar en los tiempos de respuesta."
}

Response: { "success": true }.

Errores específicos:

Código Cuándo ocurre
400 No hay feedback publicado para acusar recibo todavía (isFeedbackPublished es false).

GET /me/evaluation/{evaluationId}/report/pdf

Sección titulada «GET /me/evaluation/{evaluationId}/report/pdf»

Descarga el informe final de una evaluación como archivo PDF. El documento no se arma en el cliente: el backend toma los mismos datos que devuelve GET /me/evaluation/{evaluationId}/report, genera el HTML y lo renderiza el Worker th-pdfs (el mismo servicio que usa el Centro de Plantillas). Web y móvil obtienen el mismo archivo pidiendo la misma URL — la app no tiene que reimplementar ninguna sección del informe.

Path param: evaluationId (number).

Query params (EvaluationReportPdfQueryDto):

Param Tipo Obligatorio Enum / default Descripción
username string No Default: usuario autenticado Omitir en self-service. Sólo se usa para descargar el informe de otro colaborador (jefe, evaluador o perfil de Desempeño); la autorización la resuelve el mismo control que protege el JSON del informe.
mode string No executive | full — default full executive imprime únicamente el resumen ejecutivo (primera página); full imprime el informe completo.
hide string (CSV) No Ver claves abajo Secciones a excluir, separadas por coma. Sólo tiene efecto en mode=full. Las claves desconocidas se descartan sin error.
lang string No Default: español Idioma del documento (es-ES, en-US…). Se normaliza por prefijo: cualquier valor que empiece por en imprime en inglés; todo lo demás, en español. No afecta el idioma de los mensajes de error.

Claves válidas de hide:

Clave Sección que oculta
objectives Objetivos
enterpriseObjectives Objetivos empresariales
responsibilities Responsabilidades
competencies Competencias (también oculta la página de conductas, que depende de ella)
functions Funciones
philosophies Filosofías
values Valores
leadershipMultiplier Líder multiplicador vs minimizador

Request:

GET /me/evaluation/3980/report/pdf?mode=full&hide=values,philosophies&lang=es-ES HTTP/1.1
Authorization: Bearer <jwt>
x-tenant-id: <tenant>

Response 200 — binario, no JSON. El cuerpo es el PDF crudo, sin la envoltura de las respuestas JSON.

Header Valor
Content-Type application/pdf
Content-Disposition inline; filename="<slug>.pdf" — el nombre se arma con el nombre de la evaluación y del evaluado, en minúsculas, sin tildes y con guiones (ej. evaluacion-de-desempeno-2026-nombre-apellido.pdf).
Cache-Control no-store — el documento se regenera en cada descarga.

Qué contiene el documento. La primera página es siempre el resumen ejecutivo. En mode=full se agregan, en este orden, las páginas de: objetivos, objetivos empresariales, responsabilidades, competencias, conductas, funciones, filosofías, valores, líder multiplicador y feedback detallado. Cada página sólo se imprime si la sección está habilitada en la evaluación y trae datos, por lo que el número de páginas varía según la campaña. Reglas que conviene conocer:

  • La página de feedback detallado no se puede ocultar con hide y no se imprime en modo ejecutivo. Se imprime sólo si existe algún comentario: observaciones por evaluador, autoevaluación u observación del jefe, feedback publicado (isFeedbackPublished) o comentario de acuse ya confirmado (isFeedbackAcknowledged).
  • En una evaluación de líder multiplicador no se imprime la página de filosofías (esas conductas viajan dentro de la de liderazgo).
  • En evaluaciones 360° anónimas se hereda la redacción de evaluadores del informe JSON y se oculta el bloque de evaluadores.
  • El pie de cada página lleva la numeración (Página X de Y) y el crédito de TwiinsHRM; el encabezado lleva el logo del tenant (o su nombre, si no tiene logo).

Errores específicos:

Código Cuándo ocurre
400 mode con un valor distinto de executive / full. El message llega como array de strings.
403 username es otro colaborador y el solicitante no es su evaluador, su jefe en la jerarquía ni tiene el perfil de seguridad de Desempeño. Mensaje: No tiene permisos para ver este reporte de evaluación. No ocurre al descargar el informe propio.
404 La evaluación no existe (Evaluation {id} not found).
503 El servicio de generación no está disponible. El message está redactado para mostrarse al usuario tal cual y distingue: Hay muchos documentos generándose en este momento… (límite del Worker), La generación del documento tardó demasiado… (timeout), El servicio de generación de documentos no está configurado. (el entorno no tiene el token del Worker) y No se pudo generar el documento en este momento. (cualquier otro fallo).

Variante de GET /me/evaluation que devuelve, además de los mismos campos, evaluationUsers[] embebido en cada campaña (igual forma que en /details) y hasStrategicObjective.

Sin query params.

Valor Significado Texto sugerido
A Activa Activa
I Inactiva Inactiva
R Registrada Registrada
F Finalizada Finalizada
X Eliminada — (excluir de listados)

La card de campaña en el listado usa este status para el badge Activa/Inactiva, pero además calcula en el cliente si endDate < fecha actual para mostrar “Finalizada” superpuesto al badge — no es un valor que devuelva el backend, es una regla del cliente sobre endDate.

Estados de una fila de evaluación (EvaluationUserStatusEnum)

Sección titulada «Estados de una fila de evaluación (EvaluationUserStatusEnum)»
Valor Significado backend Texto sugerido en el roster
A Activa (pendiente de calificar) Pendiente
P En proceso Pendiente
F Finalizada Completada
V En validación Pendiente
C En calibración Pendiente
N Rechazada Pendiente
R Registrada Pendiente

El roster simplifica todo lo que no sea F a “Pendiente” con el botón “Empezar evaluación”, salvo que actionRequired esté presente (ver siguiente tabla), en cuyo caso se reemplaza el botón por el texto de la acción bloqueante.

Acción requerida antes de poder calificar (actionRequired.key)

Sección titulada «Acción requerida antes de poder calificar (actionRequired.key)»
Valor Texto sugerido Cuándo aparece
selfEvaluationPending Autoevaluación pendiente El control de flujo de la campaña está activo y el evaluado todavía no terminó su propia autoevaluación.
immediateManagerPending Pendiente Jefe Inmediato Evaluación de tipo Objetivo donde falta la acción del jefe inmediato antes de continuar.

Si actionRequired es null, el botón “Empezar evaluación” está habilitado.

Relación evaluador → evaluado (RelationEnum)

Sección titulada «Relación evaluador → evaluado (RelationEnum)»
Valor (relationId) Significado
1 Jefe Inmediato
2 Colaborador
3 Pares
4 Cliente Interno
5 Autoevaluación
6 Gerente Funcional

Tipos de evaluación (EvaluationTypeAbbreviationEnum)

Sección titulada «Tipos de evaluación (EvaluationTypeAbbreviationEnum)»
Valor Significado Navegación desde la card
PP Evaluación de Periodo de Prueba /performance/:id (genérica)
ED Evaluación de Desempeño /performance/:id/evaluations (roster de este documento)
PO Evaluación de Potencial de Liderazgo /performance/:id (genérica)
OB Registro y Aprobación de Objetivos Fuera de alcance — ruta distinta según registeredUserConfig de la campaña.
EM Evaluación de MRL Excluida del roster (ver Prerrequisitos); no navegable con este flujo.

GET /me/evaluation/content/all sólo devuelve pobladas las secciones habilitadas para la relación del evaluador actual; el resto llega como arrays vacíos. La app debe ocultar cualquier sección cuyo array venga vacío, sin asumir que todas las evaluaciones tienen todas las secciones.

  • Formato vertical (configuration.format === '2' para competencias y condiciones; configuration.philosophyValuesVertical === true para Filosofía y Valores): el usuario elige una conducta por competencia; guarda { competencyId: behaviorId } en el mapa simplificado correspondiente.
  • Formato horizontal ('1' o '3', o Filosofía/Valores con philosophyValuesVertical === false): cada conducta lleva su nivel; guarda { behaviorId: obtainedLevel } o las estructuras completas (competencyBehaviors anidados).
  • En vertical, el result leído tiene tres estados: 0 sin responder (no marques nada y no lo reenvíes), -1 “No observado” explícito y > 0 el id de la conducta elegida. -1 no es un nivel de la escala: ofrece “No observado” sólo si configuration.activeisNotObserved === 'S'. Las escalas incluyen todos los niveles activos, también uno con value = 0, que es una calificación válida.
  • La escala (scales.mainScale) puede ser numérica o de texto — si todos los valueName son numéricos, tratarla como escala numérica; si no, como escala de opciones con etiqueta.

Mostrar el valor de autoevaluación (resultAutoevaluation en cada item, o general.observationAutoevaluation para comentarios) sólo cuando se cumplen las tres condiciones:

  1. configuration.activeAutomaticVerification === 'S'.
  2. La relación actual no es autoevaluación (relationship !== 5).
  3. metadata.userAutoevaluationStatus === 'F' (el evaluado ya finalizó su propia autoevaluación).
  • El progreso se calcula como ítems calificados / metadata.totalItems (suma de todas las secciones con datos).
  • El botón “Enviar” (envío final, isFinal: true) sólo se habilita cuando todas las secciones con ítems están completas — no sólo la sección visible en pantalla.
  • El botón “Guardar avance” (isFinal: false) está disponible en cualquier momento, aunque falten secciones.
  • Después de un envío final exitoso, volver al roster (GET /me/evaluation/{id}/type/{abbreviation}) para reflejar el nuevo estado.
  • Si el usuario reintenta guardar sobre una evaluación donde todas sus filas ya están F, el backend responde 400 — la app debería haber deshabilitado la acción antes de llegar a este punto (roster con status = F).

Historial: filtro por año y nivel de desarrollo

Sección titulada «Historial: filtro por año y nivel de desarrollo»
  • Poblar el selector de año con GET /me/evaluation/history/years; si el año actual no tiene datos, seleccionar por defecto el año más reciente que sí tenga (no forzar el año calendario actual).
  • El filtro “Nivel de desarrollo” (Cumple lo esperado / Por debajo de lo esperado / No evaluado) es una convención del cliente sobre score, no un campo del backend: score === null → “No evaluado”; score >= 70 → “Cumple lo esperado”; en cualquier otro caso → “Por debajo de lo esperado”. El umbral (70) es del cliente de referencia, no algo que confirme el backend — validar contra el criterio real de negocio antes de reutilizarlo.
  • Mostrar la acción “Ver informe” en el historial sólo si isFinished && canViewReport del item correspondiente.
  • Dentro del informe, mostrar el bloque de feedback sólo si feedback.isFeedbackPublished.
  • Si isFeedbackPublished && !isFeedbackAcknowledged, mostrar el formulario de acuse (comentario opcional + confirmación); tras PATCH .../acknowledge exitoso, refrescar el informe para que isFeedbackAcknowledged pase a true y el bloque quede de solo lectura.

Cuándo mostrar la acción. El backend no valida que la evaluación esté terminada ni canViewReport al generar el PDF — ese control es sólo del historial. Aplica en el cliente la misma regla de “Ver informe”: mostrar “Descargar informe” únicamente si isFinished && canViewReport del item del historial.

Opciones de descarga (comportamiento del portal de referencia). El portal ofrece un selector de alcance y un checklist de secciones, y los traduce a query params:

Opción en la UI Param Regla
Resumen ejecutivo / Informe completo mode=executive / mode=full Un solo valor a la vez.
Secciones desmarcadas hide=<claves separadas por coma> Enviar sólo en mode=full; en ejecutivo omitir hide. Si no se apagó ninguna sección, omitir el param.
Idioma activo de la app lang Enviar el locale de la interfaz (es-ES / en-US) para que el documento salga en el mismo idioma que el usuario está viendo.
— (informe propio) username Omitir.

Manejo de la respuesta.

  • Tratar el éxito como bytes, no como JSON, y verificar Content-Type: application/pdf antes de abrirlo. En error, leer el cuerpo como JSON para mostrar message (ver aviso en el endpoint).
  • Mostrar el message del backend tal cual (viene redactado para el usuario). Si el cuerpo no se puede parsear —por ejemplo, el 504 de API Gateway— mostrar un texto genérico localizado en la app, no un literal del backend.
  • El backend responde en español los errores independientemente de lang: ese parámetro sólo cambia el documento.

Estado de carga y reintentos.

  • Deshabilitar el botón y mostrar estado de carga mientras la petición está en vuelo (el portal lo hace con :loading). Un arranque en frío puede tardar ~17 s; configura un timeout del cliente mayor a 30 s para no cortar una generación válida antes que el servidor.
  • No lances descargas en paralelo, no pre-generes el PDF “por si acaso” ni reintentes en bucle: cada descarga dispara un render en un servicio compartido con límite de uso (su rechazo es el 503 de “muchos documentos generándose”). Ante ese 503, dejar que el usuario reintente manualmente.
  • No cachear el archivo como fuente de verdad: Cache-Control: no-store indica que el contenido cambia con los datos vigentes de la evaluación (por ejemplo, tras publicar o acusar recibo de feedback). Si se guarda una copia local para compartirla, tratarla como temporal.
  • Sin campañas activas: GET /me/evaluation devuelve [] — mostrar estado vacío (“No tienes evaluaciones activas”).
  • Roster vacío: GET /me/evaluation/{id}/type/{abbreviation} sin ítems — no debería ocurrir si la campaña apareció en el listado, pero si pasa, tratar como estado vacío, no como error.
  • Sin historial: GET /me/evaluation/history devuelve [] — mostrar estado vacío, ocultando los filtros de año/nivel salvo que existan años disponibles.
  • content/all con 404 (Evaluations for evaluated user not found): el par evaluador → evaluado no existe para esa campaña; tratar como error de carga y volver al roster. Con 400 (control de flujo) mostrar el motivo del mensaje. Por defensa, si llegara un 200 sin evaluation?.id, tratarlo también como error de carga y no como campaña sin secciones.