Visão Geral do App
O frontend do Neemias é uma PWA (Progressive Web App) construída com foco em offline-first e resiliência de rede. A aplicação funciona plenamente mesmo sem conexão com o backend, sincronizando automaticamente ao reconectar.
Stack Tecnológica
| Camada | Tecnologia |
|---|---|
| Framework | React 19 + TypeScript |
| Build | Vite 6 |
| Estilização | Tailwind CSS v4 + shadcn/ui + MUI 7 |
| Estado / Persistência | Dexie.js (IndexedDB) via useLiveQuery |
| Roteamento | React Router v7 com PermissionGuard por rota |
| Autenticação | window.crypto.subtle (SHA-256 client-side), sessão em IndexedDB com TTL de 24h |
| PWA | vite-plugin-pwa, tema #01696f |
| i18n | Português brasileiro fixo — objeto pt_BR em app/src/app/utils/i18n.ts |
Gerenciamento de Estado
Sem Redux ou Zustand. O estado é derivado diretamente do IndexedDB via Dexie.js com o hook useLiveQuery. Cada componente que precisa de dados reativos assina consultas ao banco local, que é a fonte da verdade.
Caminho de Escrita (Write Path)
Toda mutação no sistema segue o padrão Event Sourcing:
- EventWriter — singleton que gerencia gravação de eventos (
writeAttendance(),writeStudent()) - Cada evento é gravado na tabela local + enfileirado na
syncQueueem uma única transação Dexie (atômica) - A interface reage ao evento — o estado atual é uma projeção dos eventos persistidos
typescript
// Factory para CRUD genérico com role guard e soft-delete
createCrudService<T>(config) → { list, create, update, remove, ... }Entidades Principais
Student (app/src/db/types.ts)
studentId,displayName,photoRef,status(ACTIVE|DELETED)guardianName,guardianNameAlt?,birthDate?,phones: PhoneEntry[],address: AddressclassId?→ FK paraClassnucleusParticipates,nucleusRegion?,nucleusName?allergies?,specialNeeds?,familyMembershipStatus
Class (Turma)
classId,name,ageMin?,ageMax?,status(ACTIVE|DELETED)- Seed: Berçário, Maternal Infantil, Jardim de Infância, Infantil, Juniores 1/2, Pré-Adolescentes
ClassSlot / ClassSession (Aula)
ClassSlot: dia da semana + horário (dayOfWeek,startTime,label) — recorrente semanal ou avulsaClassSession: ocorrência concreta (sessionDate), vinculada a umClassSlot
AttendanceEvent
eventId,studentId,classSessionId,actionType(MARK_PRESENT|MARK_ABSENT)actorId,actorRole,timestamp,syncState,isConflictLoser,conflictSupersededBy?
LocalUser
userId,username,displayName,passwordHash?,role,status- Roles:
ADMIN,CHAMADOR,RELATORIOS,CADASTRO
Regras de Negócio
- RBAC — 4 papéis com permissões explícitas.
CHAMADORsó marca presença;CADASTROgerencia alunos/aulas;RELATORIOSsó vê relatórios;ADMINacesso total. - Soft delete — alunos viram
status: "DELETED"com justificativa obrigatória (10-500 chars). Histórico de presença preservado. - Event sourcing — toda mutação passa pelo EventWriter com transação Dexie atômica.
- Offline-first — IndexedDB é fonte da verdade local. Sync automático com backoff exponencial.
- Sessão expira em 24h — modal força reautenticação.
AttendancePagebloqueia marcação com sessão expirada. - Criptografia em repouso — dados sensíveis (
changePayload,justification) criptografados com AES via hash da senha. - Conflitos de sincronia — perdedor marcado com
isConflictLoser: true+ ponteiroconflictSupersededBy.
Arquivos-Chave
| Arquivo | Função |
|---|---|
app/src/db/db.ts | Schema Dexie + migrações (v1→v6) |
app/src/db/types.ts | Definições de tipos de todas as entidades |
app/src/modules/eventWriter.ts | EventWriter — writeAttendance(), writeStudent() |
app/src/modules/shared/createCrudService.ts | Factory de CRUD com role guard |
app/src/modules/sync/registry.ts | Registro de providers de sincronia com fallback |
app/src/modules/attendance/ | Serviço + UI de chamada |
app/src/modules/students/ | Serviço CRUD de alunos |
app/src/modules/classes/ | Serviço CRUD de turmas |
app/src/modules/sync/ | Sincronia com backend + resolução de conflitos |
app/src/app/context/AuthContext.tsx | Estado de autenticação, roles, sessão |
app/src/app/context/OfflineContext.tsx | Dreno automático da fila de sync |
app/src/app/utils/i18n.ts | Traduções pt-BR (~324 linhas) |
app/vite.config.ts | Configuração Vite + Vitest |
Fonte: app/CONTEXT.md