Todos os dados exibidos nos screenshots (nomes, endereços, processos) são fictícios — ambiente de demonstração. As telas abaixo são do subsistema SFO (Obras), o mais maduro e em produção — os demais subsistemas ainda estão em levantamento/implementação.
Contexto
O SFN nasceu como um sistema único de fiscalização de obras para a Secretaria de Obras de uma prefeitura municipal, substituindo blocos de papel e planilhas por um pipeline auditável. Com a adesão de novas secretarias, o projeto foi reestruturado de aplicação única para portal multi-secretaria: login único (SSO) na raiz do domínio, cada secretaria com seu próprio subsistema, todos compartilhando a mesma infraestrutura offline-first, PWA e impressão térmica — mas com domínio de negócio e fluxo de trabalho próprios, porque cada secretaria fiscaliza de um jeito diferente.
A plataforma é PWA tablet-first: o fiscal cria o procedimento no local, anexa fotos da vistoria, imprime no Zebra portátil e entrega o documento físico ao autuado — tudo offline-capable, em qualquer subsistema.
Arquitetura do portal
Um domínio único da prefeitura roteia por path para stacks Docker independentes — cada subsistema com seu próprio client, server e (quando aplicável) banco, publicados sob o mesmo domínio via Nginx:
| Subsistema | Secretaria | Path | Status |
|---|---|---|---|
| SFN | Portal (SSO) | / | Produção — LDAP/AD + JWT, sem banco próprio |
| SFO | Infraestrutura e Obras | /obras/ | Produção — subsistema completo (cadeia abaixo) |
| SFAM | Meio Ambiente e Habitação | /meio-ambiente/ | Em levantamento — 2 equipes (Regularização Fundiária, Meio Ambiente) |
| SFAP | Fiscalização de Área Pública | /area-publica/ | Escopo definido — fluxo próprio (ver abaixo) |
| SFBEA | Bem-Estar Animal | — | Planejado — desmembrado do SFAM como subsistema próprio |
O portal SFN concentra a autenticação LDAP/AD e emite um JWT (segredo HS256 compartilhado por todos os subsistemas) — o usuário loga uma vez e, se tiver acesso a mais de um subsistema, escolhe qual abrir. Um registro central (SUBSISTEMAS_JSON) mapeia cada subsistema a seu grupo de AD, URL base e credencial de API, e alimenta tanto o seletor de subsistemas quanto o encaminhamento entre eles (abaixo).
Cadeia de procedimentos — subsistema SFO (Obras)
Cada item é registro independente — dados são copiados no momento da criação, alterações posteriores não afetam os demais. A navegação bidirecional mostra origem e gerados.
Solicitação → Notificação → Embargo → Multa
- Solicitações chegam de Ouvidoria, Protocolo, MP, Guarda Municipal, georreferenciamento ou app do cidadão (via API externa).
- Notificação é criada da solicitação (com banner verde/âmbar conforme imóvel está ou não no cadastro imobiliário municipal).
- Embargo é gerado a partir de notificação, herdando rastreabilidade.
- Multa segue fluxo de status
PEND_ENTREGA → AG_RECURSO → RECURSO_EXPIRADO → RECURSO_ACEITO / MULTA_FINALcom janela de recurso configurável (default 30 dias).
Extensibilidade sem duplicar regra de negócio
Cada novo subsistema parte de uma cópia da stack do SFO, mas a estratégia é reaproveitar a infraestrutura (autenticação/SSO, sync offline-first, sistema de impressão, PWA) e deixar o domínio de negócio livre para divergir — porque cada secretaria fiscaliza de um jeito realmente diferente. O caso mais claro é o SFAP (Área Pública), que diverge do SFO em pontos estruturais:
- Cadeia mais curta:
Solicitação → Notificação, sem Embargo nem Multa — essas etapas simplesmente não existem no domínio da equipe de área pública. - Handoff por e-mail, não por API: ao notificar, o SFAP gera um relatório e envia por e-mail para as equipes de Social, Guarda Municipal e Obras assumirem os próximos passos — diferente do encaminhamento via API entre SFO e SFAM (abaixo), porque essas equipes ainda não têm subsistema próprio no portal.
- Sem inscrição imobiliária: a Notificação não referencia o cadastro imobiliário municipal (fiscaliza área pública, não lote privado) — a localização é por endereço livre + coordenada GPS capturada no momento da criação.
- Tela nova sem equivalente no SFO: aba “Monitoramento”, para áreas fiscalizadas periodicamente (não a partir de uma solicitação pontual), com registro de visita e mapa de calor no dashboard.
O SFAM segue o caminho inverso: reaproveita o fluxo linear do SFO, mas com 2 equipes internas (Regularização Fundiária, Meio Ambiente) que devem, cada uma, receber legislação e workflow próprios conforme o levantamento avança.
Recursos principais
- Wizard de notificação em 4 passos: busca de imóvel, dados do imóvel, dados do proprietário, dados da notificação.
- Busca composta: “Marfim 111” filtra logradouro
Marfim+ número exato111. - Fotos de vistoria em Notificações, Embargos e Multas — captura via câmera, edição de descrição, navegação na visualização expandida.
- Impressão térmica Zebra ZQ220 (72mm) + templates A4 oficiais com assinatura dupla (fiscal + notificado).
- Auto-transições de status — AR “Recebido” em
PEND_ENTREGAmove paraAG_RECURSO; ao abrir detalhe com prazo expirado, vai paraRECURSO_EXPIRADO. - Importação do cadastro imobiliário — UI com parser CSV resiliente (cabeçalho com reordenação/renomeação) + upsert por inscrição.
- Valores em U.F.M — Unidade Fiscal Municipal, substituindo UFIR/R$ em todo o projeto.
Offline-first
O fiscal opera em campo sem rede — criação de procedimentos, fotos, assinatura e impressão funcionam offline, com sincronização automática ao reconectar:
- Fila de escritas em IndexedDB — criações, edições e uploads feitos offline são enfileirados e reproduzidos em ordem ao reconectar, com retries e deduplicação por ID gerado no cliente — reenvio nunca duplica registro.
- Numeração oficial em campo — cada fiscal reserva um bloco de números por tipo de procedimento; o documento criado offline já sai impresso com o número oficial
NNN/AAAA. No sync, o servidor valida a reserva em transação com lock; cancelamento devolve o número ao pool. - Login offline — após o primeiro login online, as credenciais passam a ser validadas localmente (hash bcrypt) com expiração; senha incorreta não cai no fallback.
- Leituras resilientes — listas em
NetworkFirst(online sempre busca dado fresco) com cache por entidade em IndexedDB; o detalhe carrega sob demanda os campos pesados (fotos, assinatura) e mescla no cache. - Reconciliação de uploads — na sincronização, fotos e assinaturas são roteadas ao registro correto mesmo quando a entidade foi criada offline; falha real de upload é exibida, nunca silenciada.
- Impressão em 2 vias — sem rede, a térmica imprime automaticamente via do fiscal + via do notificado.
- Status sempre visível — banner online/offline/sincronizando, contador de operações pendentes e painel de falhas de sincronização.
API de Integração Externa e encaminhamento entre subsistemas
API REST autenticada por chave permite que sistemas externos (georreferenciamento, ouvidoria, MP, app cidadão) criem solicitações sem login LDAP — e o mesmo contrato de API é reutilizado internamente para o encaminhamento entre subsistemas do portal.
- Criação idempotente via origem + ID externo (UNIQUE INDEX parcial) — reenvios são seguros por design e sinalizados na resposta.
- Consulta de status retorna a solicitação e os procedimentos gerados (notificação, embargo, multa).
- Validação Zod estrita: campos desconhecidos são rejeitados; dados imprevisíveis vão em campo JSONB de metadados.
- Encaminhamento SFO ↔ SFAM via portal: quando uma solicitação é da alçada de outra secretaria, o subsistema de origem chama o próprio server, que chama o portal SFN, que roteia para a API externa do subsistema de destino (
origem → sfo-server → sfn-server → destino) — o subsistema de origem nunca fala diretamente com o de destino, e o portal concentra o registro de credenciais/URLs de cada integração. - Falha de rede em qualquer trecho da cadeia não perde a solicitação: ela permanece no status anterior e o reenvio é seguro (idempotente).
Stack
| Camada | Tecnologia |
|---|---|
| Frontend | React 19, TypeScript 5.9, Vite 8, TailwindCSS 4.2 |
| Backend | Express 5, TypeScript, Node.js 22 |
| Banco | PostgreSQL 17 (migrations SQL sequenciais) |
| Auth | LDAP/AD + JWT compartilhado entre subsistemas (SSO) |
| Infra | Docker Compose, Nginx — 1 stack independente por subsistema, roteadas por path no mesmo domínio |
| Impressão | Zebra ZQ220 Plus (ZPL / jsPDF fallback) |
Desafios resolvidos
- SSO entre aplicações React independentes: subsistemas isolados (deploy, build e ciclo de release próprios) compartilham sessão via JWT com segredo comum e guard client-side que redireciona ao portal quando não há usuário válido em
localStorage. - Broker de encaminhamento idempotente: desenhar o encaminhamento entre subsistemas sem acoplamento direto (origem não conhece destino) e sem duplicar solicitação em caso de retry.
- Domínio de negócio divergente sobre a mesma infraestrutura: decidir, subsistema a subsistema, o que é infraestrutura reaproveitável (sync offline, impressão, PWA) e o que é regra de negócio que não deve ser forçada a se parecer com o SFO (caso do SFAP — ver “Extensibilidade” acima).
- Bug de fuso horário em datas locais (
parseDateParts,addDays) corrigido com split por-em vez denew Date(). - Idempotência da API externa com
UNIQUE INDEXparcial por origem + ID externo — protege reenvios. - Importação em massa do cadastro imobiliário legado em lotes de 1000 via API, reaproveitando o mesmo endpoint da UI.
- Cadeia de renotificações preservada quando multa é gerada a partir de embargo (herda
renotificacaoIds).