diff --git a/.cursor/rules/projects/nucleos-eleitorais.mdc b/.cursor/rules/projects/nucleos-eleitorais.mdc index 4ba3236a..e07e33fd 100644 --- a/.cursor/rules/projects/nucleos-eleitorais.mdc +++ b/.cursor/rules/projects/nucleos-eleitorais.mdc @@ -11,8 +11,8 @@ alwaysApply: false - **Ciclo 2 entregue, mesclado e deployado (2026-07-18):** A1 território multi-município/bairro, A3 baseline TSE 2022 (modelo + import), B1 overview da lista, C1 compartilhar página e D1 PWA — detalhes no item datado abaixo. - **A2 entregue e mesclado em `main` (2026-07-18):** cadastro estático município↔ZE (`bahiaTseZones`, TSE 2024) + chips opt-in no formulário (`NucleusTerritoryAndZonesFields` / `territorySuggestions`); sem migration. Plano: [`docs/plans/zonas-por-municipio.md`](../../../docs/plans/zonas-por-municipio.md). - **C3 Planos de Ação implementado (2026-07-18):** collection `actionPlan` + vertical `/campanha/planos` (lista com tabs Próximos/Todos/Realizados/Rascunhos, detalhe com tabs Visão geral/Tarefas/Atualizações, forms novo/editar), blocos "Próximos eventos" no overview de núcleos e no dashboard; `startAt` opcional só em rascunho; access por `coordinators`/`leadership`; migration `20260718_222832_add_action_plan`. -- **B2 Mapa Fase 1 (geometrias) entregue (2026-07-18):** TopoJSON estático municípios IBGE + TIs por dissolução, `bahiaMunicipalityCodes`, helpers `bahiaGeometries`, script `pnpm build:geometries`. Sem UI (B3). Plano: [`docs/plans/mapa-bahia-geometrias.md`](../../../docs/plans/mapa-bahia-geometrias.md). -- **B5 registrado (2026-07-18):** débitos de escala/DRY pós-B2 do `/simplify` (lazy load de geometrias mun/TI + helper `ensureCachedDownload` para CLIs) — plano [`docs/plans/escala-dry-pos-b2.md`](../../../docs/plans/escala-dry-pos-b2.md); F1 preferencialmente com B3; F2 cortável. +- **B2 Mapa Fase 1 (geometrias) entregue (2026-07-18):** TopoJSON estático municípios IBGE (`bahia-municipalities.topo.json`, ~132 KB, object `municipalities`) + TIs por dissolução (`bahia-identity-territories.topo.json`, ~15 KB, object `territories`); tabela `bahiaMunicipalityCodes` + `codeForMunicipality` / `municipalityForCode`; helpers `bahiaGeometries` (`getMunicipalityFeature` / `getTerritoryFeature`); script re-executável `pnpm build:geometries` (cache `data/geometries/`, sem DB); testes int de bijeção/cobertura/teto de bytes. Sem migration, sem UI (Leaflet = B3). Plano as-built: [`docs/plans/mapa-bahia-geometrias.md`](../../../docs/plans/mapa-bahia-geometrias.md). +- **B5 registrado (2026-07-18):** débitos de escala/DRY pós-B2 do `/simplify` que não entraram no cleanup (lazy load mun/TI + helper `ensureCachedDownload` para `build:geometries` / `db:seed:tse`) — plano [`docs/plans/escala-dry-pos-b2.md`](../../../docs/plans/escala-dry-pos-b2.md); F1 preferencialmente com B3; F2 cortável. - **C7 registrado (2026-07-18):** débitos de escala/DRY pós-C3 do `/simplify` (território/contato compostos, helpers FormData/URL, leituras por aba, RMW/índice) — plano [`docs/plans/escala-dry-pos-c3.md`](../../../docs/plans/escala-dry-pos-c3.md); não bloqueia o MVP da agenda. - Fluxos operacionais cobertos: login isolado, CRUD/arquivo de núcleos por slug, designação de coordenador (`geral`), CRM de lideranças, estimativas sugerir/confirmar com versão UUID, feed de atualizações, convites WhatsApp (`wa.me` + hash de uso único), dashboard com agregados só de núcleos ativos. - Pendência operacional (não de código): Consent `lideranca-autopreenchimento` + lote jurídico LGPD antes de dados reais; smoke pós-deploy Vercel. Ver Onda 0 em [`docs/roadmap.md`](../../../docs/roadmap.md). @@ -93,6 +93,7 @@ alwaysApply: false - Em 2026-07-17, decisão de produto: `Município` e `Bairro` do Núcleo passam a aceitar múltiplas entradas (arrays planos `cities`/`neighborhoods`), e `region` vira `regions` derivado no servidor a partir dos municípios, permitindo cruzar Territórios de Identidade. Bairros exigem município único. Modelo A (arrays planos) escolhido em vez de array estruturado por município. Plano: [`docs/plans/territorio-multi-municipio-bairro.md`](../../../docs/plans/territorio-multi-municipio-bairro.md); item do roadmap: A1. - Em 2026-07-18, decisão de produto (revisão A2): no formulário de território, sugestões cruzadas **opt-in** via chips `{rótulo} +` (município/TI → união de ZEs faltantes; TI → municípios irmãos; ZE → municípios oficiais), em vez do auto-preenchimento forçado + Zonas TSE read-only da versão 2026-07-17 do plano. TI a partir do município continua derivado (A1). Cadastro estático município↔zona (`bahiaTseZones`) permanece. Plano: [`docs/plans/zonas-por-municipio.md`](../../../docs/plans/zonas-por-municipio.md); item do roadmap: A2. - Em 2026-07-18, **A2 entregue e mesclado em `main`:** `src/lib/bahiaTseZones.ts` (TSE 2024 `detalhe_votacao_munzona` BA, fixture `bahia-tse-zones.official.json`, índices módulo `citiesByZone` / `zonesByTerritory`), `territorySuggestions.ts` (`buildTerritorySuggestions` com `outsideZones`), `TerritorySuggestionChips` + `NucleusTerritoryAndZonesFields` no formulário; `TseZoneInput` só controlado (`value`/`onChange`); aviso âmbar opcional para ZE fora do cadastro da geografia; alias `NucleusTerritoryFields` removido; sem migration. +- Em 2026-07-18, **B2 entregue (fundação de geometrias do mapa):** malhas IBGE qualidade intermediaria → TopoJSON versionado; TIs por `merge` da composição oficial (não shapefile IDE Bahia); extensão `*.topo.json`; reconciliação de nomes via `canonicalizeMunicipalityName`; script sem guard de banco; cleanup `/simplify` barato no mesmo PR; follow-ups de escala → B5. - Em 2026-07-18, **A1 entregue:** `regions`/`cities`/`neighborhoods` como `text` + `hasMany` (tabela `electoral_nucleus_texts`); migration `20260718_190559_territorio_multi_municipio_bairro` com backfill; filtros da lista permanecem single-select na URL e consultam com `{ equals }` (associação ao array — não o operador substring `contains`); UI de `/campanha` com chips multi + bairros só com 1 município; dashboard e criação de liderança atualizados (`Contact.city` só quando `cities.length === 1`). - O DTO paginado de atualizações contém somente filtro/paginação/feed; a visão geral chama um loader dedicado que busca `limit: 3` e apenas os autores necessários à prévia. - A lista paginada de lideranças seleciona e serializa somente os campos da linha; avaliação interna e dados completos de contato são carregados apenas para a liderança selecionada. O fechamento usa `onCloseAutoFocus` no Sheet e `finalFocus` no Drawer, focaliza a linha de origem (ou o título do Núcleo) antes de navegar para a URL canônica, sem RAF/timeout. diff --git a/AGENTS.md b/AGENTS.md index 2ac576fd..b142bdf1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,7 @@ Backlog consolidado (bloqueadores, site, campanha, white-label): [`docs/roadmap. 3. **`Post`/`Tag` ship the news system, but there is still no `Pages` collection for institutional content.** Partly resolved: the `post`/`tag` collections back the news/publications system, and the home "Últimas notícias" list plus the `/[type]`, `/[type]/[category]`, and article routes are live (see "Posts & Tags" below). Still hardcoded/pending: the home hero heading + subtitle copy (`HomePage` global still exposes only a single `image` field — the text lives in `src/app/(frontend)/(home)/page.tsx`), and there is no `Pages` collection yet for institutional content such as the bio and propostas. 4. **Campaign LGPD consent texts are not yet configured in production.** Invites and self-service leadership flows fail closed until an admin creates `Consent.key = 'lideranca-autopreenchimento'` with counsel-approved copy. The C2 supporter registry (merged to `main`, production-blocked) additionally requires `Consent.key = 'apoiador-cadastro'` and `Consent.key = 'apoiador-intencao-voto'` (vote intention is sensitive data) — same fail-closed behavior. All four keys are bundled in the single legal batch of Onda 0 (see deploy checklist). -**Recently resolved (2026-07-18):** the `/campanha` nuclei MVP shipped (auth/RBAC, nuclei, leaderships, vote estimates, updates, WhatsApp invites, dashboard) with consolidated migration `20260718_010733_consolidate_campaign_schema`. Later the same day, cycle 2 was merged and deployed: multi-municipality/neighborhood territory arrays (migration `20260718_190559_territorio_multi_municipio_bairro`), TSE 2022 election baseline collections + `pnpm db:seed:tse` (migration `20260718_195854_add_election_results`), the filtered nuclei-list overview panel, the nucleus share dialog, and the installable `/campanha` PWA. Still on 2026-07-18, **A2** shipped: static Bahia municipality↔TSE zone map (`src/lib/bahiaTseZones.ts` from TSE 2024 `detalhe_votacao_munzona` BA) plus opt-in territory/zone suggestion chips in the nucleus form (`territorySuggestions`, `NucleusTerritoryAndZonesFields`) — no migration, no forced equality on save. Also the same day: **C2** Cadastro nominal de apoiadores (`supporter` join `Contact`↔campanha with optional `nucleus`, migration `20260718_222656_add_supporter` with `UNIQUE NULLS NOT DISTINCT (contact_id, nucleus_id)`, consent keys `apoiador-cadastro` / `apoiador-intencao-voto` resolved via a generalized `campaignConsent.ts` that fails closed, `/campanha/apoiadores` vertical with list+KPIs/detail/CSV-import wizard; phone required in v1, `lideranca` has no access to the area; production use with real data still waits on Consent keys + legal sign-off). Also the same day: C3 Planos de Ação (`actionPlan` + `/campanha/planos`, migration `20260718_222832_add_action_plan`, blocos "Próximos eventos"). Earlier (2026-07-15): local Postgres via `docker-compose.yml` (Postgres 17, `teqo_test`); Payload migrations baselined; dev/test database guards; `post`/`tag` news routes deployed (see "Posts & Tags" below). +**Recently resolved (2026-07-18):** the `/campanha` nuclei MVP shipped (auth/RBAC, nuclei, leaderships, vote estimates, updates, WhatsApp invites, dashboard) with consolidated migration `20260718_010733_consolidate_campaign_schema`. Later the same day, cycle 2 was merged and deployed: multi-municipality/neighborhood territory arrays (migration `20260718_190559_territorio_multi_municipio_bairro`), TSE 2022 election baseline collections + `pnpm db:seed:tse` (migration `20260718_195854_add_election_results`), the filtered nuclei-list overview panel, the nucleus share dialog, and the installable `/campanha` PWA. Still on 2026-07-18, **A2** shipped: static Bahia municipality↔TSE zone map (`src/lib/bahiaTseZones.ts` from TSE 2024 `detalhe_votacao_munzona` BA) plus opt-in territory/zone suggestion chips in the nucleus form (`territorySuggestions`, `NucleusTerritoryAndZonesFields`) — no migration, no forced equality on save. Also the same day: **C2** Cadastro nominal de apoiadores (`supporter` join `Contact`↔campanha with optional `nucleus`, migration `20260718_222656_add_supporter` with `UNIQUE NULLS NOT DISTINCT (contact_id, nucleus_id)`, consent keys `apoiador-cadastro` / `apoiador-intencao-voto` resolved via a generalized `campaignConsent.ts` that fails closed, `/campanha/apoiadores` vertical with list+KPIs/detail/CSV-import wizard; phone required in v1, `lideranca` has no access to the area; production use with real data still waits on Consent keys + legal sign-off). Also the same day: C3 Planos de Ação (`actionPlan` + `/campanha/planos`, migration `20260718_222832_add_action_plan`, blocos "Próximos eventos"). Also the same day: **B2** map foundation — static TopoJSON for Bahia municipalities + identity territories, `bahiaMunicipalityCodes`, helpers in `bahiaGeometries`, re-runnable `pnpm build:geometries` (no migration, no UI; Leaflet is B3). Post-`/simplify` scale follow-ups for that delivery are registered as **B5** (`docs/plans/escala-dry-pos-b2.md`). Earlier (2026-07-15): local Postgres via `docker-compose.yml` (Postgres 17, `teqo_test`); Payload migrations baselined; dev/test database guards; `post`/`tag` news routes deployed (see "Posts & Tags" below). ## Posts & Tags (news / publications) @@ -94,7 +94,7 @@ The first `/campanha` operational cycle is centered on `electoralNucleus`; a Nú - **Collections:** `electoralNucleus` (territory, coordinators, TSE zone references, intelligence and vote estimates), `leadership` (unique `Contact`↔nucleus join with support status), `nucleusUpdate` (immutable field reports), and `campaignInvite` (single-use WhatsApp invite hashes). All are in admin group `Campanha`. - **Canonical nucleus URLs:** nuclei use a unique canonical `slug` generated from `name` and route through `/campanha/nucleos/[slug]`. IDs stay internal. Names are immutable after creation so existing shared links remain durable without an alias table. -- **Territory:** Bahia is implicit. Since 2026-07-18 nuclei use `hasMany` arrays `regions` / `cities` / `neighborhoods` (plus `locality` / `territoryNotes`), validated against official Bahia identity territories (`src/lib/bahiaTerritories.ts`): `regions` is derived server-side from the selected cities, and neighborhoods require exactly one city. List filters stay single-select and query array membership with `{ equals }`. Migration `20260718_190559_territorio_multi_municipio_bairro` backfilled the old scalar fields (`docs/plans/territorio-multi-municipio-bairro.md`). **A2 (2026-07-18):** static municipality↔TSE zone map in `src/lib/bahiaTseZones.ts` (`tseZonesForCity` / `tseZonesForTerritory` / `citiesForTseZone`); the create/edit form coordinator `NucleusTerritoryAndZonesFields` offers opt-in `{label} +` chips from `buildTerritorySuggestions` (city/TI → missing zones; TI siblings and zone cities → municipalities). Manual `TseZoneInput` stays free (1–999); server validation does not require `tseZones` to match the official set; an amber client advisory may warn when a typed zone is outside the selected geography. Details: `docs/plans/zonas-por-municipio.md`. +- **Territory:** Bahia is implicit. Since 2026-07-18 nuclei use `hasMany` arrays `regions` / `cities` / `neighborhoods` (plus `locality` / `territoryNotes`), validated against official Bahia identity territories (`src/lib/bahiaTerritories.ts`): `regions` is derived server-side from the selected cities, and neighborhoods require exactly one city. List filters stay single-select and query array membership with `{ equals }`. Migration `20260718_190559_territorio_multi_municipio_bairro` backfilled the old scalar fields (`docs/plans/territorio-multi-municipio-bairro.md`). **A2 (2026-07-18):** static municipality↔TSE zone map in `src/lib/bahiaTseZones.ts` (`tseZonesForCity` / `tseZonesForTerritory` / `citiesForTseZone`); the create/edit form coordinator `NucleusTerritoryAndZonesFields` offers opt-in `{label} +` chips from `buildTerritorySuggestions` (city/TI → missing zones; TI siblings and zone cities → municipalities). Manual `TseZoneInput` stays free (1–999); server validation does not require `tseZones` to match the official set; an amber client advisory may warn when a typed zone is outside the selected geography. Details: `docs/plans/zonas-por-municipio.md`. **B2 (2026-07-18):** static map geometries — see "Campaign map geometries (B2)" below (Leaflet surfaces are B3). - **Role scope:** `geral` sees/manages all nuclei, assigns coordinators, and confirms estimates; `coordenador` sees assigned nuclei and manages their leaderships, updates, invites, and estimates; `lideranca` sees only nuclei linked through an engaged leadership record, creates own updates, and suggests estimates. - **Sensitive fields:** `leadership.supportStatus`, internal notes, consent notes, and strategic nucleus intelligence are staff-only and are excluded from leadership view models. Revoking `engajado` revokes nucleus access immediately. Dashboard aggregates omit Payload-redacted missing statuses and fail closed on invalid enum values. - **People and consent:** leaderships always reuse `Contact`; multi-collection writes are transactional. Self-service updates accept a strict whitelist derived from the invite token. Campaign consent is looked up only by the stable key `lideranca-autopreenchimento`. @@ -123,6 +123,16 @@ Mobilization events / agenda backing the campaign's field operations (roadmap it - **Access.** `geral` everything; `coordenador` where listed in `coordinators` (may create, auto-included); `lideranca` where `leadership` ∈ engaged — write limited to toggling `tasks.done` and appending `updates`. Domain writes go through `withPayloadTransaction` (`src/utilities/payloadTransaction.ts`). - **UI** `/campanha/planos` — list with tabs Próximos (default) / Todos / Realizados / Rascunhos, filters (kind, territory), cards with kind/status badge + date/time + municipality + TI chip + "Resp:" + task progress; detail with tabs Visão geral / Tarefas / Atualizações (pattern `NucleusTabNav`); new/edit forms. "Próximos eventos" blocks surface on the nuclei list overview and the dashboard. +## Campaign map geometries (B2) + +Static Bahia map foundation for future Leaflet surfaces (roadmap B2; UI is B3). No migration, collection, Consent, or server action — same static-data pattern as `bahiaTerritories` / `bahiaTseZones`. + +- **Artifacts (committed):** `src/lib/geometries/bahia-municipalities.topo.json` (~132 KB; Topology object `municipalities`, 417 features, `properties: { codarea, name }`) and `src/lib/geometries/bahia-identity-territories.topo.json` (~15 KB; object `territories`, 27 features, `properties: { code, name }`). Soft size budget in tests: ≤ ~600 KB each. +- **Code table:** `src/lib/bahiaMunicipalityCodes.ts` — canonical municipality name → IBGE 7-digit `codarea`, plus `codeForMunicipality` / `municipalityForCode`. Fixture `tests/fixtures/bahia-municipality-codes.official.json` + int tests for bijection/coverage. +- **Runtime helpers:** `src/lib/bahiaGeometries.ts` — `getMunicipalityFeature` / `getTerritoryFeature`, typed topologies, feature arrays. Eagerly decodes TopoJSON→GeoJSON at module load (lazy split is B5 F1 — prefer with B3). +- **Territory polygons:** dissolved from IBGE municipality meshes via `bahiaIdentityTerritoryRecords` (`topojson` `merge`), not the IDE Bahia shapefile (IDE remains provenance/validation reference only). +- **Rebuild:** `pnpm build:geometries` (`scripts/build-bahia-geometries.mjs`) downloads IBGE Malhas (qualidade intermediaria) + Localidades, reconciles names with `canonicalizeMunicipalityName`, simplifies/quantizes, emits the artifacts above. Cache under `data/geometries/` (gitignored; override with `GEOMETRIES_CACHE_DIR`). **Does not touch the database** — no `assertLocalDatabase` / no Neon risk. Not part of `pnpm build` or `pnpm dev`. Details: `docs/plans/mapa-bahia-geometrias.md`. Scale/DRY follow-ups from `/simplify`: `docs/plans/escala-dry-pos-b2.md` (B5). + ## Election baseline data (TSE 2022) Public TSE 2022 results back the electoral baseline (roadmap item A3; UI lands with A4). Three collections in admin group `Dados Eleitorais`: `electionTally` (per city×zone totals: eligible voters, turnout, valid/blank/null votes), `electionCandidateVote` (nominal votes per candidate×city×zone), and `electionCandidate` (candidate registry with a cross-year `identityKey` hash — no CPF or voter ID is ever persisted). Read access requires an authenticated `campaignUser` or `users` account (`canReadElectionData`); writes are Payload-admin-only, and the import runs via CLI with `overrideAccess: true`. @@ -166,7 +176,7 @@ src/ ├── collections/ # Users, CampaignUser, CampaignInvite, ElectoralNucleus, Leadership, NucleusUpdate, Supporter, ActionPlan, ElectionTally, ElectionCandidateVote, ElectionCandidate, Media, Petition, Signature, Consent, Contact, Subscription, Post, Tag ├── globals/ # Global configs (SiteSettings, HomePage, Metadata) ├── components/ # Custom React components (campaign/*, ui from shadcn) -├── lib/ # cities.ts, bahiaTerritories.ts, bahiaTseZones.ts, territorySuggestions.ts, formData.ts, zod schemas (lib/schemas/*) +├── lib/ # cities.ts, bahiaTerritories.ts, bahiaTseZones.ts, bahiaMunicipalityCodes.ts, bahiaGeometries.ts, geometries/*.topo.json, territorySuggestions.ts, formData.ts, zod schemas (lib/schemas/*) ├── utilities/ # campaign*, nucleus*, posts, documents, globals, locks, etc. └── payload.config.ts # Main config ``` diff --git a/docs/plans/escala-dry-pos-b2.md b/docs/plans/escala-dry-pos-b2.md index 7ec2d82f..9d53aa80 100644 --- a/docs/plans/escala-dry-pos-b2.md +++ b/docs/plans/escala-dry-pos-b2.md @@ -1,6 +1,6 @@ # Escala e DRY pós-B2 (geometrias + scripts CLI) -Status: rascunho +Status: registrado no roadmap (fases pendentes) Atualizado em: 2026-07-18 Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha B, item B5) Responsável: — diff --git a/docs/plans/mapa-bahia-geometrias.md b/docs/plans/mapa-bahia-geometrias.md index 2612a494..c723e630 100644 --- a/docs/plans/mapa-bahia-geometrias.md +++ b/docs/plans/mapa-bahia-geometrias.md @@ -48,12 +48,12 @@ Hierarquia TSE: município → (1+) zona → (muitas) seção; a relação munic - **Join por chave estável:** - Municípios: o núcleo guarda `cities[]` (array de nomes, desde A1). Tabela `src/lib/bahiaMunicipalityCodes.ts` (nome canônico → código IBGE de 7 dígitos) + fixture `tests/fixtures/bahia-municipality-codes.official.json` + teste `tests/int/bahiaMunicipalityCodes.int.spec.ts`. Reconciliação IBGE→canônico via `canonicalizeMunicipalityName` (`src/lib/electionResults.ts`). - Territórios: join por `code` (01–27) já presente em `bahiaIdentityTerritoryRecords`; sem tabela extra. -- **Helpers:** `src/lib/bahiaGeometries.ts` — `getMunicipalityFeature(codarea)`, `getTerritoryFeature(code)`, topologias tipadas (framework-free para B3). -- **Proveniência e versionamento:** cada artefato com cabeçalho/documentação de proveniência (URL, SHA-256 do download de origem), no estilo de `bahiaTerritories.ts` / `bahiaTseZones.ts`. +- **Helpers:** `src/lib/bahiaGeometries.ts` — `getMunicipalityFeature(codarea)`, `getTerritoryFeature(code)`, arrays `bahiaMunicipalityFeatures` / `bahiaTerritoryFeatures`, topologias tipadas (`bahiaMunicipalitiesTopology` / `bahiaIdentityTerritoriesTopology`; objects `municipalities` / `territories`). Framework-free para B3. Lookups de código: `codeForMunicipality` / `municipalityForCode` em `bahiaMunicipalityCodes.ts`. +- **Proveniência e versionamento:** cabeçalho em `bahiaMunicipalityCodes.ts` + script (URL + SHA-256 dos downloads IBGE), no estilo de `bahiaTerritories.ts` / `bahiaTseZones.ts`. ## Script de geração (one-off, re-executável) -- `scripts/build-bahia-geometries.mjs` (`pnpm build:geometries`): baixa IBGE Malhas + Localidades, reconcilia nomes, constrói topologia com `topojson-server` + `topojson-simplify` + `quantize`, dissolve territórios com `topojson-client` `merge`, emite os `*.topo.json`, `bahiaMunicipalityCodes.ts` e o fixture. Cache em `data/geometries/` (gitignored). **Não toca banco** — sem `assertLocalDatabase`. Não roda em build/dev — só quando se atualiza a versão de origem. +- `scripts/build-bahia-geometries.mjs` (`pnpm build:geometries`): baixa IBGE Malhas + Localidades (via `downloadToBuffer` de `electionResultsZip.ts`), reconcilia nomes com `canonicalizeMunicipalityName` (só `UnknownMunicipalityError`), constrói topologia com `topojson-server` + `topojson-simplify` (`SIMPLIFY_QUANTILE = 0.35`) + `quantize` (`1e4`), dissolve territórios com `topojson-client` `merge`, emite os `*.topo.json`, `bahiaMunicipalityCodes.ts` e o fixture. Cache em `data/geometries/` (gitignored; `GEOMETRIES_CACHE_DIR`). **Não toca banco** — sem `assertLocalDatabase`. Não roda em build/dev — só quando se atualiza a versão de origem. ## Leaflet na campanha (B3 — pendente) @@ -74,6 +74,21 @@ Hierarquia TSE: município → (1+) zona → (muitas) seção; a relação munic **Sequenciamento:** a Fase 1 é independente e paralelizável. A Fase 2 rende mais depois de B1 ✓ (overview) e ganha valor com A4 (baseline no produto). Nenhum dos dois bloqueia B3 — o mapa funciona só com estimativa/nº de núcleos. B5 F1 é dependência suave de qualidade de bundle. +## Entrega as-built (B2, 2026-07-18) + +| Artefato | Caminho | Notas | +| -------- | ------- | ----- | +| TopoJSON municípios | `src/lib/geometries/bahia-municipalities.topo.json` | ~132 KB; 417 features; object `municipalities` | +| TopoJSON TIs | `src/lib/geometries/bahia-identity-territories.topo.json` | ~15 KB; 27 features; object `territories` (dissolução IBGE) | +| Tabela nome→`codarea` | `src/lib/bahiaMunicipalityCodes.ts` | + `codeForMunicipality` / `municipalityForCode` | +| Helpers runtime | `src/lib/bahiaGeometries.ts` | `topojson-client` `feature()` + Maps no load do módulo | +| Fixture oficial | `tests/fixtures/bahia-municipality-codes.official.json` | evidence SHA + bijeção | +| Testes int | `tests/int/bahiaMunicipalityCodes.int.spec.ts`, `tests/int/bahiaGeometries.int.spec.ts` | cobertura 417/27, teto de bytes, nomes de objects via módulo | +| Script | `scripts/build-bahia-geometries.mjs` | `pnpm build:geometries`; deps: `topojson-client` (runtime), `topojson-server` / `topojson-simplify` (dev) | +| Cache | `data/geometries/` | gitignored | + +**Cleanup `/simplify` já aplicado no PR da Fase 1:** reuso de `downloadToBuffer`; catch só de `UnknownMunicipalityError`; remoção de dead code de codegen (`stableStringifyTopo` enganoso, tipo `BahiaMunicipalityCode` não usado). **Não aplicado (→ B5):** lazy load split mun/TI; helper CLI compartilhado `ensureCachedDownload`. + ## Ciclo seguinte — Zonas TSE como camada (B4) Após A2 ✓ + B3. Polígono por dissolução dos municípios membros (abordagem A, default — já validada na Fase 1 para TIs) ou por geocodificação dos endereços das seções → casco (abordagem B, questão em aberto). Ponto representativo por centroide da zona. @@ -103,10 +118,14 @@ Após A2 ✓ + B3. Polígono por dissolução dos municípios membros (abordagem ## Referências -- `docs/roadmap.md` (B2 / B3 / B4 na trilha B) +- `docs/roadmap.md` (B2 / B3 / B4 / B5 na trilha B) +- `docs/plans/escala-dry-pos-b2.md` (follow-ups pós-`/simplify`) +- AGENTS.md — seção "Campaign map geometries (B2)" - `src/lib/bahiaTerritories.ts` + `tests/fixtures/bahia-identity-territories.official.json` + `tests/int/bahiaTerritories.int.spec.ts` (padrão a espelhar) - `src/lib/bahiaMunicipalityCodes.ts`, `src/lib/bahiaGeometries.ts`, `src/lib/geometries/*.topo.json` +- `src/lib/electionResults.ts` (`canonicalizeMunicipalityName`), `src/lib/electionResultsZip.ts` (`downloadToBuffer`) - `scripts/build-bahia-geometries.mjs` +- `tests/int/bahiaMunicipalityCodes.int.spec.ts`, `tests/int/bahiaGeometries.int.spec.ts` - `src/collections/ElectoralNucleus.ts`, `src/utilities/campaignDashboardPageData.ts`, `src/utilities/nucleusViewModels.ts`, `src/utilities/nucleusListOverviewPageData.ts` (agregados a reusar no B3) - `docs/plans/zonas-por-municipio.md` (dependência da camada de Zonas) - `docs/plans/baseline-eleitoral-tse.md` (baseline por zona, consumidor do mapa)