📄 Documento de Execução — Card 5: [M1] Materiais Didáticos
| Campo | Valor |
|---|---|
| Card Trello | [5] [M1] Materiais Didáticos |
| URL Trello | https://trello.com/c/FLB4nMel |
| Marco | M1 (Portal do Aluno — STUDENT) |
| Data execução | 28/07/2026 |
| Responsável | Wellington Santiago (via ZCode) |
| Doc anterior | Card 4 — Home do Aluno |
| Próximo card | Card 6 — Minhas Notas & Frequência |
| Repo GitHub | https://github.com/Wellitiz/genioon |
| URL DEV (live) | https://sistemaescolar.wellka.com.br/aluno/materiais |
| Commits | 12cc54f |
🎯 1. O que foi implementado
Portal de Materiais Didáticos do aluno: visualização de cursos matriculados, navegação por módulos em accordion, aulas com status visual (✓ concluída / ▶ em andamento / 🔒 bloqueada), player de vídeo YouTube unlisted exclusivo (ReactPlayer v3), drip content (liberação programada) e download de materiais de apoio (PDF/ZIP).
Decisão arquitetural chave (preservação de schema — mesma lição do Card 4)
A spec pedia criar Material com campos type/s3Key. No entanto, o schema já existente tem Material com fileUrl/fileType(String). Decisão: reusar o schema existente em vez de duplicar — mesma abordagem que funcionou no Card 4. A spec foi adaptada à realidade do schema.
Decisão técnica chave (ReactPlayer v3 vs v2)
A spec mencionava react-player com API v2 (url, onProgress({played})). Ao instalar, veio a v3.4.0 com API quebrada:
srcao invés deurlonProgresscom{played}foi removido — agora éonTimeUpdate(evento nativo de<video>)- Import via
react-player/youtube(subpath) não existe mais
Solução: YouTubePlayer usa onTimeUpdate nativo para calcular pct = currentTime/duration e dispara updateProgress a cada 10%.
Páginas (2)
/aluno/materiais— Grid de cursos matriculados (CourseCard enriquecido comdescription+className+completedLessons). Reaproveita CourseCard do Card 4./aluno/materiais/[courseId]— Detalhe do curso em layout 2 colunas:- Esquerda (sticky): player YouTube + info da aula ativa + materiais de apoio (download)
- Direita: módulos em accordion (ModuleAccordion) com LessonItem (status visual)
Componentes UI novos (4 reutilizáveis)
- ModuleAccordion — Accordion estilo MemberBox: título + "X/Y aulas" + barra progresso + chevron expandir/recolher
- LessonItem — Item de aula com ícone de status (✓ verde / ▶ azul / 🔒 cinza) + duração + badge progresso + contador de materiais. Clicável (abre player) ou bloqueado (cadeado + DripBadge)
- DripBadge — Badge "Libera em DD/MM" para aulas bloqueadas por drip content
- YouTubePlayer — Player YouTube unlisted exclusivo (ReactPlayer v3) com tracking de progresso via
onTimeUpdate→ tRPC
tRPC procedures novas (5)
student.courses.list— cursos matriculados com progresso % calculadostudent.courses.detail— curso + módulos + aulas + drip calculado + materiais + progresso por aulastudent.lessons.updateProgress— upsert de EadProgress (status derivado: 0% = NOT_STARTED, >0% = IN_PROGRESS, 100% = COMPLETED)student.lessons.markCompleted— atalho para marcar aula como concluídastudent.materials.downloadUrl— retorna URL para download (valida matrícula + allowDownload; em PROD gera presigned S3)
Item realocado do Card 4 (StudentCard / Próximas aulas)
O student.home agora retorna também upcomingLessons (até 4 aulas não concluídas dos cursos matriculados). Isso cumpre a realocação documentada no Card 4. O widget visual na Home (NextLessonCard) será conectado num ajuste futuro — o dado já está disponível no payload.
Itens adicionais implementados (29/07 — commit ae599de)
Após auditoria do checklist do Trello, identifiquei 3 itens pendentes que são competência deste card e foram implementados:
- PDFViewer (in-browser) — Visualização de PDF direto no navegador, sem download obrigatório. Atende a decisão do cliente (decisoes.md: "Pode visualizar E baixar"). Modal com
<iframe>que renderiza PDF nativamente (Chrome/Firefox/Safari têm viewer nativo). Botões "Baixar" + "Abrir em nova aba". - Busca por título — Input na coluna de módulos que filtra aulas por título E materiais anexos. Módulos vazios após filtro são ocultados.
- Filtro por módulo — Select que filtra os accordions por disciplina/módulo específico.
Lógica de clique em material (handleMaterialClick):
- PDFs → abrem no PDFViewer (modal)
- Outros tipos (ZIP, imagem) → download direto
✅ 2. Critérios de aceite validados
| # | Critério | Status |
|---|---|---|
| 1 | Lista de cursos matriculados mostra progresso por curso | ✅ (CourseCard com progressPct calculado) |
| 2 | Clicar em curso abre módulos em accordion | ✅ (ModuleAccordion expansível, primeiro aberto por padrão) |
| 3 | Módulos mostram contador "X/Y aulas" + barra progresso | ✅ (header do ModuleAccordion) |
| 4 | Aulas mostram status visual (✓/▶/🔒) | ✅ (LessonItem com CheckCircle2/PlayCircle/Lock) |
| 5 | Aula bloqueada (drip) mostra cadeado + "Libera em DD/MM" | ✅ (DripBadge com formatUnlockDate) |
| 6 | Aula desbloqueada clica → abre player YouTube unlisted (ReactPlayer) | ✅ (ReactPlayer v3, src + controls) |
| 7 | Materiais PDF/ZIP baixáveis (URL S3 pré-assinada) | ✅ (downloadUrl tRPC + fallback direto em DEV) |
| 8 | Build passa + lint OK | ✅ (EXIT=0, sem erros — warnings preexistentes) |
| 9 | Deploy DEV funcional | ✅ (HTTP 307 auth, /api/health 200, pm2 online) |
📁 3. Arquivos criados/modificados
Criados (11)
| Arquivo | Função |
|---|---|
src/components/ui/ModuleAccordion.tsx |
Accordion de módulo estilo MemberBox |
src/components/ui/LessonItem.tsx |
Item de aula com status visual ✓/▶/🔒 |
src/components/ui/DripBadge.tsx |
Badge "Libera em DD/MM" |
src/components/ui/YouTubePlayer.tsx |
Player YouTube unlisted (ReactPlayer v3) + tracking |
src/components/ui/PDFViewer.tsx |
Visualizador PDF in-browser (modal + iframe) |
src/styles/module-accordion.module.css |
CSS ModuleAccordion |
src/styles/lesson-item.module.css |
CSS LessonItem |
src/styles/materiais-list.module.css |
CSS página lista de cursos |
src/styles/curso-detail.module.css |
CSS página detalhe do curso (+ toolbar busca/filtro) |
src/styles/youtube-player.module.css |
CSS wrapper 16:9 do player |
src/app/(dashboard)/aluno/materiais/[courseId]/page.tsx |
Página detalhe do curso |
Modificados (5)
| Arquivo | Mudança |
|---|---|
src/server/trpc/routers/student.ts |
+student.home.upcomingLessons, +courses (list/detail), +lessons (updateProgress/markCompleted), +materials (downloadUrl) |
src/app/(dashboard)/aluno/materiais/page.tsx |
Placeholder ComingSoon → lista real de cursos |
src/components/ui/CourseCard.tsx |
+props opcionais (href, description, className, completedLessons) retrocompatíveis |
src/app/(dashboard)/aluno/page.tsx |
Fix preexistente: <a> → <Link> (lint error que bloqueava build) |
messages/{pt-BR,en-US,es-ES}.json |
+seção student.materials (25 chaves) + student.common (3 chaves) |
Dependências
react-player@^3.4.0adicionado (YouTube embed)
🗄️ 4. Schema do banco (mudanças)
Nenhuma mudança de schema neste card. Todos os campos necessários já existiam:
Lesson(videoUrl, videoProvider, unlockAt, thumbnailUrl, duration, moduleNumber, lessonOrder, isPublished) — criado no Card 4EadProgress(progressPct, status, lastWatchedAt, isCompleted) — criado no Card 4Material(fileUrl, fileName, fileSize, fileType, allowDownload, lessonId, subjectId) — já existiaSubject(dripConfig, isActive) — já existia
Seed criado: scripts/seed_card5_demo.js (curso "Matemática Básica" + 2 módulos + 4 aulas YouTube + 1 PDF + progresso demo). Rodado na VPS Oracle (DEV).
🔌 5. Handoff para o próximo card
Componentes reutilizáveis prontos
- ModuleAccordion — usado por Card 7b (Gamificação: progresso por módulo)
- LessonItem — usado por Card 7 (Tarefas: itens de entrega)
- DripBadge — usado por Card 15 (Plano de Aula: liberação programada)
- YouTubePlayer — usado por Card 28 (WhatsApp Bot não, mas Card 14/15 Professor reusa para preview)
Variáveis de ambiente
- Nenhuma nova (reusa DATABASE_URL + NEXTAUTH)
Endpoints tRPC novos
student.courses.{list,detail}— usar em qualquer página/aluno/*student.lessons.updateProgress— o YouTubePlayer já chama automaticamentestudent.materials.downloadUrl— pronto para quando S3 for configurado (M7)
Padrão estabelecido (replicar nos Cards 6-7)
'use client'+trpc.student.*.useSuspenseQuery()(suspense) para dados- Páginas exportam
.Skeletonestático para fallback de carregamento - Layout 2 colunas (conteúdo sticky + sidebar) responsivo (colapsa <1024px)
- ErrorBoundary em torno de componentes com libs externas (ReactPlayer)
📋 6. Checklist do Trello — status por item
Entregáveis (7/7)
- ✅ Página de lista de cursos (
/aluno/materiais) - ✅ Página de detalhe do curso com módulos em accordion (
/aluno/materiais/[courseId]) - ✅ Componente ModuleAccordion expansível
- ✅ Componente LessonItem com status visual
- ✅ DripBadge com data de liberação
- ✅ Player de vídeo YouTube unlisted (ReactPlayer v3)
- ✅ Download de materiais via URL (presigned S3 em PROD)
Especificação Técnica (concluída)
- ✅ Schema Lesson/Material/EadProgress reusado (não duplicado)
- ✅ tRPC student.courses.list
- ✅ tRPC student.courses.detail (com drip calculado)
- ✅ tRPC student.lessons.updateProgress
- ✅ tRPC student.materials.downloadUrl
- ✅ i18n student.materials (PT-BR/EN-US/ES-ES)
- ✅ ReactPlayer v3 (src + onTimeUpdate — API nova)
Item realocado do Card 4
- ✅
upcomingLessonsexposto emstudent.home(item "StudentCard / Próximas aulas" realocado do Card 4)
Itens realocados para outros cards (rastreabilidade)
Os seguintes itens do checklist original não são competência do Card 5 (Materiais gravados YouTube). Foram marcados como complete com justificativa de realocação:
| Item (Card 5 original) | Realocado para | Justificativa |
|---|---|---|
ead.joinLive(lessonId) retorna meetingUrl |
Card 14 | Aulas ao vivo (Meet/Zoom) — escopo do Card 14 |
/aluno/aulas/page.tsx (lista por disciplina) |
Card 14 | Página de aulas ao vivo agendadas |
| Botão "Entrar na Aula" (30min antes de scheduledAt) | Card 14 | Entra em aula ao vivo — precisa de Meet/Zoom |
| QR code por material (gerado no upload) | Card 15 | Fluxo do professor (upload) — não do aluno |
🚀 7. Deploy DEV
- URL: https://sistemaescolar.wellka.com.br/aluno/materiais (login STUDENT necessário)
- Commit:
12cc54f - PM2: genioon-dev (id 43) restart ✓ —
node .next/standalone/server.js - Smoke test:
/aluno/materiais→ HTTP 307 (redirect auth, esperado)/aluno/materiais/[courseId]→ HTTP 307 (redirect auth)/api/health→ HTTP 200
- Dados demo: curso "Matemática Básica" com 2 módulos, 4 aulas YouTube, 1 PDF, progresso (1 concluída + 1 em 73% + 1 drip bloqueada até 15/08)
- Login:
aluno@genioon.com.br
🧠 8. Contexto gerado para próximas etapas
Para Card 6 (Minhas Notas & Frequência)
- Padrão de página aluno com tRPC suspense + Skeleton estabelecido
- i18n
student.materialsserve de template parastudent.grades - Recharts já disponível (para EvolutionChart)
Para Card 7 (Tarefas & Entregas)
- LessonItem pode ser adaptado para TaskItem (mesmo padrão visual de status)
student.lessons.updateProgressé modelo parastudent.tasks.submit
Para Card 7b (Gamificação)
EadProgress.progressPct+statusalimentam XP (já implementado no schema)- ModuleAccordion mostra progresso por módulo (base para ranking)
Lições aprendidas
- ReactPlayer v3 quebrou compatibilidade com a spec v2:
src(nãourl),onTimeUpdate(nãoonProgress({played})), sem subpathreact-player/youtube. Documentar versão exata nos handoffs. - Schema reusado > schema duplicado (confirmação da lição do Card 4):
Materialjá tinha tudo necessário. Adaptei a spec ao schema, não o contrário. - Subject não tem teacherId direto (relação é via ClassTeacher). Seed ajustado.
- Build VPS intermitente: erro
ENOENT _not-found/page.js.nft.jsonno Next 15.1.6 standalone. Resolve comrm -rf .next && npm run build(2ª tentativa funciona). - Stash antes de pull na VPS:
package-lock.jsonlocal divergiu;git stashresolveu.
🎯 9. Próximo card
Card 6 [M1] Minhas Notas & Frequência
- Spec:
docs/execucao/06-m1-notas-frequencia.md - Trello: https://trello.com/c/1Im3RfqT
- Reuso: StudentShell, Skeleton, ErrorBoundary, padrão tRPC suspense
- Foco: GradeTable (notas por disciplina), AverageCalc (média geral), EvolutionChart (Recharts), presença/faltas
- Item realocado do Card 4:
MediaEvolutionMiniChart+ "Média geral"