Pular para o conteúdo
⬇ Baixar .md

Decisões arquiteturais

Decisões arquiteturais

Registro das decisões tomadas na reconstrução deste sistema, incluindo os pontos em que a implementação se afasta deliberadamente da lista de tecnologias sugerida na especificação original (seção 6). Cada decisão inclui o motivo e o que reconsiderar se as premissas mudarem.

1. Next.js full-stack, não backend Node.js separado + frontend React Native

Decisão: um único app Next.js 16 (App Router) serve tanto a API quanto a interface web, acessada via navegador (responsiva, funciona bem em celular) em vez de um app nativo React Native.

Por quê: o sistema não precisa de recursos exclusivos de app nativo (câmera, Bluetooth, background tasks pesadas). Um único deploy, um único runtime, menos superfície de configuração — mais simples de operar para a TI do hospital, dado que o requisito real é "acessível via celular", não "app instalável". A perda é notificação push mais limitada no iOS (ver decisão 5) e nenhuma distribuição via loja de apps.

Reconsiderar se: o hospital exigir presença nas lojas de app, uso offline robusto, ou integração com hardware do celular (câmera para outra finalidade, Bluetooth direto) que só um app nativo resolve bem.

2. PostgreSQL único, não PostgreSQL + MongoDB

Decisão: um único banco PostgreSQL. A tabela EventoAuditoria (com coluna payload JSONB para dados semi-estruturados) cobre o papel de "log de eventos em tempo real" que a especificação original cogitava para o MongoDB.

Por quê: volume de chamados de um hospital (mesmo grande) é ordens de grandeza menor do que os cenários em que um segundo banco NoSQL para "logs/eventos" se paga em complexidade operacional. Dois bancos = duas estratégias de backup, duas superfícies de falha, duas tecnologias para a equipe de TI manter. PostgreSQL sozinho, com os índices em Chamado (criadoEm, [tipo, status, criadoEm]) e em EventoAuditoria (criadoEm), atende as consultas de relatório sem problema nesse volume.

Reconsiderar se: o volume de eventos crescer por ordens de grandeza (múltiplos hospitais, telemetria de alta frequência de muitos dispositivos IoT) a ponto de as consultas de relatório ficarem lentas — aí uma tabela de agregados diários materializada resolveria antes de precisar de um segundo banco.

3. Socket.IO (WebSocket), não MQTT

Decisão: tempo real entre servidor e dashboards/dispositivos usa Socket.IO sobre WebSocket (com fallback automático), não um broker MQTT dedicado.

Por quê: os "clientes" deste sistema são navegadores (dashboards) e um único endpoint HTTP para o botão de emergência (POST /api/chamados/emergencia) — não uma frota de sensores IoT publicando em alta frequência, que é o caso de uso onde MQTT brilha (pub/sub leve, QoS configurável, ideal para redes instáveis de muitos dispositivos batery-powered). Socket.IO integra nativamente com autenticação por sessão (JWT do Auth.js) e não exige operar um broker adicional.

Reconsiderar se: o número de dispositivos IoT crescer para dezenas por quarto/hospital com necessidade de QoS/offline queuing do lado do broker — nesse ponto, migrar só a ingestão dos dispositivos (não os dashboards) para MQTT seria razoável, mantendo Socket.IO para a UI.

4. Sem Grafana/Metabase — relatórios in-app

Decisão: a tela /dashboard/supervisor/relatorios (números por setor, gráfico, tabela de pendentes) é construída no próprio Next.js com Recharts, consultando src/lib/relatorios/queries.ts diretamente — não há Grafana/Metabase implantado.

Por quê: os requisitos da especificação (chamados por setor, tempo médio de resposta, pendentes, SLA) são atendidos por algumas queries bem definidas; implantar e manter uma ferramenta de BI adicional (com seu próprio banco de conexão, autenticação, deploy) não se justifica só para isso.

Reconsiderar se: o hospital quiser dashboards ad-hoc para usuários não-técnicos montarem seus próprios relatórios, ou cruzar dados deste sistema com outros sistemas hospitalares — aí Metabase (mais simples de operar que Grafana para esse perfil de usuário) conectado ao mesmo Postgres seria o próximo passo natural, sem precisar mudar o schema.

