Lexios / Tutoriais
Módulos

Treinamentos · Referência técnica

Contratos do módulo de treinamentos — endpoints, schema do editor, eventos xAPI e fluxo do participante

Treinamentos · Referência técnica

Visão Geral

Esta referência cobre o contrato técnico do módulo de treinamentos — endpoints, schema do editor (BlockNote + bloco Mux) e o vocabulário de eventos xAPI gravados no LRS interno (xapi_statements). A documentação voltada ao usuário final está em Treinamentos EAD.

Endpoints

Admin (trainings:*)

MétodoRotaPermissão
GET/api/trainingstrainings:view
POST/api/trainingstrainings:create
GET/api/trainings/{id}trainings:view
PUT/api/trainings/{id}trainings:edit
DELETE/api/trainings/{id}trainings:delete
POST/api/trainings/{id}/publishtrainings:edit
POST/api/trainings/{tid}/modulestrainings:edit
PUT/api/trainings/{tid}/modules/{id}trainings:edit
DELETE/api/trainings/{tid}/modules/{id}trainings:edit
PUT/api/trainings/{tid}/modules/reordertrainings:edit
GET/api/trainings/{tid}/modules/{id}/contenttrainings:view
PUT/api/trainings/{tid}/modules/{id}/contenttrainings:edit
PUT/api/trainings/{tid}/modules/{id}/quiztrainings:edit
DELETE/api/trainings/{tid}/modules/{id}/quiztrainings:edit
POST/api/trainings/{tid}/enrollmentstrainings:participants
POST/api/trainings/{tid}/enrollments/send-invitestrainings:participants

Auth do participante (anônimas)

MétodoRotaBody
POST/api/participant-auth/token{ token }
POST/api/participant-auth/login{ email, password }
GET/api/participant-auth/me— (requer participant:access)

Player do participante (participant:access)

MétodoRota
POST/api/participant/trainings/{id}/launch
GET/api/participant/trainings/{id}
GET/api/participant/trainings/{tid}/modules/{id}
POST/api/participant/trainings/{tid}/modules/{id}/complete
POST/api/participant/trainings/{tid}/quizzes/{id}/submit
POST/api/participant/trainings/{id}/complete
POST/api/participant/xapi/statements

Schema do Editor

O conteúdo de cada módulo é persistido como JSON do BlockNote na coluna Content (jsonb) de TrainingModule. O backend não interpreta o documento — apenas armazena e devolve.

O schema do editor é estendido com um bloco custom muxVideo:

BlockNoteSchema.create({
  blockSpecs: { ...defaultBlockSpecs, muxVideo: MuxBlock }
})

Bloco muxVideo

PropriedadeTipoDefaultDescrição
playbackIdstring""Identificador do asset publicado no Mux
titlestring""Título exibido em metadata/relatórios

content: "none" — bloco atômico, sem filhos.

Persistência

  • GET /api/trainings/{tid}/modules/{id}/content retorna { content: string \| null, quiz: QuizPayload \| null }. O campo content é a string JSON inteira do BlockNote.
  • PUT .../content aceita { content: string } — o frontend serializa via JSON.stringify(editor.document).
  • O auto-save dispara após 1500ms sem digitação. Falhas não são reenviadas — o usuário precisa modificar algo de novo para retentar.

Schema do Quiz

Cada módulo pode ter no máximo um quiz. Questões aceitam três tipos:

questionTypeValidação backend
single_choiceResposta correta se selectedOptionIds == [correctId]
multiple_choiceResposta correta se o conjunto de selecionados é igual ao conjunto de corretas
true_falseMesma regra de single_choice (alternativas fixas: Verdadeiro / Falso)

Options é serializado como JSON string:

[
  { "id": "uuid", "label": "...", "isCorrect": true, "feedback": "opcional" }
]

O endpoint GET /api/participant/trainings/.../modules/{id} retira os campos isCorrect e feedback antes de devolver para o participante (ver StripCorrectness em GetModulePlayer/Endpoint.cs).

A nota é calculada como round((points_earned / points_total) * 100). A aprovação compara contra Training.Evaluation.PassingScore.

Vocabulário xAPI

Statements são imutáveis, gravados em xapi_statements. Schema segue o padrão actor · verb · object · result · context.

Verbos suportados

