TLDR: páginas e componentes não têm lógica; toda lógica vive em hooks; todo dado passa por services/ — a camada que amanhã troca de mock para HTTP real sem tocar em mais nada.
Contexto
O app nasceu como port do protótipo dc-runtime (Sistema de Cobrança.dc.html), um único componente
com um único state compartilhado pelas 14 telas. Portar isso para React exigiu decidir duas coisas
antes de escrever a primeira tela: onde a lógica passaria a morar, e como o front sobreviveria à
chegada do back-end real — que na época sequer tinha stack definida.
Decisão
Camadas
```
pages/ → uma página por rota, monta hooks + components, sem lógica própria
components/ → apenas apresentação (props in, JSX out)
layout/ → AppShell, Sidebar, NavItem, PageHeader, PageLayout — genéricos, usados em toda página logada
ui/ → PillGroup, StatusChip, DataTable, Pagination, SearchInput, InfoHint,
InfoToggle, Button, StepList, Card, Bar — genéricos, reaproveitáveis
em qualquer domínio
/ → components/painel, components/lotes, components/pagamentos,
components/clientes, components/clienteDetalhe, components/negativacao,
components/contratos, components/juridico, components/bots,
components/colaboradores — compõem os componentes de ui/ para uma
tela específica (ex: KpiCard, LoteAtivoCard, PagamentosTable)
hooks/ → toda lógica de estado/efeito/derivação mora aqui
shared/ → usePagination, useToggleIndex, useAsyncData — genéricos, sem
conhecimento de domínio
auth/, nav/, painel/, lotes/, loteDetalhe/, negativacao/, contratos/, pagamentos/,
clientes/, clienteDetalhe/, juridico/, bots/, colaboradores/ → hooks por domínio
services/ → uma função por operação (ex: fetchPainelDados), hoje resolvendo
contra mocks/, amanhã contra a API real
mocks/ → dados estáticos determinísticos (equivalentes aos arrays hardcoded
do protótipo original: NOMES_LOTE, MESES, etc.)
types/ → tipos compartilhados por domínio
routing/ → guards de rota (RequireAuth, RequireRole) e o mapa de navegação
utils/ → funções puras sem estado (ex: formatBRL)
```
```mermaid
graph LR
P["pages/"] --> C["components/"]
P --> H["hooks/"]
H --> S["services/"]
S --> M["mocks/"]
S -.->|"futuro"| API["API real"]
C --> UI["components/ui + layout"]
style S fill:#1f2937,color:#fff
style API fill:#374151,color:#fff,stroke-dasharray: 4 4
```
### Camada de serviço isolada
Toda tela busca dados via uma função em `services/` (ex: `fetchPainelDados`, `fetchLotesAtivos`,
`fetchLoteDetalhe`). Hoje essas funções apenas devolvem dados de `mocks/`, mas já são `async` — quando
o back-end real existir, a integração é trocar a implementação dessas funções (por um `fetch`/client
HTTP real), sem tocar em hooks, componentes ou páginas.
O hook `hooks/shared/useAsyncData` centraliza o padrão "buscar ao montar, ignorar resposta se o
componente já desmontou", usado por praticamente todo hook que consome um `service`. Uma exceção é
`useImportarLote`: ele usa o fetch inicial apenas como *seed* de um estado que é mutado localmente
depois (ao importar um novo lote), então não se encaixa no formato "buscar e exibir" do `useAsyncData`.
### Componentes sem lógica
Toda lógica de estado, cálculo e efeito colateral vive em hooks customizados (`hooks//use*`).
Os componentes (`components/`, `pages/`) recebem dados já prontos via props e só decidem o que
renderizar.
### Roteamento e papéis
`routing/RequireAuth` e `routing/RequireRole` substituem o `tela`/`permitidas` do protótipo original
por rotas reais via `react-router`. `hooks/nav/useNav` deriva a navegação (itens, seleção, badges) a
partir do papel do usuário e da rota atual.
Telas ainda não portadas (fases futuras) usam `pages/ComingSoonPage` como placeholder, para que a
navegação completa já funcione ponta a ponta mesmo antes de todas as telas existirem.
### Quando promover um hook local para Context compartilhado
O protótipo original é um único componente com um único `state` — várias telas liam e escreviam nos
mesmos dados. Ao portar para hooks isolados por página, isso se perde por padrão (cada
`usePagamentos()`/`useContratos()` teria seu próprio fetch e sua própria cópia do estado). Sempre que
**duas ou mais páginas** precisam enxergar a mesma mutação em tempo real, promovemos o hook para um
Context compartilhado (padrão igual ao `SessionContext` da Fase 1):
- **`PagamentosContext`** (`hooks/pagamentos/PagamentosContext.tsx`) — a tela Pagamentos (duplicar
pagamento) e a aba Pagamentos do Detalhe do cliente (pagamento avulso) escrevem no mesmo array; uma
cobrança criada em um lugar precisa aparecer no outro imediatamente.
- **`FilaNegativacaoContext`** (`hooks/negativacao/FilaNegativacaoContext.tsx`), **`ContratosContext`**
(`hooks/contratos/ContratosContext.tsx`) e **`CasosJuridicosContext`**
(`hooks/juridico/CasosJuridicosContext.tsx`) — a tela em si E o badge da barra lateral
(`hooks/nav/useNavBadges`) precisam do mesmo estado ao vivo; sem o Context, o badge mostraria uma
contagem congelada no valor inicial, nunca refletindo o que o usuário fez na tela.
Todos os Context ficam montados em `AppLayout`, que envolve todas as rotas autenticadas — por isso
sobrevivem à navegação entre páginas.
Quando um hook é usado por **uma única página** e nada mais precisa da mesma instância ao vivo (ex:
`useLoteConfig`, `useClientes`), ele continua sendo um hook local comum — promover para Context sem
essa necessidade real seria complexidade desnecessária.
## Consequências
| Consequência | Efeito |
|---|---|
| Integração com o back-end é local | A "regra de ouro" das specs INT: trocar só `services/*.ts`; se um hook precisar mudar, a spec tem que justificar |
| Lógica testável sem renderizar | Cálculos do painel, paginação e regras de importação de lote são testados nos `*.test.ts` ao lado de cada hook |
| Componentes de `ui/`/`layout/` reaproveitáveis | Qualquer tela futura os compõe passando dados diferentes via props |
| Estado compartilhado exige decisão explícita | Promover a Context é a exceção justificada, não o padrão — o custo é lembrar de fazê-lo quando um badge depende do estado de uma tela |
O motor do simulador de Bots (`hooks/bots/criarEstadoInicial.ts` e `hooks/bots/processarMensagem.ts`)
é o exemplo mais completo do padrão "extrair lógica complexa como função pura, sem depender de React"
— `enviarMensagemTexto` e `selecionarOpcaoMenu` recebem e devolvem um `BotSimState` comum, sem
`useState` nem efeito colateral, o que permitiu testar toda a máquina de estados (validações,
tentativas, round-robin) sem montar nenhum componente. `useBotSimulador` é só a casca fina que liga
isso ao React (estado, scroll automático).
## Referências
- [Camadas do app Rails](backend_layers.md) — a contraparte no back-end
- [Quirks do protótipo dc-runtime](../learnings/prototype_dc_quirks.md)
- Specs do port: [Fase 1](../specs/20260720144029_react_port_phase1_foundation.md) ·
[Fase 2](../specs/20260720172958_react_port_phase2_clientes_pagamentos.md) ·
[Fase 3](../specs/20260721082709_react_port_phase3_juridico_bots_colaboradores.md)