📄 Documento de Execução — Card 11: [M2] Chat com Escola
| Campo |
Valor |
| Card Trello |
[11] [M2] Chat com Escola |
| URL Trello |
https://trello.com/c/furmJLCa |
| Marco |
M2 — Portal da Família (último card do M2) |
| Data execução |
03/08/2026 |
| Responsável |
Wellington Santiago (via ZCode) |
| Doc anterior |
Card 10: Financeiro - PIX & Boletos |
| Próximo card |
Card 12: Diário de Classe (M3) |
| Commit |
21d321d |
🎯 1. O que foi implementado
Chat bidirecional entre responsável e professores da escola. Diferente dos comunicados (one-way, Card 16/21), o chat é conversação 1:1 com histórico persistente. A página /pais/chat (antes mockada com respostas simuladas) agora renderiza dados reais via tRPC.
5 procedures tRPC em parent.chat
conversations — lista de conversas do responsável com última mensagem, timestamp relativo e contagem de não lidas. Resolve nome/avatar/role do outro participante.
messages({ conversationId, cursor?, limit? }) — histórico paginado (cursor-based, 30 por página). Valida participação antes de retornar.
send({ conversationId?, content, attachmentUrl?, recipientId? }) — mutation: envia mensagem. Se não houver conversationId, cria nova conversa com recipientId (ou reutiliza existente).
markRead({ conversationId }) — mutation: marca todas as mensagens não-minhas como lidas (set readAt).
contacts — lista de professores disponíveis para iniciar nova conversa (vinculados às turmas dos filhos do responsável).
Página /pais/chat reescrita
- Lista de conversas: avatar + nome + última msg + timestamp + badge não lidas
- Thread de mensagens: bolhas (saíntes accent / entrantes cinza), separadores de data (Hoje/Ontem/data), read receipts (CheckCheck lida / Check enviada), anexos como link
- Composer: input texto + anexo URL + botão enviar (disabled se vazio)
- Modal Nova Conversa: lista professores dos filhos (nome + disciplina + unidade)
- Polling: conversas a cada 5s, mensagens a cada 3s (quase-tempo-real sem Socket.io)
Estratégia: Polling em vez de Socket.io
Socket.io + redis-adapter já estão instalados, mas subir um server na porta 3001 adiciona complexidade de infra (processo separado, proxy Nginx, etc.). Para MVP, polling via tRPC (5s/3s) é suficiente e mais simples de manter. Quando precisar de tempo real instantâneo, plugar Socket.io sem mudar a interface.
Decisões do cliente aplicadas (6/7 respondidas)
| Decisão |
Aplicação |
| Pai conversa com professores dos filhos |
✅ contacts lista só professores das turmas dos filhos |
| Direção pode monitorar |
⏳ Card 21 (LOCAL acessa conversas da unidade) |
| Histórico permanente |
✅ sem expiração (schema não tem TTL) |
| Anexos: img, PDF, doc |
✅ campo attachmentUrl (S3 signed URL futuro) |
| Push para nova mensagem |
✅ infra pronta (Card 8), envio no Card 29/31 |
| Pai pode iniciar conversa |
✅ botão "Nova conversa" + modal de contatos |
| Bloquear fora horário comercial? |
❌ pendente — default: 24h sem bloqueio (alinhado com "Horário de envio: 24h") |
✅ Critérios de aceite validados
| # |
Critério |
Status |
| 1 |
Responsável abre conversa |
✅ |
| 2 |
Mensagens enviadas/recebidas |
✅ (polling 3s) |
| 3 |
Anexos via S3 |
⏳ (link URL, S3 signed URL futuro) |
| 4 |
Badge de não lidas atualizado |
✅ |
| 5 |
Histórico persiste e pagina |
✅ |
| 6 |
Secretaria vê mensagens (Card 21) |
⏳ |
| 7 |
Build passa + lint OK |
✅ |
Validação real (03/08/2026)
conversations: 1 conversa (Carla Mendes, 1 não lida)
messages: 5 mensagens (4 lidas, 1 não lida)
PROF: Olá! Tudo bem com a Maria? Ela está evoluindo muito... ✓ lida
EU: Olá, prof! Tudo sim, obrigado... ✓ lida
PROF: Que ótimo! Esta semana vamos começar perspectiva... ✓ lida
EU: Perfeito! Ela já falou sobre isso... ✓ lida
PROF: Aproveitando, lembrete: até sexta... ✗ não lida
📁 2. Arquivos criados/modificados
Criados
| Arquivo |
Função |
scripts/seed_card11_chat.js |
Seed: conversa responsável↔professor + 5 mensagens (1 não lida) |
Modificados
| Arquivo |
Mudança |
src/server/trpc/routers/parent.ts |
+sub-router chat (5 procedures) |
src/app/(dashboard)/pais/chat/page.tsx |
Reescrita: mock → dados reais (lista + thread + composer + modal nova conversa) |
messages/{pt-BR,en-US,es-ES}.json |
+seção parent.chat (16 chaves) |
🗄️ 3. Schema do banco (mudanças)
Nenhuma. Reutiliza ChatConversation (participantIds Json [2], lastMessageAt) + ChatMessage (content, attachmentUrl, readAt, senderId).
🔌 4. Handoff para o próximo card
API pública (tRPC)
parent.chat.conversations → { conversations: [{ id, otherName, otherAvatarUrl, otherRole, lastMessage, lastMessageAt, unreadCount }] }
parent.chat.messages({ conversationId, cursor?, limit? }) → { messages: [...], nextCursor }
parent.chat.send({ conversationId?, content, attachmentUrl?, recipientId? }) → { message, conversationId } (mutation)
parent.chat.markRead({ conversationId }) → { updated } (mutation)
parent.chat.contacts → { contacts: [{ userId, name, avatarUrl, subject, branchName }] }
Triggers para próximos cards
- Card 21 (Comunicados & Avisos da Unidade): LOCAL/MASTER acessa conversas da unidade (monitorar chat). Criar
local.chat.list com filtro por branch.
- Card 29 (Bot OpenAI): bot pode responder automaticamente no chat fora do horário.
- Socket.io upgrade: quando precisar de tempo real instantâneo, criar server na porta 3001 + Redis adapter. Events:
chat:message, chat:typing. Salas: chat:{conversationId}.
Lições aprendidas
- Polling first, Socket later: polling 3-5s via tRPC é suficiente para MVP e evita complexidade de infra. O usuário não percebe a diferença para a maioria dos casos de uso escolar.
- participantIds como Json array: o schema usa
Json para participantIds em vez de tabela de junção. Simples para 2 participantes, mas para grupo (3+) seria melhor tabela N:N.
📋 5. Deploy DEV
🎯 6. Próximo card
Card 12 [M3] Diário de Classe - Lançamento de Notas
- Spec:
docs/execucao/12-m3-diario-classe.md
- Foco: painel do PROFESSOR (M3 começa) — lançar notas por turma/disciplina
- Reuso:
Grade model, GradeTable componente, professorProcedure (RBAC)
- M2 completo (Cards 8-11)! 🎉