VerboURIOrigem
registeredhttp://adlnet.gov/expapi/verbs/registeredBackend (matrícula)
invitedhttp://id.tincanapi.com/verb/invitedBackend (envio de convite)
launchedhttp://adlnet.gov/expapi/verbs/launchedBackend (/launch)
initializedhttp://adlnet.gov/expapi/verbs/initializedFrontend (mount do módulo)
progressedhttp://adlnet.gov/expapi/verbs/progressedFrontend (Mux 25/50/75/90%)
experiencedhttp://adlnet.gov/expapi/verbs/experiencedFrontend (Mux play/ended)
answeredhttp://adlnet.gov/expapi/verbs/answeredFrontend (por questão, antes do submit)
attemptedhttp://adlnet.gov/expapi/verbs/attemptedBackend (/quizzes/.../submit)
passed / failedhttp://adlnet.gov/expapi/verbs/passed+failedBackend (resultado da tentativa)
completedhttp://adlnet.gov/expapi/verbs/completedBackend (módulo e treinamento)
satisfiedhttp://adlnet.gov/expapi/verbs/satisfiedBackend (certificado emitido)

Endpoint de passthrough do frontend

POST /api/participant/xapi/statements aceita statements granulares emitidos pelo player. Allowlist de verbos: initialized, progressed, experienced, answered. Tipos de objeto aceitos: module, video, question, training.

Body:

{
  "verb": "progressed",
  "objectType": "video",
  "objectId": "<module guid>",
  "objectName": "Aula 1 — LGPD",
  "trainingId": "<training guid>",
  "moduleId": "<module guid>",
  "result": { "duration": "PT45S", "score": { "raw": 45, "min": 0, "max": 180, "scaled": 0.25 } },
  "extras": { "muxPlaybackId": "...", "milestone": 25, "blockId": "..." }
}

O backend valida que o objectId pertence a um treinamento em que o participante está matriculado, anexa trainingId/enrollmentId/moduleId ao Context e grava como XApiStatement. Verbos fora do allowlist retornam 400. Tipos de objeto inválidos retornam 400. Objetos não-pertencentes ao treinamento retornam 404.

Granularidade de tracking de vídeo

O bloco MuxBlock em modo read-only registra:

EventoVerboQuando
playexperienced (phase: started)Primeiro play do vídeo
progressprogressedAo cruzar 25%, 50%, 75% e 90% (não-repetível por sessão)
endedexperienced (phase: ended, result.completion: true)Vídeo terminou

Cada evento carrega muxPlaybackId no extras para correlação posterior com o painel do Mux.

Autenticação do Participante

JWT separado do JWT do tenant. Claims:

ClaimOrigem
tenant_idTenant dono do treinamento
participant_idTrainingParticipant.Id
participant_email / participant_nameCópia das colunas do participante
enrollment_id / training_idApenas no fluxo de link mágico
permissionsSempre participant:access

Expiração: 8 horas. O AccessToken da matrícula tem expiração própria (ExpiresAt) configurada no momento da matrícula.

participant:access não está em Permissions.All — não pode ser atribuída a roles do tenant.

Erros & Edge Cases

CenárioEndpointResposta
Token de matrícula expiradoPOST /participant-auth/token400 com mensagem de expiração
Login com senha mas sem matrículas ativasPOST /participant-auth/login400 "Nenhuma matrícula ativa…"
Tentar concluir módulo com quizPOST .../modules/{id}/complete400 "Este módulo possui quiz"
Submeter quiz já aprovadoPOST .../quizzes/{id}/submit400 "Quiz já foi aprovado"
Submeter quiz com MaxAttempts atingidoidem400 "Limite de tentativas atingido"
AllowRetake = false e segunda tentativaidem400 "Esta avaliação não permite retentativas"
Concluir treinamento com módulos pendentesPOST .../complete400 "Existem N módulo(s) não concluído(s)"
Verbo xAPI fora do allowlistPOST /participant/xapi/statements400 "Verbo não permitido"

Configuração

ChaveDefaultUso
App:TrainingPortalUrlhttps://app.lexios.devBase do link de acesso enviado por e-mail ({base}/treinamento/acesso?token=...)
App:FrontendUrlFallback caso TrainingPortalUrl esteja ausente

Em desenvolvimento local, ambos apontam para http://localhost:3000 (ver appsettings.Development.json).

Nesta página