Hierarquia
Tenant (Instituicao)
└── Course (titulo, slug, status, theme, publishAt, aiTutorEnabled)
└── CourseModule (titulo, sortOrder)
└── CourseLesson (tipo, content JSON, sortOrder, isPublished, publishAt)
├── CourseLessonMedia (N:N com MediaAsset)
└── LessonCompletion (1:N por studentId)
CRUD
Cursos
POST /api/institution/courses Criar
GET /api/institution/courses Listar
GET /api/institution/courses/[id] Detalhe
PATCH /api/institution/courses/[id] Atualizar
DELETE /api/institution/courses/[id] Deletar (soft, status=archived)
POST /api/institution/courses/[id]/clone Clonar (deep copy)
POST /api/institution/courses/import Importar IMS CC
Scopes: courses:read (GET) | courses:write (POST/PATCH/DELETE).
Modulos
POST /api/institution/courses/[id]/modules Criar
PATCH /api/institution/courses/[id]/modules/[mid]
DELETE /api/institution/courses/[id]/modules/[mid]
PATCH /api/institution/courses/[id]/modules/reorder (bulk sortOrder)
Aulas
POST /api/institution/courses/[id]/modules/[mid]/lessons Criar
PATCH /api/institution/courses/[id]/modules/[mid]/lessons/[lid]
DELETE /api/institution/courses/[id]/modules/[mid]/lessons/[lid]
POST /api/institution/courses/[id]/lessons/reorder (bulk)
Status do curso
Course.status: draft | published | archived
- draft: nao visivel para alunos, editavel
- published: visivel a matriculados (respeitando publishAt)
- archived: nao visivel, apenas leitura para historico
Course.publishAt agenda publicacao automatica via cron.
Publicacao escalonada
Combine Course.publishAt com Lesson.publishAt para controle granular:
Curso publicado em 2026-06-01
Modulo 1 (sem publishAt) → disponivel desde 2026-06-01
Aula 1.1 (sem publishAt) → disponivel desde 2026-06-01
Aula 1.2 (publishAt: 2026-06-08) → disponivel a partir de 2026-06-08
Modulo 2 (sem publishAt)
Aula 2.1 (publishAt: 2026-06-15) → disponivel a partir de 2026-06-15
checkLessonAvailability() em apps/web/lib/lesson-availability.ts valida em TODOS os endpoints de aula (view, quiz/start, quiz/submit, complete, interactive).
Pre-requisitos entre aulas
Lesson.prerequisiteLessonId aponta para aula que DEVE estar completa antes de acessar.
checkLessonAvailability(lesson, { checkPrerequisite: true }) valida:
lesson.isPublished === truelesson.publishAt <= now- Se
prerequisiteLessonId: existeLessonCompletiondo aluno para essa aula
Clone de curso
POST /api/institution/courses/[id]/clone faz:
- Cria novo Course com slug
{slug}-clone-{n} - Para cada Module original → cria copia
- Para cada Lesson original → cria copia com mesmo content JSON
- NAO copia: Enrollments, LessonCompletions, ClassGroups, QuizAttempts, RAG ingestion
- Respeita limite de cursos do plano (checkTenantResourceLimit)
Import IMS Common Cartridge
POST /api/institution/courses/import body multipart/form-data com .imscc:
- Parser:
packages/core/src/content/imscc-parser.ts(cheerio-based XML parser) - Suporta: IMS CC v1.0, v1.1, v1.2, v1.3
- Recursos suportados: web links, file resources, basic LTI links, QTI 1.2 quizzes (mapeados para Quiz Engine), web content resources
- NAO suportados: H5P content packages, SCORM 2004 PIF, IMS CP
RAG ingestion
Course.autoSyncRag: Boolean controla reingestao automatica. Detalhes em RAG Ingestion.
Limitacoes
- Maximo 100 modulos por curso (soft limit, performance)
- Maximo 200 aulas por modulo (soft limit, UX)
- Content JSON por aula ate 5MB (validado no save)
- Slug deve ser unico por tenant + idioma