Entrar como:FuncionalidadesDoc
Pular para o conteúdo
← Voltar para a visão geral
✅ ConcluídoCard #8 · M2

Home da Família (Mobile)

Marco M2: Portal da Família (PARENT · PWA)

📅02 de agosto de 2026
👤Wellington Santiago (via ZCode)
🔧Commits: 7b6160fe69aa09

📄 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 limiteChildSwitcher 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 tipoPushOptInBanner 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 financeiroTuitionCard 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

  1. Router tRPC parent (src/server/trpc/routers/parent.ts):

    • parent.children.list — filhos vinculados via StudentGuardian (com matrícula ACTIVE)
    • parent.dashboard({ studentId })valida vínculo (FORBIDDEN se não houver StudentGuardian), agrega: média geral (ponderada por weight), frequência %, próximas faltas, próxima aula (ScheduleSlot de hoje), últimas 3 notas, últimas 3 faltas, mensalidade (Payment mais recente), avisos não lidos
    • parent.announcements.list — comunicados publicados para PARENT/STUDENT
  2. ParentShell (src/components/layout/ParentShell.tsx) — wrapper sobre MobileShell (Card 3) + ParentChildProvider (contexto de filho selecionado, persiste em localStorage).

  3. 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/Recibo
    • TuitionNotAvailable — fallback quando NÃO é o responsável financeiro
    • RecentGradesWidget — 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
  4. ChildSwitcher (src/components/parent/ChildSwitcher.tsx) — dropdown no header. Só aparece se 2+ filhos. Avatar + nome + turma por opção, check no selecionado.

  5. ParentChildContext (src/components/parent/ParentChildContext.tsx) — React Context + localStorage. Auto-seleciona o isPrimary na primeira visita.

  6. PWA (public/sw.js, public/manifest.json, public/offline.html):

    • manifest.json atualizado: nome "GENIOON", theme_color #3B82F6, display standalone, ícones SVG 192/512 maskable, lang pt-BR
    • sw.js: precache do shell (/, /login, manifest, offline), Network-First p/ navegação (fallback offline), Stale-While-Revalidate p/ _next/static e assets estáticos, listener push (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.js só em produção (evita conflito com HMR em dev)
  7. usePushPermission (src/hooks/usePushPermission.ts) — hook que gerencia Notification.permission + inscrição no PushManager. Estados: default/granted/denied/unsupported. Funções: requestPermission(), unsubscribe(). TODO (Card 11/31): enviar inscrição ao backend.

  8. PushOptInBanner (src/components/parent/PushOptInBanner.tsx) — banner gradiente accent na primeira visita. Esconde-se após decisão (sessionStorage). Botão "Ativar" dispara requestPermission().

  9. 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, causando MODULE_NOT_FOUND em 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 tiver middleware.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á existia
  • StudentGuardian (studentId, guardianId, isPrimary, @@unique) — já existia
  • Payment (guardianId, status, dueDate, finalAmount) — já existia
  • Announcement (targetRoles, branchId, publishedAt, isPinned) — já existia

ℹ️ O template do card sugeria criar model Guardian com campos diferentes (relation, isPrimary). O schema real já tinha Guardian + StudentGuardian separados (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 consumir useParentChild()
  • <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.dashboard com mais detalhes (boletim completo, evolução, frequência por disciplina). Reusar EvolutionChart + AttendanceBar do Card 6.
  • Card 10 (Financeiro PIX/Boletos): criar parent.financeiro.{list, pagarViaPix, gerarBoleto}. TuitionCard já aponta para /pais/financeiro.
  • Card 11 (Chat): implementar push real (Web Push API + VAPID). usePushPermission + PushOptInBanner já prontos. Criar parent.push.{subscribe, unsubscribe} e tabela PushSubscription.
  • Card 12 (Aulas ao vivo): nextClass.isLive = true quando houver sessão Zoom/Meet ativa. Botão "Entrar" abre o player.

Lições aprendidas

  1. Validação de vínculo é OBRIGATÓRIA: nunca confiar só no studentId do input. Toda procedure PARENT valida StudentGuardian.findUnique({ studentId_guardianId }) antes de retornar dados.
  2. Agregação no procedure: parent.dashboard faz 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.
  3. Bug middleware-manifest: Next.js 15 standalone às vezes não inclui .next/server/middleware-manifest.json. Sempre copiar no postbuild se houver middleware.ts. Documentado em copy-standalone-assets.mjs.
  4. PWA sem next-pwa: para MVP, SW nativo (/sw.js + ServiceWorkerRegister) é suficiente e mais simples que next-pwa/serwist. Push real pode usar web-push library 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.dashboard ou criar parent.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/acompanhamento com tabs: Boletim / Frequência / Tarefas / EAD

Para Card 10 (Financeiro PIX & Boletos)

  • Criar mutations parent.financeiro.pagarViaPix({ paymentId }) e gerarBoleto({ paymentId })
  • Integrar gateway (decisão cliente pendente — provavelmente Asaas/PagHiper)
  • TuitionCard já aponta para /pais/financeiro

Para Card 11 (Chat com Escola)

  • Implementar push real: gerar VAPID keys, criar PushSubscription table, 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.dashboard ou criar procedures dedicadas
← Voltar para a visão geral