Todos os dados exibidos nos screenshots (nomes, endereços, processos) são fictícios — ambiente de demonstração. As telas são do subsistema SFO (Obras), o mais maduro e em produção — exceto a última, do SFAP (Área Pública), em 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 implementação — 2 equipes (Regularização Fundiária, Meio Ambiente) |
| SFAP | Fiscalização de Área Pública | /area-publica/ | Em implementação — fluxo próprio (ver abaixo) |
| SFBEA | Bem-Estar Animal | /bem-estar-animal/ | Em implementação — 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, outras secretarias ou app do cidadão (via API externa). Na entrada passam por triagem: prioridade, prazo legal de atendimento calculado a partir da data de entrada e atribuição a um fiscal — a lista alterna entre “Todas” e “Minhas”, e registros sem responsável aparecem marcados como sem fiscal.
- 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).
Qualquer procedimento pode ser encerrado como regularizado — a saída “boa” da cadeia, que interrompe o escalonamento sem gerar embargo ou multa e alimenta o indicador de regularizações do dashboard.
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” (já funcional — última imagem da galeria), para áreas fiscalizadas periodicamente e não a partir de uma solicitação pontual: cadastro da área, mapa Leaflet com os polígonos monitorados e registro da última visita.
O SFAM segue o caminho inverso: reaproveita o fluxo linear do SFO, mas com 2 equipes internas (Regularização Fundiária, Meio Ambiente) que recebem, cada uma, legislação e workflow próprios. O SFBEA, desmembrado do SFAM, ganhou subsistema próprio ao ficar claro que o domínio de bem-estar animal não cabia como “mais uma equipe” dentro do Meio Ambiente.
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. - Endereço autuado ≠ endereço de entrega — o documento é entregue onde o proprietário mora, que muitas vezes não é o imóvel fiscalizado (nem a mesma cidade).
- Catálogo configurável de infrações e serviços — escolher a categoria da irregularidade preenche automaticamente descrição e fundamento legal, com fundamentos distintos para notificação e para multa; o serviço a executar é sugerido em chips e continua editável em texto livre.
- 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), com intensidade de impressão e gramatura dos caracteres ajustáveis para compensar o desgaste do cabeçote e do papel.
- 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; a multa é calculada por dias em atraso × valor diário da categoria e o cálculo fica registrado no log.
Dashboard e auditoria
- Dashboard analítico com filtro por período (7 dias, 30 dias, trimestre, ano ou tudo), abas por procedimento e cards com sparkline de evolução: notificações, embargos ativos, multas em aberto, solicitações, regularizados e total acumulado em U.F.M.
- Distribuições e prazos — donuts por status de notificação e de solicitação, painel de retorno AR dos Correios (recebido, endereço insuficiente, pendente) e fila de fiscalizações ordenada por prazo.
- Registros legados — a base importada do sistema anterior fica fora dos indicadores por padrão, com toggle “incluir legados” para relatórios que precisam do histórico completo; sem isso, todo gráfico de produtividade nasceria distorcido.
- Trilha de auditoria categorizada — cada evento é classificado (solicitação, notificação, embargo, multa, AR, status, importação, atribuição, numeração, configuração, foto, login) e filtrável por tipo, período e usuário, com impressão. A descrição guarda o dado relevante do evento, não só o “quem mexeu”: a linha da multa registra o cálculo que a gerou.
- Legislação vigente configurável — as leis e decretos aplicados pelo sistema ficam versionados nas configurações, com link para o texto oficial; quando o município publica um novo decreto de fiscalização, muda-se a configuração, não o código.
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).