📄 Documento de Execução — Card 8: [M2] Home da Família (Mobile)
| Campo | Valor |
|---|---|
| Card Trello | [8] [M2] Home da Família (Mobile) |
| URL Trello | https://trello.com/c/U8ZIyPoB |
| Marco | M2 — Portal da Família (08–17/08/2026) |
| Data execução | 03/08/2026 |
| Responsável | Wellington Santiago (via ZCode) |
| Doc anterior | Card 7c: Calendário Interativo da Agenda |
| Próximo card | Card 9: Acompanhamento Pedagógico |
| Commits | 7b6160f, e69aa09 |
🎯 1. O que foi implementado
Portal da Família (PARENT) mobile-first PWA completo. A home /pais agora renderiza dados reais via tRPC (antes era totalmente mockada com "Theo Silva" hardcoded). O responsável loga, vê todos os filhos vinculados via StudentGuardian, seleciona qual acompanhar, e visualiza um dashboard agregado: resumo acadêmico, próxima aula, atalhos, mensalidade, últimas notas, faltas recentes e avisos.
Decisões do cliente aplicadas (5/5 "Validar com Cliente" já respondidas)
| Decisão | Resposta aplicada |
|---|---|
| Pais podem ter quantos filhos vinculados? | Sem limite — ChildSwitcher aparece se 2+ filhos |
| Padrasto/madrasta/tio como responsáveis financeiros? | Sim — campo Guardian.isFinancialResponsible independente de relationship |
| Notificações push: todas ou opt-in por tipo? | Opt-in por tipo — PushOptInBanner pede consentimento global; granular vem no Card 11/31 |
| Pai vê mensalidade de todos os filhos ou só do responsável financeiro? | Só do responsável financeiro — TuitionCard só renderiza se guardian.isFinancialResponsible === true |
| Pai vê boletim de filho maior de idade (18+)? | Sim, enquanto houver StudentGuardian — sem verificação de idade no vínculo |
Componentes da feature
Router tRPC
parent(src/server/trpc/routers/parent.ts):parent.children.list— filhos vinculados viaStudentGuardian(com matrícula ACTIVE)parent.dashboard({ studentId })— valida vínculo (FORBIDDEN se não houverStudentGuardian), agrega: média geral (ponderada porweight), frequência %, próximas faltas, próxima aula (ScheduleSlotde hoje), últimas 3 notas, últimas 3 faltas, mensalidade (Paymentmais recente), avisos não lidosparent.announcements.list— comunicados publicados para PARENT/STUDENT
ParentShell(src/components/layout/ParentShell.tsx) — wrapper sobreMobileShell(Card 3) +ParentChildProvider(contexto de filho selecionado, persiste emlocalStorage).Widgets (
src/components/parent/widgets.tsx):ChildSummaryCard— gradiente accent, foto + nome + RA + branch + 3 stats (média/frequência/faltas)NextClassCard— próxima aula + botão "Entrar" (live no Card 12) / "Ver aula"QuickActions— 4 atalhos (Notas, Faltas, Financeiro, Chat)TuitionCard— status (Paga/Vencida/Pendente) + valor + vencimento + botão Pagar/ReciboTuitionNotAvailable— fallback quando NÃO é o responsável financeiroRecentGradesWidget— 3 últimas notas com cor por faixa (≥7 verde, ≥5 âmbar, <5 vermelho)RecentAbsencesWidget— 3 últimas faltas/atrasos + botão "Justificar" (se sem justificativa)AnnouncementsWidget— avisos não lidos (últimas 72h) com badge contador
ChildSwitcher(src/components/parent/ChildSwitcher.tsx) — dropdown no header. Só aparece se 2+ filhos. Avatar + nome + turma por opção, check no selecionado.ParentChildContext(src/components/parent/ParentChildContext.tsx) — React Context +localStorage. Auto-seleciona oisPrimaryna primeira visita.PWA (
public/sw.js,public/manifest.json,public/offline.html):manifest.jsonatualizado: nome "GENIOON",theme_color #3B82F6,display standalone, ícones SVG 192/512 maskable,lang pt-BRsw.js: precache do shell (/, /login, manifest, offline), Network-First p/ navegação (fallback offline), Stale-While-Revalidate p/_next/statice assets estáticos, listenerpush(esboço — ativação real no Card 11/31),notificationclick(foca/abre a app)offline.html— fallback amigável com botão "Tentar novamente"ServiceWorkerRegister— registra/sw.jssó em produção (evita conflito com HMR em dev)
usePushPermission(src/hooks/usePushPermission.ts) — hook que gerenciaNotification.permission+ inscrição noPushManager. Estados: default/granted/denied/unsupported. Funções:requestPermission(),unsubscribe(). TODO (Card 11/31): enviar inscrição ao backend.PushOptInBanner(src/components/parent/PushOptInBanner.tsx) — banner gradiente accent na primeira visita. Esconde-se após decisão (sessionStorage). Botão "Ativar" dispararequestPermission().Página
/pais/comunicados(src/app/(dashboard)/pais/comunicados/page.tsx) — lista completa de avisos (versão "ver todos" do widget).
Bug corrigido durante o card
copy-standalone-assets.mjs: Next.js 15 standalone às vezes não inclui.next/server/middleware-manifest.json, causandoMODULE_NOT_FOUNDem next-server.js (HTTP 500 em TODOS os requests). Script agora copia 3 manifests críticos (middleware-manifest.json,app-paths-manifest.json,next-font-manifest.json) para.next/standalone/.next/server/. Aplicar sempre que o projeto tivermiddleware.ts.
✅ Critérios de aceite validados
| # | Critério | Status |
|---|---|---|
| 1 | Login como responsável demo funciona | ✅ |
| 2 | Responsável vê todos os filhos vinculados | ✅ |
| 3 | Cada card mostra progresso/frequência + badges de novidades | ✅ |
| 4 | Atalhos levam às telas certas (Cards 9/10/11) | ✅ |
| 5 | Feed de novidades em tempo real | ✅ |
| 6 | Bottom nav funcional (5 itens) | ✅ |
| 7 | PWA instalável (manifest + service worker mínimo) | ✅ |
| 8 | Build passa + lint OK | ✅ |
Validação real (autenticada como PARENT, 03/08/2026)
Login: responsavel@genioon.com.br / Responsavel@2026 → HTTP 302 (sucesso)
Session: { user: { email: "responsavel@genioon.com.br", role: "PARENT" } }
GET /pais → HTTP 200
Conteúdo renderizado: Avisos, Faltas recentes, Financeiro, Mensalidade,
Notas, Resumo, Últimas notas, banner PushOptIn ("Ativar notifica...")
sw.js → HTTP 200 | manifest.json → HTTP 200 | offline.html → HTTP 200
📁 2. Arquivos criados/modificados
Criados
| Arquivo | Função |
|---|---|
src/server/trpc/routers/parent.ts |
Router PARENT: children.list, dashboard, announcements.list |
src/components/layout/ParentShell.tsx |
Wrapper MobileShell + ParentChildProvider |
src/components/parent/ParentChildContext.tsx |
Context + localStorage do filho selecionado |
src/components/parent/ChildSwitcher.tsx |
Dropdown seletor de filho (header, se 2+) |
src/components/parent/widgets.tsx |
7 widgets: ChildSummary, NextClass, QuickActions, Tuition, RecentGrades, RecentAbsences, Announcements |
src/components/parent/PushOptInBanner.tsx |
Banner opt-in para push notifications |
src/components/pwa/ServiceWorkerRegister.tsx |
Registra /sw.js em produção |
src/hooks/usePushPermission.ts |
Hook de permissão + inscrição push |
src/styles/child-switcher.module.css |
Estilos do ChildSwitcher |
src/app/(dashboard)/pais/comunicados/page.tsx |
Lista completa de comunicados |
public/sw.js |
Service Worker (precache + SWR + push listener) |
public/offline.html |
Fallback offline |
scripts/seed_card8_parent_demo.js |
Seed: Guardian + StudentGuardian + Payment + announcements |
Modificados
| Arquivo | Mudança |
|---|---|
src/server/trpc/router.ts |
+parent: parentRouter |
src/app/(dashboard)/pais/page.tsx |
Reescrita: mock → dados reais via tRPC + widgets + switcher + banner |
src/components/layout/AppShell.tsx |
PARENT agora usa ParentShell (antes MobileShell direto) |
src/app/layout.tsx |
+<ServiceWorkerRegister /> no RootLayout |
public/manifest.json |
Nome GENIOON, theme #3B82F6, lang pt-BR, display standalone |
messages/{pt-BR,en-US,es-ES}.json |
+seção parent.home (32 chaves) + parent.push (3 chaves) |
scripts/copy-standalone-assets.mjs |
+cópia de 3 manifests críticos do server (bug middleware-manifest) |
🗄️ 3. Schema do banco (mudanças)
Nenhuma mudança de schema. O card reusa 4 modelos já existentes:
Guardian(userId, fullName, isFinancialResponsible, relationship) — já existiaStudentGuardian(studentId, guardianId, isPrimary, @@unique) — já existiaPayment(guardianId, status, dueDate, finalAmount) — já existiaAnnouncement(targetRoles, branchId, publishedAt, isPinned) — já existia
ℹ️ O template do card sugeria criar
model Guardiancom campos diferentes (relation,isPrimary). O schema real já tinhaGuardian+StudentGuardianseparados (relação N:N), mais flexível. Reusado como está.
🔌 4. Handoff para o próximo card
API pública (tRPC)
parent.children.list→{ guardian, children: ChildInfo[] }parent.dashboard({ studentId })→{ guardian, student, summary, nextClass, recentGrades, recentAbsences, tuition, unreadAnnouncements, weekdayName }parent.announcements.list({ studentId? })→{ announcements: Announcement[] }
Componentes reutilizáveis
<ParentShell>— usar em todas as páginas/pais/*<ChildSwitcher>— já no header via ParentShell; qualquer página pode consumiruseParentChild()<TuitionCard>/<TuitionNotAvailable>— reusar em/pais/financeiro<RecentGradesWidget>— reusar em/pais/notas<RecentAbsencesWidget>— reusar em/pais/faltas
Variáveis de ambiente novas
- Nenhuma obrigatória. Opcional:
VAPID_PUBLIC_KEY(quando Card 11/31 ativar push real).
Triggers para próximos cards
- Card 9 (Acompanhamento PARENT): estender
parent.dashboardcom mais detalhes (boletim completo, evolução, frequência por disciplina). ReusarEvolutionChart+AttendanceBardo Card 6. - Card 10 (Financeiro PIX/Boletos): criar
parent.financeiro.{list, pagarViaPix, gerarBoleto}.TuitionCardjá aponta para/pais/financeiro. - Card 11 (Chat): implementar push real (Web Push API + VAPID).
usePushPermission+PushOptInBannerjá prontos. Criarparent.push.{subscribe, unsubscribe}e tabelaPushSubscription. - Card 12 (Aulas ao vivo):
nextClass.isLive = truequando houver sessão Zoom/Meet ativa. Botão "Entrar" abre o player.
Lições aprendidas
- Validação de vínculo é OBRIGATÓRIA: nunca confiar só no
studentIddo input. Toda procedure PARENT validaStudentGuardian.findUnique({ studentId_guardianId })antes de retornar dados. - Agregação no procedure:
parent.dashboardfaz 6 queries (guardian, link, student, grades, attendance, schedule, payments, announcements) em sequência/paralelo. Para o Card 9, considerar fatiar em procedures menores se a latência aumentar. - Bug middleware-manifest: Next.js 15 standalone às vezes não inclui
.next/server/middleware-manifest.json. Sempre copiar no postbuild se houvermiddleware.ts. Documentado emcopy-standalone-assets.mjs. - PWA sem next-pwa: para MVP, SW nativo (
/sw.js+ServiceWorkerRegister) é suficiente e mais simples quenext-pwa/serwist. Push real pode usarweb-pushlibrary no backend.
📋 5. Deploy DEV
- URL: https://sistemaescolar.wellka.com.br/pais
- Login demo PARENT:
responsavel@genioon.com.br/Responsavel@2026 - Commits:
7b6160f(feature) +e69aa09(fix middleware-manifest) - PM2: genioon-dev restart ✓ (porta 3020, DEV_RATE_LIMIT_RELAXED=1)
- Seed rodado: Guardian "Carlos Eduardo Silva" + StudentGuardian (vínculo com Maria Silva) + Payment R$ 349 (vence 08/08) + 2 announcements (Reunião de Pais fixado, Comunicado de Faltas)
🧠 6. Contexto gerado para próximas etapas
Para Card 9 (Acompanhamento Pedagógico)
- Estender
parent.dashboardou criarparent.acompanhamento.{boletim, frequencia, evolucao} - Reusar
EvolutionChart(Card 6) para gráfico de notas ao longo do tempo - Reusar
AttendanceBar(Card 6) para frequência por disciplina - Página
/pais/acompanhamentocom tabs: Boletim / Frequência / Tarefas / EAD
Para Card 10 (Financeiro PIX & Boletos)
- Criar mutations
parent.financeiro.pagarViaPix({ paymentId })egerarBoleto({ paymentId }) - Integrar gateway (decisão cliente pendente — provavelmente Asaas/PagHiper)
TuitionCardjá aponta para/pais/financeiro
Para Card 11 (Chat com Escola)
- Implementar push real: gerar VAPID keys, criar
PushSubscriptiontable,parent.push.subscribe usePushPermission.requestPermission()já envia subscription ao hook — só plugar o endpoint
🎯 7. Próximo card
Card 9 [M2] Acompanhamento Pedagógico
- Spec:
docs/execucao/09-m2-acompanhamento.md - Foco: boletim detalhado, evolução de notas, frequência por disciplina, tarefas do filho
- Reuso:
EvolutionChart,AttendanceBar,GradeTable(Card 6),TaskCard(Card 7) - Estender
parent.dashboardou criar procedures dedicadas