Arquitetura
Arquitetura
Visão geral
┌────────────────┐ QR Code / HTTPS ┌──────────────────────────┐
│ Paciente │ ───────────────────────▶ │ │
│ (navegador) │ │ │
└────────────────┘ │ │
│ Next.js 16 (app único) │
┌────────────────┐ POST /api/.../ │ - App Router (páginas │
│ Botão IoT │ emergencia (HTTPS, │ + API routes) │
│ (ESP32, │──▶ deviceId+apiKey) ────▶│ - server.ts: HTTP + │
│ protótipo) │ │ Socket.IO custom │
└────────────────┘ │ - node-cron: │
│ escalonamento │
┌────────────────┐ HTTPS + WebSocket │ - web-push: VAPID │
│ Dashboards │◀─────────────────────────▶│ │
│ (equipes, │ └────────────┬─────────────┘
│ navegador) │ │ Prisma
└────────────────┘ ▼
┌──────────────────────────┐
│ PostgreSQL │
│ - dados operacionais │
│ - EventoAuditoria │
│ (append-only, │
│ REVOKE aplicado) │
└──────────────────────────┘
Tudo atrás de: Caddy (proxy reverso, HTTPS automático)
Ver docs/decisoes-arquiteturais.md para o porquê de cada escolha acima
(monólito Next.js, Postgres único, Socket.IO, sem Grafana/Kubernetes).
Componentes
Aplicação (Next.js 16, App Router)
Um único processo Node.js (server.ts) que:
- Serve as páginas (App Router —
src/app/). - Serve a API REST (
src/app/api/). - Roda um servidor Socket.IO no mesmo processo, no path
/socket.io, autenticado via o JWT de sessão do Auth.js (nunca confia em identidade enviada pelo cliente no handshake). - Roda o job de escalonamento (
node-cron, a cadaESCALONAMENTO_INTERVALO_SEGUNDOS, padrão 15s).
Camada de dados (PostgreSQL + Prisma)
- Duas roles de banco distintas (ver
deploy/postgres-init/01-roles.sql):chamados_migrate: dona do schema, usada só por humanos/CI rodandoprisma migrate— nunca pelo app em produção.chamados_app: runtime da aplicação, privilégio reduzido — semUPDATE/DELETEemEventoAuditoriadesde a migração*_lockdown_auditoria.
- Modelo de dados completo em
prisma/schema.prisma; referência de campos emdocs/documentacao-tecnica.md.
Tempo real (Socket.IO)
Salas por posto (qualquer um dos 3 setores — Enfermagem, Copa,
Rouparia), mais uma sala "sem posto" por setor, e uma sala SUPERVISOR
(src/lib/realtime/events.ts#salaPosto/salaSemPosto). EMERGENCIA é
roteado pra sala do posto de Enfermagem do quarto. Cada login de
Enfermagem/Copa/Rouparia entra na sala do próprio posto (+ a "sem
posto" do próprio setor); SUPERVISOR entra nas salas dos postos
escolhidos em "Meus setores"; ADMINISTRADOR entra só em SUPERVISOR
(acesso irrestrito, todo evento chega lá também). Eventos:
chamado:novo, chamado:atualizado, chamado:emergencia,
chamado:escalado, chamado:escalado_critico,
chamado:finalizacao_escalada, chamado:desescalado,
chamado:cancelado.
Notificações
- Visual/sonora: sempre ativa nos dashboards (
useAlarmeSonoro, gera o beep via Web Audio API — sem depender de arquivo de áudio). - Push (Web Push/VAPID): opt-in por usuário (
NotificacaoPrompt), via service worker (public/sw.js). Ver limitação de iOS Safari emdocs/decisoes-arquiteturais.md.
Dispositivos IoT
Autenticação por deviceId + apiKey (chave com hash bcrypt no banco),
não por sessão de usuário — o chamador é um gateway de hardware. Ver
firmware/esp32-botao-emergencia/ para o firmware de referência.
Disponibilidade e failover — desenhado, não provisionado nesta fase
O deploy padrão (docker-compose.yml) é uma única VM: um ponto único
de falha. O desenho abaixo é o caminho para alta disponibilidade real,
mas não foi provisionado como parte desta reconstrução (fora do escopo de
um MVP/primeira reconstrução — ver docs/decisoes-arquiteturais.md sobre
Kubernetes):
- App: múltiplas réplicas do container
appatrás do Caddy (load balancing), todas apontando para o mesmo Postgres. O app é stateless exceto pelas conexões Socket.IO — com múltiplas réplicas, seria necessário um adaptador Socket.IO compartilhado (ex.: adaptador Redis) para eventos chegarem a clientes conectados em réplicas diferentes. Não implementado ainda. - Banco: Postgres com replicação streaming (um primário + réplica(s) de leitura/standby), com failover automático (ex.: Patroni) ou manual documentado.
- Proxy: Caddy já suporta múltiplos upstreams nativamente; o gargalo de HA real é o Socket.IO multi-réplica acima.
Monitoramento
GET /api/healthreporta conectividade com o banco e se o Socket.IO está de pé — usável por qualquer monitor externo (ex.: Uptime Kuma, healthcheck do orquestrador).- Logs:
console.log/console.errorpara stdout/stderr do container — capturáveis pordocker compose logsou qualquer coletor de logs do orquestrador (ex.: Loki, CloudWatch, journald). Não há um agregador de logs implantado nesta reconstrução. - Métricas de negócio:
/dashboard/supervisor/relatorios(não é monitoramento de infraestrutura, é indicador operacional).
Escalabilidade
No volume esperado (chamados de um hospital), o gargalo mais provável é
o job de escalonamento fazendo um full scan de chamados abertos a cada
tick — aceitável na escala atual (índice em [status, tipo] mantém isso
rápido mesmo com milhares de chamados históricos, já que o filtro é só
sobre os ABERTO/EM_ATENDIMENTO, tipicamente uma fração pequena do
total). Se o volume crescer por ordem de grandeza, considerar mover essa
verificação para triggers/notificações do próprio Postgres em vez de
polling.