5. Web Push, não notificação push nativa

Decisão: notificações push usam Web Push padrão (VAPID) via service worker (public/sw.js), consequência direta da decisão 1 (sem app nativo).

Por quê: é a opção que existe para apps web. Funciona nativamente em Android/desktop (Chrome, Edge, Firefox). No iOS Safari, só funciona se o usuário instalar o site como PWA ("Adicionar à Tela de Início") — uma limitação da Apple, não deste projeto, e algo a comunicar explicitamente para a equipe no treinamento (plano-de-treinamento.md).

Reconsiderar se: a experiência de push no iOS sem instalação de PWA for um requisito não-negociável — nesse caso, um app nativo (ou wrapper tipo Capacitor) seria necessário só para push, o que reabriria a decisão 1.

6. Sem Kubernetes — Docker Compose + proxy reverso

Decisão: deploy via Docker Compose (Postgres + app + Caddy para HTTPS automático), não Kubernetes.

Por quê: a escala real (chamados de um ou poucos hospitais da rede Unimed NF) não demanda orquestração de containers com autoscaling horizontal. Compose é suficiente para alta disponibilidade básica (restart automático de containers) e é muito mais simples de operar para uma equipe de TI hospitalar sem um time de plataforma dedicado.

Reconsiderar se: o sistema for adotado por múltiplas unidades simultaneamente com necessidade real de autoscaling, deploy multi-região, ou já existir um cluster Kubernetes na Unimed NF que a TI prefira reaproveitar — nesse caso, a containerização já feita (Dockerfile) facilita a migração; não seria um recomeço do zero.

7. Auth.js v5 (beta), não uma solução própria de sessão

Decisão: autenticação por usuário/senha via Auth.js v5 (next-auth@5.0.0-beta.32), sessão JWT.

Por quê: apesar de a versão 5 ainda estar em beta no momento desta reconstrução, é a integração padrão e mais testada para App Router do Next.js, com suporte a middleware de borda e route handlers. A alternativa de rolar autenticação própria (JWT manual + cookies) tem mais superfície de erro em um sistema hospitalar do que depender de uma biblioteca com grande adoção, mesmo em beta prolongado.

Ação de acompanhamento: monitorar o lançamento de uma versão estável (GA) do Auth.js v5 e atualizar assim que disponível — não é uma decisão definitiva, é a melhor opção pragmática hoje. Rodar npm audit periodicamente enquanto isso (ver CI, .github/workflows/ci.yml).

8. WebRTC só com STUN, sem servidor TURN

Decisão: o áudio bidirecional paciente-posto (/chamar/[numeroQuarto] ↔ tela de atendimento da Enfermagem) usa WebRTC com um único servidor STUN público (stun:stun.l.google.com:19302) pra descoberta de IP — sem servidor TURN (retransmissão) próprio.

Por quê: STUN sozinho resolve a maioria das conexões dentro da mesma rede Wi-Fi do hospital (o cenário mais comum: paciente e posto de enfermagem na mesma rede local) sem nenhuma infraestrutura adicional pra manter. Um TURN é outro serviço rodando 24/7, com seu próprio custo de banda (todo o áudio passa por ele quando é necessário) — não se justifica sem antes confirmar que falhas de conexão direta são um problema real em produção.

O que quebra sem TURN: chamadas entre redes com NAT simétrico ou firewall corporativo agressivo (comum em redes hospitalares segmentadas/VLANs isoladas) podem não conseguir estabelecer conexão direta. Sinalização (quem liga pra quem) sempre funciona — é só a conexão de mídia P2P que pode falhar nesses casos.

Reconsiderar se: usuários relatarem chamadas que tocam mas não conectam — sintoma clássico de STUN insuficiente. Solução: subir um TURN próprio (ex.: coturn, mesmo docker-compose) ou usar um serviço gerenciado (Twilio STUN/TURN, Cloudflare Calls). É uma mudança só de configuração no array ICE_SERVERS (src/hooks/useChamadaAudioPaciente.ts e src/hooks/useChamadaAudioStaff.ts), não de arquitetura de sinalização.