diff --git a/.cursor/rules/projects/nucleos-eleitorais.mdc b/.cursor/rules/projects/nucleos-eleitorais.mdc
index 62adc182..f2f334b0 100644
--- a/.cursor/rules/projects/nucleos-eleitorais.mdc
+++ b/.cursor/rules/projects/nucleos-eleitorais.mdc
@@ -7,6 +7,7 @@ alwaysApply: false
## Status
+- **E16 entregue em código (2026-07-25):** Dossiê do município (pré-agenda) — aba `?tab=dossie` no detalhe (`MunicipalityDossier.tsx`, server component em Suspense) compondo os loaders existentes via `municipalityDossierData.ts` (baseline TSE + rank A11, conta da cadeira E8, pledges, rede de lideranças com frescor `updatedAt`, agenda `actionPlan` `overrideAccess: false`, sinais C12, perfil IBGE A8 com caveat para zonas — reentrada do artefato órfão); caps 8 lideranças / 5 sinais / 3+2 planos com "ver tudo" por seção; novo campo staff-only `municipality.budgetNotes` ("Emendas aportadas", G11 manual-first) via migration `20260725_022155_add_municipality_budget_notes` (o `.json` da migration também sara a cadeia de snapshots pós-`campaign_goals`); form de estratégia + zod + view model estendidos; **print CSS** (chrome `print:hidden`, shells `print:h-auto print:overflow-visible`, tipografia A4 em `styles.css`) — dossiê imprime 1–2 páginas com capa autocontida ("Gerado em"). Int test `municipalityDossierData.int.spec.ts` (mock do `unstable_cache` do baseline). Gate verde (tsc/lint/knip/363 tests/build); Aikido bloqueado por sessão expirada (re-login navegador pendente). Plano: [`dossie-municipio.md`](../../../docs/plans/dossie-municipio.md).
- **B19 registrado (2026-07-24):** gerenciar assessores em `/campanha/assessores` (lista + novo + detalhe) — só Coordenador Geral e Candidato; criar/editar contas `advisor`, carteira de municípios, reenviar senha; alinha access update/phone do `candidate`. Sem migration. Plano: [`gerenciar-assessores.md`](../../../docs/plans/gerenciar-assessores.md).
- **E8 entregue em código (2026-07-24):** conta da cadeira — artefato TSE commitado estendido para v2 (`campoFederalVotesByYear`, `federalTallyByYear`, `majoritarian2022` presidente/governador T1 2022, via `pnpm build:election-aggregates`; teto de bytes revisado 400→700 KB) + curadoria `src/lib/campoParties.ts` (ano→siglas do campo, validada contra `electionPartySpectrum` por unit test); global `campaignGoals` (meta estadual 150 mil + margem, migration `20260724_180000_add_campaign_goals_global`, access staff-read/coordinator-write, **sem** hook de revalidação — `/campanha` é dinâmico com auth); `municipalityPotential.ts` (válidos projetados, teto do campo, captura, share intracampo, roll-off 2022-only, decomposição proporcional da meta + sanity por TI) e `goalCoverage.ts` (`meta = expectedVotes[cenário] ?? suggestedGoal`; `comprometido = Σ pledges`, nunca a expectativa da mesa — semântica travada contra o conflito da auditoria); UI Impeccable classe B: linha "Cobertura da meta" no dashboard e no overview da lista, nova coluna na lista de municípios (renomeando "Cobertura"→"Assessoria" para eliminar ambiguidade), card "Conta da cadeira" no detalhe (fixo em `central`, sem seletor de cenário). `/simplify` pós-entrega: removida a checagem de sanidade por TI do caminho quente por requisição (fica exportada/testada para o E12 consumir), lookup de potencial por município virou O(1) (`Map` em vez de `.find`), loaders quase-idênticos de votos majoritários/deputado federal unificados, tipo de tally duplicado unificado, formatadores de percentual/progresso deduplicados entre dashboard/overview/card. Desbloqueia **E9/E10/E12/E13** no roadmap. **Follow-up mesmo dia (feedback do usuário):** tooltips hover/foco nos 4 números de diagnóstico do card (`MunicipalityHoverTooltip`, sem ícone de interrogação); seed local passou a importar presidente/governador também para 2014/2018 (artefato/fórmulas continuam 2022-only); `/impeccable critique` do card (2×P1 touch dead-end + colisão de layout, 2×P2 idiomas de disclosure/cópia densa, 1×P3 progress bar) endereçado por completo — tooltip agora abre/fecha por toque com dismiss por toque fora, nunca cobre "Cobertura da meta", cópia reescrita em frase-líder + fórmula sem cross-reference, barra de progresso mais visível a 0%; registrado **E18** (documentação de conceitos de inteligência de campanha, plano futuro). Plano: [`conta-da-cadeira.md`](../../../docs/plans/conta-da-cadeira.md).
- **C12 Registro-fundação entregue em código (2026-07-24):** trajetória de `votePledge` por versions nativas com baseline; atualização municipal `sinal` tipada (explicações, fonte e triangulação); `actionPlan.origin`; demandas vinculadas ao plano com custo derivado e criação múltipla atômica no formulário; `allocationDecision` ex-ante imutável com snapshot pequeno. Migration consolidada `20260724_180000_add_campaign_foundation_records`; testes de integração e jornada E2E dedicados.
diff --git a/.impeccable/config.json b/.impeccable/config.json
index 8e40de01..ffa72c0d 100644
--- a/.impeccable/config.json
+++ b/.impeccable/config.json
@@ -20,6 +20,15 @@
],
"createdAt": "2026-07-23T22:40:00.000Z",
"reason": "Meta's documented noscript tracking pixel: a hidden 1x1 whose src is a templated facebook.com/tr URL — not a rendered image"
+ },
+ {
+ "rule": "design-system-color",
+ "value": "#c86112",
+ "files": [
+ "src/app/(frontend)/styles.css"
+ ],
+ "createdAt": "2026-07-25T02:48:38.641Z",
+ "reason": "Pre-existing public-site body brand gradient (live in production, predates the hook); flagged while an unrelated print block was added"
}
]
}
diff --git a/AGENTS.md b/AGENTS.md
index 617c20c6..eb75e31b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -65,6 +65,8 @@ Backlog consolidado (bloqueadores, site, campanha, white-label): [`docs/roadmap.
**Recently resolved (2026-07-24):** **E4R** Import único da planilha de projeção — `pnpm db:seed:projecao` (`scripts/import-projecao.mjs`) lê o xlsx canônico em `docs/sheets/`, faz match via `canonicalizeMunicipalityName` + `municipalityCatalog`, e **sempre sobrescreve** `municipality.expectedVotes` + `priority` (re-seed quando a mesa manda planilha nova); Salvador pulado; `--dry-run` + guard `ALLOW_REMOTE_DB`; parser puro em `src/lib/projectionSheetParse.ts` (Bom→`optimistic`, Regular→`central`, Mínimo→`pessimistic`). **Fase 2 (mesmo dia, decisão do produto — não é mais zero PII):** o seed também importa as colunas de estratégia, com PRIORITÁRIAS sobrepondo MAPA GERAL quando não-vazia — SITUAÇÃO→`politicalTrend` (QUEDA/MANTÉM/AUMENTO→desfavorável/neutra/favorável, com nota de proveniência), DOBRADINHAS→`stateDeputy`+`stateDeputies` (união; célula crua em `dobradinhaNotes`), LIDERANÇAS→`contact` name-only **sem telefone** + `leadership` (dedup estadual por slug do nome; `Contact.phone` virou opcional via migration `20260724_175500_contact_phone_optional`, UI continua exigindo), ASSESSOR RESPONSÁVEL→`campaignUser`+`advisors` (união; Edizio=coordinator, Solla=candidate — `candidate` agora é elegível como assessor em `eligibleCampaignStaffWhere`; novos usuários com email `@planilha.invalid` + senha aleatória, sem login até um admin trocar), ENCAMINHAMENTOS→`nextSteps`, OBSERVAÇÃO→`strengths`/`risks` (classificação curada por slug). Relações são união (nunca removem vínculos da UI); re-run idempotente; nomes de atores políticos entraram sem trilha de consentimento — regularizar com a assessoria jurídica. No mesmo dia, o grupo duplicado `municipality.voteGoals` ("Meta Bom/Regular/Mínimo") foi **removido** — collection, zod, view models, form/card e utilitário `voteGoals.ts` — via migration `20260724_133600_drop_municipality_vote_goals` (backfill metas→estimativas onde a estimativa estava vazia): `expectedVotes` (pessimista/média/otimista) é a única série por cenário do município. Plano: [`docs/plans/import-planilha-projecao.md`](docs/plans/import-planilha-projecao.md).
+**Recently resolved (2026-07-25):** **E16** Dossiê do município (pré-agenda, field request O6) — staff-only `dossie` tab on the municipality detail page (6th value in `municipalityDetailTabs`, same query-param pattern): `src/utilities/municipalityDossierData.ts` composes the existing loaders in one pass (TSE baseline + A11 rank via `municipalityElectionGeographyForSlug`, E8 goal account, pledges, leaderships sorted by a new `LeadershipRowViewModel.updatedAt` freshness field, updates feed, two scoped `actionPlan` reads with `overrideAccess: false`, and A8 demographics — the previously consumer-less `bahiaMunicipalityDemographics.ts` re-enters through the "Perfil" section, with an explicit whole-city caveat for Salvador zona municipalities). Section caps 8 lideranças / 5 sinais / 3 upcoming + 2 realizado plans, each with a "ver tudo" link to its tab. New staff-only `municipality.budgetNotes` textarea ("Emendas aportadas", G11 manual-first) via migration `20260725_022155_add_municipality_budget_notes` — its committed `.json` snapshot also heals the drizzle snapshot chain after the snapshot-less `campaign_goals` migration; strategy form/zod/action extended; the section renders only when filled. **Print view is CSS-only:** app chrome gets `print:hidden`, the `h-svh`/`overflow-hidden` shells get `print:h-auto print:overflow-visible` (otherwise only page 1 prints), and `styles.css` gains A4 print typography; the dossier cover is self-contained (name, TI, badges, "Gerado em") so the printed page keeps its identity. Int test mocks `loadMunicipalityElectoralBaseline` (its `unstable_cache` wrapper needs the Next runtime). Plano: [`docs/plans/dossie-municipio.md`](docs/plans/dossie-municipio.md).
+
**Recently resolved (2026-07-24):** **E8** Conta da cadeira (metas derivadas, potencial por município, cobertura) — committed TSE artifact extended to v2 (`pnpm build:election-aggregates` now writes `campoFederalVotesByYear`, `federalTallyByYear`, and 2022 `majoritarian2022` presidente/governador T1 per municipality, alongside the existing `votesByYear`/`validVotesByYear`; byte budget raised 400→700 KB in the unit test); `src/lib/campoParties.ts` (curated field-party siglas per election year, unit-tested against `electionPartySpectrum.ts`'s `esquerda` bucket — not a second party classifier); global `campaignGoals` (`src/globals/CampaignGoals.ts`, migration `20260724_180000_add_campaign_goals_global`, `stateGoal` default 150,000 + `margin`/`baseYear`/`note`, `read` = admin/staff, `update` = admin/`isCampaignUnrestricted`, **no** `afterChange` hook — `/campanha` is dynamic with auth so nothing caches it); pure derived utilities `src/utilities/municipalityPotential.ts` (projected valid votes, field ceiling, capture rate, intra-field share, 2022-only roll-off, proportional goal decomposition + TI sanity check) and `src/utilities/goalCoverage.ts` — **locked semantics:** `meta = expectedVotes[scenario] ?? suggestedGoal`, `comprometido = aggregate.effectiveByScenario[scenario]` (pledges only — the mesa's own expectation never substitutes for the pledge sum in the coverage denominator, unlike `resolveMunicipalityStaffVoteTotal`); orchestration in `src/utilities/municipalityGoalAccount.ts` (`cache()`-deduplicated across dashboard/list/detail). UI (Impeccable class B): "Cobertura da meta" row on the dashboard and the list overview `CampaignMetricStrip`, a new list column (renaming the old "Cobertura" column to "Assessoria" to remove ambiguity) via a shared `MunicipalityListGoalCoverageCell` (compact/default layouts), and a new "Conta da cadeira" card on the municipality detail page (fixed to the `central` scenario, replacing the "Votos estimados" block that used to live in `MunicipalityStrategyCard`). Unblocks roadmap items **E9/E10/E12/E13**. Plano: [`docs/plans/conta-da-cadeira.md`](docs/plans/conta-da-cadeira.md).
**Recently resolved (2026-07-23):** **A10** Cenários de estimativa (pessimista/média/otimista) — `votePledge.estimatedVotes` e `municipality.expectedVotes` viram groups (`pessimistic|central|optimistic`); migration `20260723_124200_add_vote_estimate_scenarios` (backfill scalar→`central`); agregação por cenário em `votePledgeData` com default `central`; mapa 2026 com `values2026ByScenario` + seletor local; lista/overview/dashboard com média + faixa secundária; liderança continua com um `declaredVotes` sem ver estimativas. Plano: [`docs/plans/cenarios-estimativa-votos.md`](docs/plans/cenarios-estimativa-votos.md). Desbloqueia **E8**.
diff --git a/docs/plans/dossie-municipio.md b/docs/plans/dossie-municipio.md
index 47a0a8db..ec3ad2be 100644
--- a/docs/plans/dossie-municipio.md
+++ b/docs/plans/dossie-municipio.md
@@ -1,10 +1,10 @@
# E16 — Dossiê do município (pré-agenda)
-Status: rascunho (O6 — pedido explícito da sessão de campo de 2026-07-23)
-Atualizado em: 2026-07-24
+Status: entregue em código (2026-07-25)
+Atualizado em: 2026-07-25 (as-built: aba `?tab=dossie` staff-only com `MunicipalityDossier.tsx` + `municipalityDossierData.ts` compondo os loaders existentes — baseline TSE/rank A11, conta da cadeira E8, rede com frescor `updatedAt`, agenda `actionPlan`, sinais, perfil A8 com caveat para zonas; caps 8/5/3+2 com "ver tudo"; `municipality.budgetNotes` via migration `20260725_022155_add_municipality_budget_notes` — o `.json` também sara a cadeia de snapshots pós-`campaign_goals`; print CSS: chrome `print:hidden`, shells `print:h-auto print:overflow-visible`, tipografia A4; int test com mock do `unstable_cache` do baseline. Revisão pré-implementação 2026-07-24: `budgetNotes` ganhou migration própria — o veículo C12 caducou; registrado o estado zero-consumidor de A8, resolvido pela seção "Perfil")
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Inteligência de campanha, E16; plano-mestre [inteligencia-campanha.md](inteligencia-campanha.md), gap G11)
Impeccable: B — visão nova dentro do detalhe do município existente (tab/rota `dossie` + print), sem sistema novo
-Appetite: ~1 dia eng; sem migration na v1 (compõe dados existentes; campo de emendas é fase G11 manual)
+Appetite: ~1 dia eng; uma migration pequena (`budgetNotes` — ver questão resolvida abaixo); o resto compõe dados existentes (fonte estruturada de emendas segue G11 adiado)
Responsável: —
## Design (Impeccable)
@@ -26,7 +26,7 @@ A sessão real de 2026-07-23 ([CUSTOMER.md](../CUSTOMER.md)) trouxe O6 como o ú
- **Tab/rota "Dossiê"** no detalhe do município (staff-only): seções na ordem de leitura de campo — capa (nome, TI, prioridade, nível quando existir), conta local (série 2014/2018/2022, % da própria votação via A11, captura/cobertura quando E8 existir), rede (lideranças ativas + responsável + frescor), conjuntura (`politicalTrend` + forças/riscos + disputa local), dobradinhas (M4 + notas), agenda/planos recentes e sinais recentes (últimos `municipalityUpdate`), perfil (demografia A8), emendas (quando houver dado — v1 mostra a seção só se preenchida).
- **Visão print:** `print:`-friendly (CSS print da mesma rota; sem lib de PDF) — 1–2 páginas A4, cabeçalho com data de geração ("dossiê de " — dado envelhece).
-- **Emendas manual-first (G11):** nota staff curta no `MunicipalityStrategyCard` existente se couber em campo atual (`nextSteps`/`dobradinhaNotes`) **ou** campo `budgetNotes` staff-only novo — decidir na implementação com custo de migration à vista (questão em aberto abaixo).
+- **Emendas manual-first (G11):** campo `budgetNotes` staff-only novo em `municipality` (migration pequena própria — decisão registrada na questão resolvida abaixo), editado no formulário de estratégia; a seção "Emendas aportadas" do dossiê só aparece quando preenchido.
- Access: tudo staff (coordinator/advisor/candidate); leader não acessa (lockdown); campos staff-only seguem a redaction dos view models.
## Decisões travadas
@@ -38,7 +38,7 @@ A sessão real de 2026-07-23 ([CUSTOMER.md](../CUSTOMER.md)) trouxe O6 como o ú
## Questões em aberto
-- **Onde mora o dado de emendas na v1?** Opções: reusar campo texto existente (`nextSteps`) com convenção | campo staff-only novo `budgetNotes` (migration pequena). **Recomendação:** campo novo `budgetNotes` na migration de C12 se E16 for aprovado antes dela rodar (mesma janela 2 — zero migration extra); senão, convenção em `nextSteps` até C12.
+- ~~**Onde mora o dado de emendas na v1?**~~ **Resolvida (2026-07-24):** campo staff-only novo `budgetNotes` em `municipality`, com migration própria pequena (`add_municipality_budget_notes`). A opção "carona na migration de C12" caducou — C12 foi entregue com `20260724_180000_add_campaign_foundation_records` antes de E16 começar. **Rejeitada:** convenção dentro de `nextSteps` (polui a semântica do campo e quebra a regra "seção Emendas só aparece se preenchida").
- **Dossiê mostra nível N0–N4 (E14)?** **Recomendação:** sim quando existir, staff-only — mas nunca no print destinado a circulação além da coordenação (vocabulário duplo). v1: print omite nível por default.
## Abordagem proposta
@@ -77,10 +77,25 @@ Componentes: `src/utilities/municipalityDossierData.ts` (novo — orquestra load
- **Economia local virar pesquisa.** v1 usa o que A8 tem (demografia); "economia" além disso é nota de conjuntura manual — não construir base socioeconômica nova.
- **Print pixel-perfect.** CSS print razoável basta; não é material de campanha, é documento de trabalho.
+## Já resolvido no simplify (não reabrir)
+
+- Dados mortos `votePosition2022` / `pledgeCoverage` removidos de `MunicipalityDossierData` (rank já vem do baseline card).
+- Agenda reusa `buildActionPlanListWhere` + `actionPlanListSelect` + `toActionPlanListViewModel` + `pagination: false`.
+- `loadFreshestMunicipalityLeaderships` (sort `-updatedAt` + limit no Postgres) em vez de fetch-all-and-slice.
+- Conta da cadeira encadeada no `Promise.all` (pledges → goal), concorrente com baseline/rede/sinais/agenda.
+- Datas/números via `formatBahiaDateTimeLabel` / `formatElectionNumber`; Prettier em `migrations/index.ts` e fontes novas.
+
+## Explicitamente fora (triage pós-simplify 2026-07-25)
+
+- Memoização em module-scope de `computeStatewideGoalDecomposition` — pré-existente E8, fora do diff do dossiê (revisitar no plano [conta-da-cadeira.md](conta-da-cadeira.md) se o hot path do dashboard doer).
+- Encolher o page size do feed de sinais de 10 → 5 — o loader compartilhado precisa de `totalDocs`; micro-otimização sem evidência.
+- Critique do `MunicipalityGoalAccountCard` (tooltips touch) — sessão E8, já hardenido no follow-up #3 daquele plano; não é dívida do E16.
+
## Adiado com gatilho
- **G11 fonte estruturada de emendas** (Câmara/Portal da Transparência → valor por município/ano). Gatilho: E16 em uso real + coordenação pedindo o dado sistemático.
- **Compartilhamento do dossiê via WhatsApp (kit).** Gatilho: pedido de campo; hoje o print resolve.
+- **Helper compartilhado da proveniência da meta** (`usesMesaEstimate` / rótulo "estimativa da mesa" vs "meta sugerida"). Hoje 2 call sites (`MunicipalityGoalAccountCard` + dossiê) abaixo do limiar de abstração do repo. Gatilho: 3º consumidor da mesma derivação/cópia.
## Referências
diff --git a/docs/roadmap.md b/docs/roadmap.md
index 43f6ae49..d6729b63 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -1,6 +1,6 @@
# Roadmap — Teqo
-Atualizado em: 2026-07-24 (B19 gerenciar assessores; **E8 ✓** conta da cadeira — desbloqueia E9/E10/E12/E13; **E18** documentação de conceitos registrado como adjacente ao programa; C12 ✓; C11 absorve escala plano↔demandas; B18; B17; B16; reorder DnD fora de escopo; A11 ✓; B15 ✓; E4R ✓; E17 ✓; B14; janela 1: smoke, R6)
+Atualizado em: 2026-07-25 (**E16 ✓** dossiê do município — aba + print + `budgetNotes` G11; anterior 2026-07-24: B19 gerenciar assessores; **E8 ✓** conta da cadeira — desbloqueia E9/E10/E12/E13; **E18** documentação de conceitos registrado como adjacente ao programa; C12 ✓; C11 absorve escala plano↔demandas; B18; B17; B16; reorder DnD fora de escopo; A11 ✓; B15 ✓; E4R ✓; E17 ✓; B14; janela 1: smoke, R6)
Registro canônico dos **próximos** planos e débitos. Histórico de entregas: resumo abaixo + planos em [`docs/plans/`](plans/) + notebook [`.cursor/rules/projects/nucleos-eleitorais.mdc`](../.cursor/rules/projects/nucleos-eleitorais.mdc).
## Âncoras do calendário eleitoral 2026 (Res. TSE 23.760/2026)
@@ -48,6 +48,7 @@ Textos provisórios de Consent + `/privacidade` auto-provisionados ([onda-0.md](
- **E17 (2026-07-24)** — tabela comparativa dos 27 Territórios de Identidade no Início staff (`/campanha`): `territoryOverview.ts` (rollup puro client-safe: `computeTerritoryRollup` + `sortTerritoryRows`) + `loadTerritoryOverview.ts` (loader server-only, `overrideAccess: true`) + `TerritoryOverviewTable.tsx` (tabela densa, ordenação client-side default `% da própria votação desc`, Metropolitano decomposto em Salvador 19 zonas × Demais RMS, linha→`/campanha/municipios?region=`). Somas/razões apenas (salvaguarda MAUP); sem migration/collection. Primeira fatia de E12. Plano: [tabela-ti-inicio.md](plans/tabela-ti-inicio.md).
- **C12 (2026-07-24)** — registro-fundação: versions nativas em `votePledge` com baseline; sinais tipados em `municipalityUpdate`; origem do `actionPlan`; demandas vinculadas com custo derivado e criação múltipla atômica; `allocationDecision` ex-ante imutável. Migration `20260724_180000_add_campaign_foundation_records`; testes int + E2E. Plano: [registro-fundacao.md](plans/registro-fundacao.md).
- **E8 (2026-07-24)** — conta da cadeira: artefato TSE commitado estendido para v2 (`campoFederalVotesByYear`, `federalTallyByYear`, `majoritarian2022` presidente/governador T1, via `pnpm build:election-aggregates`); curadoria `src/lib/campoParties.ts` (ano→siglas do campo, validada contra `electionPartySpectrum`); global `campaignGoals` (meta estadual 150 mil + margem, access staff-read/coordinator-write, migration `20260724_180000_add_campaign_goals_global`); `municipalityPotential.ts` (válidos projetados, teto do campo, captura, share intracampo, roll-off 2022-only, decomposição proporcional da meta + sanity por TI) e `goalCoverage.ts` (`meta = expectedVotes[cenário] ?? suggestedGoal`; `comprometido = Σ pledges`, nunca a expectativa da mesa); encaixado no dashboard, na visão geral e na lista de municípios (nova coluna "Cobertura da meta"), e num novo card "Conta da cadeira" no detalhe. Plano: [conta-da-cadeira.md](plans/conta-da-cadeira.md). Desbloqueia **E9**, **E10**, **E12**, **E13** (e melhora **B13**/**E16**).
+- **E16 (2026-07-25)** — dossiê do município (pré-agenda, pedido O6): aba `?tab=dossie` staff-only no detalhe compondo os loaders existentes (`municipalityDossierData.ts` + `MunicipalityDossier.tsx`) — capa com data de geração, série TSE/rank (A11), conta da cadeira (E8), rede de lideranças com frescor, conjuntura/dobradinhas/encaminhamentos, agenda (`actionPlan`), sinais recentes (C12), perfil IBGE (A8 — reentrada do artefato, caveat para zonas); caps 8/5/3+2 com "ver tudo"; campo staff-only `municipality.budgetNotes` ("Emendas aportadas", G11 manual-first, migration `20260725_022155`); **visão print** via CSS (chrome oculto, shells destravados, tipografia A4 — 1–2 páginas). Plano: [dossie-municipio.md](plans/dossie-municipio.md).
## Próximos — Campanha (`/campanha`)
@@ -58,7 +59,6 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re
| ID | Fatia | Plano | Entrega essencial | Classe | Appetite | Janela | Depende de |
| --- | ---------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------- | --------- | ------------------------------ |
| E9 | Fila de alocação | [detalhes](plans/fila-de-alocacao.md) | Lista de decisão (7 colunas + % da própria votação — absorve A11), ordenação por déficit descoberto (default do staff quando houver metas — registro no fluxo de poder) + risco por frescor, "coluna da vergonha" | B | ~1,5d | 2 | E8 ✓ |
-| E16 | Dossiê do município (pré-agenda) | [detalhes](plans/dossie-municipio.md) | Dossiê pré-visita compondo dados existentes (série/captura, pledges/cobertura, lideranças, conjuntura, forças/riscos, dobradinhas, sinais, perfil A8) + emendas quando houver dado (G11); visão print — pedido explícito de campo (O6) | B | ~1d | 2 | — (melhora com E8 ✓/C12) |
| E10 | Classificação territorial relativa | [detalhes](plans/classificacao-territorial-relativa.md) | Âncoras relativas (LQ / share da cadeira marginal), multi-eixo; substitui 35/20/10 para DF | B | ~1d | 3 | E8 ✓ |
| B13 | Escala relativa no mapa | [detalhes](plans/escala-relativa-mapa.md) | Quantis do candidato (default) + LQ + rank; símbolo proporcional por votos em jogo; % válidos mantido | B | ~2d | 3 | E8 ✓, E10 |
| E14 | Níveis de envolvimento N0–N4 | [detalhes](plans/niveis-de-envolvimento.md) | `engagementLevel` com histerese e sinais de reversão; staff-only (vocabulário duplo) | B | ~1d | 3 | C12 |
@@ -137,7 +137,7 @@ flowchart TD
E8i["E8 ✓ Conta da cadeira"]
E9i["E9 Fila de alocação"]
C12i["C12 ✓ Registro-fundação"]
- E16i["E16 Dossiê do município"]
+ E16i["E16 ✓ Dossiê do município"]
E10i["E10 Classificação relativa"]
B13i["B13 Escala relativa no mapa"]
E14i["E14 Níveis N0–N4"]
@@ -179,13 +179,13 @@ flowchart TD
B8F2 -.ZE Salvador.-> B14n
```
-Paralelizáveis agora: **E9** (E8 ✓ entregue), **E10**/**E12** (E8 ✓ desbloqueou ambos), **E16** (compõe o existente), **E18** (E8 ✓ já dá conteúdo para v1), **B14** (atalho geo no Início; soft B8 F2 só para ZE Salvador), **B16** (filtros no header; soft B15 ✓ já entregue), **B17** (seletor de colunas; soft B15/B16 — pousa na barra atual ou slim), **B18** (filtros salvos + submenu Municípios; soft B16/B17 — botão na barra), **B19** (gerenciar assessores — onboarding sem `/admin`; sem deps duras), fill-ins (ícone de prioridade na lista, Cenário junto aos filtros — encaixa na barra slim de B16 —, O0+, RS+). **D3** só após smoke + folga e só se o fluxo sede-digita (C12 ✓) não absorver os deltas do ZAP (não compete com E9).
+Paralelizáveis agora: **E9** (E8 ✓ entregue), **E10**/**E12** (E8 ✓ desbloqueou ambos), **E18** (E8 ✓ já dá conteúdo para v1), **B14** (atalho geo no Início; soft B8 F2 só para ZE Salvador), **B16** (filtros no header; soft B15 ✓ já entregue), **B17** (seletor de colunas; soft B15/B16 — pousa na barra atual ou slim), **B18** (filtros salvos + submenu Municípios; soft B16/B17 — botão na barra), **B19** (gerenciar assessores — onboarding sem `/admin`; sem deps duras), fill-ins (ícone de prioridade na lista, Cenário junto aos filtros — encaixa na barra slim de B16 —, O0+, RS+). **D3** só após smoke + folga e só se o fluxo sede-digita (C12 ✓) não absorver os deltas do ZAP (não compete com E9).
### Sequência por janela (só pendentes)
**Janela 1 — agora → 05/08 (convenções):** smoke pós-deploy em produção + onboarding do time (Onda 0 §2/§4); **E4R ✓** em código (aplicar `pnpm db:seed:projecao` em produção após smoke); **E8 ✓** conta da cadeira entregue; **E17 ✓** tabela TI no Início; **B15 ✓** (sort por coluna) e **A11 ✓** (posição em votos + default `sort=votos`) entregues 2026-07-24; **E18** documentação de conceitos (`/campanha/conceitos`, v1 só E8) se sobrar folga — pedido do usuário na mesma sessão do E8; **B19** gerenciar assessores (caminho do onboarding sem `/admin` — criar/ativar contas e carteiras); **B16** (filtros no header) / **B17** (seletor de colunas) / **B18** (filtros salvos + atalho sidebar) se folga de UX da lista no onboarding; gate de adoção: sinal Little Hire — ≥1 update espontâneo até 30/07, cobrança da tabela dispensada (planilhas já em `docs/sheets/`) — acompanhamento em [IMPROVE-APP-PLAN.md](IMPROVE-APP-PLAN.md); **R6** critique/polish; **E9** pode começar em paralelo; **B14** (atalho geo) se sobrar folga de campo no onboarding; Onda 0 jurídica em paralelo (externa).
-**Janela 2 — 05/08 → 16/08 (pré-propaganda):** C2 dados reais assim que o jurídico liberar; **E9** fila de alocação (E8 ✓ e **C12 ✓** já entregues); **E16** dossiê do município (pedido O6 — compõe o existente, melhora com E8 ✓/C12 ✓); D2 se sobrar folga.
+**Janela 2 — 05/08 → 16/08 (pré-propaganda):** C2 dados reais assim que o jurídico liberar; **E9** fila de alocação (E8 ✓ e **C12 ✓** já entregues); ~~**E16** dossiê do município~~ (entregue 2026-07-25, antecipado da janela 2); D2 se sobrar folga.
**Janela 3 — 16/08 → set:** A6 dobradinha (pós-TSE 15/08); **E10** classificação relativa → **B13** escala relativa no mapa; **E14** níveis N0–N4; **E11** motor de sugestões v1; **E12** camada TI; **E13** planejador de giros; **B8 F2** polígonos das zonas de Salvador; débitos sobreviventes. Se houver folga **e** os deltas do ZAP não estiverem sendo absorvidos pelo fluxo sede-digita (C12): começar **D3**.
@@ -195,7 +195,7 @@ Paralelizáveis agora: **E9** (E8 ✓ entregue), **E10**/**E12** (E8 ✓ desbloq
**Não cortáveis:** Onda 0 (jurídico/Consent); ~~**E4R** seed da planilha~~ (entregue 2026-07-24 — ainda aplicar em produção após smoke); ~~**E8** conta da cadeira~~ (entregue 2026-07-24); ~~**C12** registro-fundação~~ (entregue 2026-07-24); C2 dados reais; assimetria declarado×estimado (relação de campo); **E9** (a fila de alocação é o mínimo de "inteligência, não planilha"); **B19** gerenciar assessores (onboarding Onda 0 §4 — CG/candidato não entram em `/admin`; sem esta UI o time só nasce via seed ou admin Payload).
-**Cortes seguros** (se o prazo apertar, nesta ordem): **D5** inbox→rascunhos (manter registro manual + `wa.me`); **D4** envio bridge (manter `wa.me`); **D3** fundação do canal (atalho `wa.me` continua); **E12** camada TI (rollup manual por lista; **E17** já dá a leitura regional básica no Início); **E13** planejador de giros (rebaixado na fila de corte em 2026-07-24: "perna"/agenda é a restrição dominante nomeada em campo — cortar só depois de E12; agenda segue manual com J-A/J-B como guia); **E15** backtest (pós-eleição por definição — cortar = perder o aprendizado 2030); **E11** motor v1 (manter fila E9 sem sugestões); **B13** símbolo proporcional (manter quantis/LQ como escala); **E14** níveis (manter `priority` alta/normal); **B8 F2** polígonos (mapa continua agregado no município; manter F1 bairros); **D2** push (adiar); **A6**; **B14** município mais próximo (lista/busca e Recentes continuam); **B18** filtros salvos (lista + filtros manuais continuam; Visitados cobrem “voltar”); **B17** seletor de colunas (tabela completa continua; scroll horizontal); **B16** filtros no header (barra de filtros atual continua a funcionar); ~~**B15** sort por coluna~~ (entregue 2026-07-24); ~~**A11**/**E17**~~ (entregues 2026-07-24); **E18** documentação de conceitos (os tooltips inline do E8 já explicam o essencial; página é reforço); **E16** dossiê (último dos extras — pedido explícito de campo); débitos/fill-ins.
+**Cortes seguros** (se o prazo apertar, nesta ordem): **D5** inbox→rascunhos (manter registro manual + `wa.me`); **D4** envio bridge (manter `wa.me`); **D3** fundação do canal (atalho `wa.me` continua); **E12** camada TI (rollup manual por lista; **E17** já dá a leitura regional básica no Início); **E13** planejador de giros (rebaixado na fila de corte em 2026-07-24: "perna"/agenda é a restrição dominante nomeada em campo — cortar só depois de E12; agenda segue manual com J-A/J-B como guia); **E15** backtest (pós-eleição por definição — cortar = perder o aprendizado 2030); **E11** motor v1 (manter fila E9 sem sugestões); **B13** símbolo proporcional (manter quantis/LQ como escala); **E14** níveis (manter `priority` alta/normal); **B8 F2** polígonos (mapa continua agregado no município; manter F1 bairros); **D2** push (adiar); **A6**; **B14** município mais próximo (lista/busca e Recentes continuam); **B18** filtros salvos (lista + filtros manuais continuam; Visitados cobrem “voltar”); **B17** seletor de colunas (tabela completa continua; scroll horizontal); **B16** filtros no header (barra de filtros atual continua a funcionar); ~~**B15** sort por coluna~~ (entregue 2026-07-24); ~~**A11**/**E17**~~ (entregues 2026-07-24); **E18** documentação de conceitos (os tooltips inline do E8 já explicam o essencial; página é reforço); ~~**E16** dossiê~~ (entregue 2026-07-25); débitos/fill-ins.
## Bloqueadores atuais
diff --git a/src/app/(campaign)/campanha/(app)/layout.tsx b/src/app/(campaign)/campanha/(app)/layout.tsx
index 771ab7a8..a23da9f1 100644
--- a/src/app/(campaign)/campanha/(app)/layout.tsx
+++ b/src/app/(campaign)/campanha/(app)/layout.tsx
@@ -17,10 +17,12 @@ export default async function CampaignAppLayout({ children }: { children: React.
}
return (
-
+ // print: unlock the h-svh/overflow-hidden shells and drop the app chrome,
+ // otherwise only the first page of the municipality dossier prints (E16).
+
-
-
+
+
{children}
diff --git a/src/app/(campaign)/campanha/(app)/municipios/[slug]/editar/formActions.ts b/src/app/(campaign)/campanha/(app)/municipios/[slug]/editar/formActions.ts
index 8e272157..919ee825 100644
--- a/src/app/(campaign)/campanha/(app)/municipios/[slug]/editar/formActions.ts
+++ b/src/app/(campaign)/campanha/(app)/municipios/[slug]/editar/formActions.ts
@@ -32,6 +32,7 @@ export const updateMunicipalityStrategyFormAction = async (
stateDeputies: repeatedRelationshipFormValues(formData, 'stateDeputies'),
dobradinhaNotes: nullableFormText(formData, 'dobradinhaNotes'),
nextSteps: nullableFormText(formData, 'nextSteps'),
+ budgetNotes: nullableFormText(formData, 'budgetNotes'),
})
revalidateMunicipalityListPaths({ slug: optionalMunicipalitySlugFromForm(formData) })
return { status: 'success', message: 'Estratégia atualizada.' }
diff --git a/src/app/(campaign)/campanha/(app)/municipios/[slug]/page.tsx b/src/app/(campaign)/campanha/(app)/municipios/[slug]/page.tsx
index 1c640200..ac1bcd5c 100644
--- a/src/app/(campaign)/campanha/(app)/municipios/[slug]/page.tsx
+++ b/src/app/(campaign)/campanha/(app)/municipios/[slug]/page.tsx
@@ -9,6 +9,7 @@ import { CampaignPageShell } from '@/components/campaign/CampaignPageShell'
import { CandidateComparePicker } from '@/components/campaign/CandidateComparePicker'
import { MunicipalityBaselineCard } from '@/components/campaign/MunicipalityBaselineCard'
import { MunicipalityCandidateComparisonTable } from '@/components/campaign/MunicipalityCandidateComparisonTable'
+import { MunicipalityDossier } from '@/components/campaign/MunicipalityDossier'
import { MunicipalityGoalAccountCard } from '@/components/campaign/MunicipalityGoalAccountCard'
import { MunicipalityLeadershipsPanel } from '@/components/campaign/MunicipalityLeadershipsPanel'
import { MunicipalityPledgesPanel } from '@/components/campaign/MunicipalityPledgesPanel'
@@ -34,6 +35,7 @@ import {
import { municipalityElectionGeographyForSlug } from '@/utilities/municipalityElectionGeography'
import { loadMunicipalityElectoralBaseline } from '@/utilities/municipalityElectoralBaseline'
import { loadMunicipalityGoalAccount } from '@/utilities/municipalityGoalAccount'
+import { loadMunicipalityDossierData } from '@/utilities/municipalityDossierData'
import {
getMunicipalityDetailViewModel,
MunicipalityNotFoundError,
@@ -90,7 +92,8 @@ export default async function MunicipalityDetailPage({
return (
-
+ {/* print: the dossier tab carries its own capa; page chrome stays out. */}
+
{view.name}
{municipalityKindLabels[view.kind]}
@@ -116,6 +119,18 @@ export default async function MunicipalityDetailPage({
) : null}
+ {activeTab === 'dossie' ? (
+ // Composes every vertical of the município in one read — streams like
+ // the elections tab since it includes the same TSE baseline read.
+ }>
+
+
+ ) : null}
+
{activeTab === 'elections' ? (
// Streams below the header/tab chrome — the TSE baselines and the
// comparison table are the slowest reads on this page.
@@ -245,6 +260,19 @@ const GoalAccountCard = async ({
)
}
+const DossierTab = async ({
+ view,
+ advisorSummaries,
+ payloadUser: { payload, user },
+}: {
+ view: Awaited>
+ advisorSummaries: Awaited>
+ payloadUser: PayloadUser
+}) => {
+ const data = await loadMunicipalityDossierData(payload, user, view)
+ return
+}
+
const MunicipalityTabFallback = () => (