Pular para o conteúdo
⬇ Baixar .md

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:

  1. Serve as páginas (App Router — src/app/).
  2. Serve a API REST (src/app/api/).
  3. 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).
  4. Roda o job de escalonamento (node-cron, a cada ESCALONAMENTO_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 rodando prisma migrate — nunca pelo app em produção.
    • chamados_app: runtime da aplicação, privilégio reduzido — sem UPDATE/DELETE em EventoAuditoria desde a migração *_lockdown_auditoria.
  • Modelo de dados completo em prisma/schema.prisma; referência de campos em docs/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 em docs/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 app atrá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/health reporta 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.error para stdout/stderr do container — capturáveis por docker compose logs ou 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.