Plataforma para priorização de atendimentos na Atenção Primária à Saúde. O SinalACS transforma sinais clínicos estruturados em uma fila de trabalho para o Agente Comunitário de Saúde (ACS), priorizada por risco e preparada para operação em conectividade instável.
Estado atual: protótipo funcional, validado localmente em stack Docker (PostgreSQL, Mosquitto, backend Serverpod e Traefik) e em emulador Android. Não está pronto para produção: não há deploy de produção, iOS, nem integração com um provedor de identidade real. A lista completa do que falta, com responsáveis, está em PROGRESS.md.
App do paciente (apps/patient)
- Login sem senha: CPF + data de nascimento + código de uso único (OTP), e cadastro por convite em QR Code.
- Alerta de urgência real (
alerts.createRedAlert) com acompanhamento de entrega, triagem estruturada determinística e status da solicitação. - "Meus Dados" (LGPD): exportação, histórico de consentimentos, revogação e pedidos de correção e exclusão; aceite dos Termos de Uso.
- Lembretes locais e recebimento de avisos à comunidade por push (Gorush + FCM).
App do ACS (apps/acs)
- Login institucional com matrícula, senha (Argon2id) e TOTP; refresh token
rotativo guardado no Keystore e liberado por biometria (provado só no
emulador API 36; no API 30+ o PIN do aparelho também libera e uma digital
nova não invalida a chave); bloqueio após 30 s em
segundo plano e
FLAG_SECUREna janela. - Fila de visitas priorizada por risco, restrita à microárea do ACS (RBAC), com mapa, territorialização e geofencing.
- Registro de visitas offline em banco local cifrado (SQLCipher), com fila por dono, reenvio e detecção de conflito; recebimento de alertas vermelhos por MQTT com TLS e confirmação (ACK).
Backoffice (apps/admin, Flutter Web e Android)
- Login de coordenador/administrador com senha e TOTP obrigatório.
- Indicadores, microáreas, alertas (com escalonamento do alerta vermelho não confirmado), auditoria com selo por linha, verificação da cadeia e exportação CSV, gestão de contas de ACS e de MFA, pedidos do titular (LGPD), avisos agendados e versões dos Termos.
Backend (backend/, Serverpod)
- Motor de triagem determinístico (verde, amarelo, vermelho), alerta vermelho idempotente por microárea publicado no broker, sincronização de visitas, colunas clínicas cifradas em repouso (AES-256-GCM) e trilha de auditoria encadeada.
Consulte PROGRESS.md para o status detalhado dos milestones e spec/PRD_system.md para requisitos e decisões técnicas. As telas estão documentadas em docs/README.md.
apps/
acs/ Aplicativo Flutter do Agente Comunitário de Saúde
patient/ Aplicativo Flutter do paciente
admin/ Backoffice administrativo (Flutter Web e Android)
backend/ Workspace Dart: servidor Serverpod (sinalacs_server) e cliente gerado (sinalacs_client)
infra/ Configuração local de infraestrutura (Mosquitto, Traefik, Gorush)
scripts/ dev/ (configuração e execução local), qa/ (validação e e2e), ci/
spec/ PRD, UX, privacidade e fluxos do produto
docs/ Telas, guias operacionais e planos de implementação
video/ Roteiro e material do vídeo de apresentação
- Flutter com Dart 3.12 ou mais recente para os três apps (o piso efetivo vem
de
backend/sinalacs_client, dependência dos apps); a CI usa Flutter 3.44.8. - Dart
^3.12.0para o backend (backend/pubspec.yaml). - Android SDK com API 36 e JDK 17 para gerar ou executar os apps no Android.
- Docker Engine com Docker Compose v2 para subir a stack local.
- Emulador Android ou aparelho físico, opcional para execução mobile.
As versões usadas pela CI estão em .github/workflows/ci.yml.
Antes do primeiro docker compose up, gere a configuração local:
./scripts/dev/bootstrap_env.shO script cria o .env com segredos aleatórios desta máquina —
POSTGRES_PASSWORD, TEST_DATABASE_PASSWORD, MQTT_BACKEND_PASSWORD,
MQTT_ACS_PASSWORD, JWT_SECRET, AUDIT_CHAIN_SECRET,
HEALTH_DATA_ENCRYPTION_KEY, CPF_HASH_PEPPER, DEV_ACS_PASSWORD e
DEV_ADMIN_PASSWORD — e gera backend/sinalacs_server/config/passwords.yaml
(gitignored; sem ele a suíte do Serverpod morre sem imprimir nada). Os dois
arquivos ficam com chmod 600 e nenhum entra no git. O script nunca
sobrescreve sem --force; rodado sem --force sobre um .env antigo, só
acrescenta DEV_ADMIN_PASSWORD, se ela faltar.
.env.example é a referência completa das variáveis. O
docker-compose.yml declara cada segredo como ${VAR:?...}: se faltar, o
Compose falha dizendo qual, em vez de subir com uma senha embutida no arquivo
versionado.
Trocar POSTGRES_PASSWORD ou HEALTH_DATA_ENCRYPTION_KEY com um pg_data/
já existente quebra o ambiente: a senha só vale na primeira inicialização do
volume e as colunas clínicas já cifradas ficam ilegíveis. Para adotar valores
novos: docker compose down && rm -rf pg_data/.
Configuração de servidor e banco vem de sinalacs_server/config/*.yaml e pode
ser sobrescrita por variáveis de ambiente (SERVERPOD_DATABASE_HOST e
companhia, SERVERPOD_APPLY_MIGRATIONS, SERVERPOD_REDIS_ENABLED,
SERVERPOD_INSIGHTS_SERVER_PORT). O MQTT tem variáveis próprias, lidas por
sinalacs_server/lib/src/config/app_config.dart, assim como JWT_SECRET,
APP_ENV e ENABLE_DEV_LOGIN (desligado por padrão). Fora de development, o
servidor recusa subir se JWT_SECRET estiver ausente, vazio ou igual ao
valor de desenvolvimento. Detalhes em backend/CLAUDE.md.
Na raiz do repositório, suba PostgreSQL, Mosquitto, backend e Traefik:
docker compose up --buildServiços expostos no ambiente local:
| Serviço | Endereço |
|---|---|
| Traefik | http://localhost |
| Dashboard Traefik (inseguro, somente desenvolvimento) | http://localhost:8081 |
| Backend | https://localhost/ (RPC atrás do Traefik; a 8080 em texto claro não é publicada) |
| PostgreSQL | localhost:5432 |
| Mosquitto MQTT (TLS) | localhost:8883 |
8883 é a única porta que o broker publica. A 1883 anônima e a WebSocket
9001 não são publicadas nem escutadas: o mosquitto.conf só declara
listener 8883 (medido: as duas recusam conexão no host e não aparecem em
/proc/net/tcp dentro do container). O docker compose ps mostra 1883/tcp na
linha do mosquitto porque a imagem a declara em EXPOSE, não porque exista algo
atendendo nela.
Para encerrar a stack:
docker compose downO Compose lê todas as credenciais do .env gerado por
scripts/dev/bootstrap_env.sh — cada máquina tem
as suas. Não reutilize credenciais de desenvolvimento nem habilite o dashboard
inseguro do Traefik em ambientes públicos.
cd apps/acs && flutter pub get && cd -
./scripts/dev/run_acs.shUse o script, não flutter run direto. A senha do broker é resolvida em tempo
de compilação e não tem valor padrão: ela é gerada por máquina pelo
bootstrap_env.sh. O script lê o .env, copia as duas CAs de desenvolvimento
(a do broker e a do RPC) para os assets e passa os cinco --dart-define por um
arquivo temporário (--dart-define-from-file,
apagado ao sair), para a senha não trafegar na linha de comando do flutter. Os
cinco são SINALACS_HOST, SINALACS_MQTT_HOST, SINALACS_MQTT_USER,
SINALACS_MQTT_PASSWORD e GOOGLE_MAPS_API_KEY. Um
flutter build apk sem o SINALACS_MQTT_PASSWORD — o único dos cinco sem
valor padrão — falha (a guarda vive em
apps/acs/android/app/build.gradle.kts) em vez de compilar em silêncio um APK
que nunca recebe alerta.
Para escolher o dispositivo, ou gerar o APK:
flutter devices
./scripts/dev/run_acs.sh -d <device-id>
./scripts/dev/run_acs.sh --buildO ACS de desenvolvimento entra com a matrícula ACS-001 e a senha de
DEV_ACS_PASSWORD (.env), gravada pelo seed
backend/sinalacs_server/bin/seed_acs_credentials.dart.
O paciente não usa MQTT: o default de SINALACS_HOST já serve no emulador.
cd apps/patient
flutter pub get
flutter run
# em aparelho físico, apontando para a máquina da stack:
flutter run --dart-define=SINALACS_HOST=https://<ip-da-máquina>/Em aparelho físico na LAN, o host precisa casar em dois lugares — não só no
certificado. O RPC_CERT_SAN_EXTRA (.env) acrescenta o IP ao SAN da folha do
Traefik, mas quem decide se a requisição chega ao backend é a regra do router:
Host(\10.0.2.2`) || Host(`localhost`) || Host(`sinalacs.localhost`) (docker-compose.yml). Um host coberto pelo SAN e **fora** da regra faz o TLS passar e recebe o 404 do Traefik — medido: com a folha que cobre 127.0.0.1, curl --cacert …/ca.crt https://127.0.0.1/health/check` devolve
404 page not found, enquanto https://localhost/ devolve 200. Então o IP
precisa entrar também na regra; no broker não existe router, e é por isso
que lá o SAN sozinho basta.
Antes do primeiro flutter run — ou sempre que um runtime/ da stack for
apagado —, copie as CAs de desenvolvimento para os assets, com a stack de pé:
./scripts/dev/sync_dev_ca.shSem essa cópia nada fica vermelho na hora de compilar: flutter build e
flutter test saem verdes e o APK vai sem certificado nenhum dentro, e o app só
se denuncia depois, no handshake do TLS. O único comando que reclama é o
flutter analyze, pelo diretório que o pubspec.yaml declara e não existe.
Com a stack de pé e as CAs copiadas (./scripts/dev/sync_dev_ca.sh):
cd apps/admin
flutter pub get
flutter run -d emulator-5554 # Android; SINALACS_HOST padrão: https://10.0.2.2/No navegador (flutter run -d chrome --dart-define=SINALACS_HOST=https://localhost/)
o TLS é validado pelo próprio navegador, que não confia na CA de
desenvolvimento do RPC; esse caminho não foi validado ponta a ponta.
O seed cria o administrador ADM-001, com a senha de DEV_ADMIN_PASSWORD
(.env), sem TOTP e sem código de ativação. Antes do primeiro login, emita
um código (válido por 24 h, mostrado uma única vez):
cd backend/sinalacs_server
SERVERPOD_DATABASE_PASSWORD="$(grep -E '^POSTGRES_PASSWORD=' ../../.env | cut -d= -f2-)" \
dart run bin/issue_staff_activation_code.dart ADM-001 --issued-by seu-nomeA ferramenta lê as variáveis SERVERPOD_DATABASE_* (padrões localhost:5432,
sinalacs_db, sinalacs_user); a senha não tem padrão e é a
POSTGRES_PASSWORD do .env. Troque seu-nome por quem emite. Veja
backend/CLAUDE.md. Na tela de login, a conta usa esse
código para ativar a verificação em duas etapas.
O app ACS foi validado com compileSdk e targetSdk 36. Para gerar o APK:
cd apps/acs && flutter clean && flutter pub get && cd -
./scripts/dev/run_acs.sh --buildO artefato é criado em:
apps/acs/build/app/outputs/flutter-apk/app-debug.apk
O mesmo procedimento pode ser aplicado ao app do paciente, substituindo
apps/acs por apps/patient.
O app ACS recusa gerar release assinada com a chave de debug: sem chave de
release configurada, flutter build apk --release falha. Para um teste local
consciente, -Psinalacs.allowDebugSigning=true libera a chave de debug — nunca
para distribuição. Os apps paciente e admin ainda assinam release com a
chave de debug. Nenhum dos três tem chave de release provisionada; veja
docs/publicacao-google-play.md.
Execute cada conjunto a partir do respectivo diretório:
cd backend && dart pub get && dart analyze
cd backend/sinalacs_server && dart test # unitários + integração
cd backend/sinalacs_server && dart test test/unit # só os herméticos, sem banco
cd apps/acs && flutter pub get && flutter analyze && flutter test
cd apps/patient && flutter pub get && flutter analyze && flutter test
cd apps/admin && flutter pub get && flutter analyze && flutter testOs testes de integração do backend usam o harness do Serverpod e exigem o
Postgres de teste em localhost:9090 (sinalacs_server/config/test.yaml). A
validação ponta a ponta contra a stack real (scripts em scripts/qa/,
integration_test/ no emulador) está descrita em
CONTRIBUTING.md e no README de cada app.
A CI (.github/workflows/ci.yml) roda em toda PR,
em pushes para main/develop e sob demanda, com nove jobs:
workflow-lint, serverpod-backend, backend-docker-build, patient-app,
acs-app, admin-app, coverage-report, android-e2e (o único que sobe um
emulador contra a stack) e admin-android-build. Todos menos android-e2e
são obrigatórios para o merge em main e develop.
Não há deploy de produção implementado neste momento. O arquivo docker-compose.yml é destinado ao desenvolvimento local; ele não oferece TLS público, gestão de segredos, persistência operacional, observabilidade, backup ou políticas de acesso compatíveis com produção.
Existe um caminho de piloto/demo em serviços free-tier para o backend, documentado em backend/DEPLOY.md. Esse caminho é propositalmente barato e simplificado para demonstração — ele não substitui nenhum dos requisitos de produção do PRD listados abaixo.
O caminho previsto no PRD para produção inclui:
- Provisionamento imutável com Pulumi.
- PostgreSQL, Mosquitto e Traefik com redes privadas, TLS 1.3 e segredos fora do repositório.
- ACLs MQTT, autenticação institucional e RBAC por microárea.
- Observabilidade com OpenTelemetry, Prometheus e Grafana.
- Revisão de LGPD, auditoria e política de retenção antes de qualquer piloto.
Os critérios completos estão em spec/PRD_system.md e o desenho de privacidade em spec/lgpd_design.md.
O projeto lida com dados de saúde. Não inclua dados reais de pacientes em testes, logs, capturas de tela ou configurações de desenvolvimento. A classificação de risco é determinística e não pode ser alterada manualmente, alertas vermelhos não são descartados em silêncio e o acesso do ACS fica restrito à própria microárea.
Autenticação, RBAC por microárea, TLS no RPC e no broker (com senha, ACL e certificado de cliente) estão implementados na stack local. Ainda faltam, entre outros: provisionamento de certificado de cliente por aparelho em produção, refresh token para o paciente, imposição de append-only da trilha de auditoria no banco e o deploy de produção — ver PROGRESS.md e a política de segurança.
Consulte LICENSE.