mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:53:05 +00:00
Align docs with A2 as-built delivery on main.
Record the merged municipality↔TSE zone suggestions in AGENTS, the nuclei notebook, roadmap, and the zonas plan (post-simplify API shape).
This commit is contained in:
parent
ac7f2775cd
commit
3b703d42a3
4 changed files with 37 additions and 35 deletions
|
|
@ -9,6 +9,7 @@ alwaysApply: false
|
|||
|
||||
- **MVP Ciclo 1 entregue e enviado em `main` (2026-07-18):** ondas 1–8 (auth/RBAC, domínio+UI de Núcleo/Liderança/Inteligência/Atualizações/Convites, dashboard, hardening) + refactors de composição Next.js, primitivos compartilhados, locks PostgreSQL, fixtures E2E e migration consolidada `20260718_010733_consolidate_campaign_schema`.
|
||||
- **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).
|
||||
- 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).
|
||||
- Em 2026-07-17, a listagem de Núcleos passou a aplicar filtros automaticamente pelo estado da URL, sem botão de confirmação, com comboboxes territoriais estritos compartilhados com o formulário.
|
||||
|
|
@ -85,7 +86,7 @@ alwaysApply: false
|
|||
- Em 2026-07-17, `Contact.city` passou a opcional porque Núcleos podem representar um Território de Identidade sem município/localidade; os fluxos públicos continuam exigindo cidade em seus próprios schemas. O cadastro de liderança mascara o celular no cliente, normaliza 11 dígitos no servidor e preserva valores/erros após falhas.
|
||||
- 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:** `src/lib/bahiaTseZones.ts` (TSE 2024 `detalhe_votacao_munzona` BA, fixture `bahia-tse-zones.official.json`), `territorySuggestions.ts`, `TerritorySuggestionChips` + `NucleusTerritoryAndZonesFields` no formulário de criar/editar núcleo; `TseZoneInput` controlado; aviso âmbar opcional para ZE fora do cadastro da geografia; sem migration.
|
||||
- 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, **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.
|
||||
|
|
|
|||
|
|
@ -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 text is 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 (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. 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. 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`).
|
||||
- **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`.
|
||||
- **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`.
|
||||
|
|
@ -147,7 +147,7 @@ src/
|
|||
├── collections/ # Users, CampaignUser, CampaignInvite, ElectoralNucleus, Leadership, NucleusUpdate, 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, formData.ts, zod schemas (lib/schemas/*)
|
||||
├── lib/ # cities.ts, bahiaTerritories.ts, bahiaTseZones.ts, territorySuggestions.ts, formData.ts, zod schemas (lib/schemas/*)
|
||||
├── utilities/ # campaign*, nucleus*, posts, documents, globals, locks, etc.
|
||||
└── payload.config.ts # Main config
|
||||
```
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ Responsável: —
|
|||
|
||||
Revisões:
|
||||
- 2026-07-18 (entrega): cadastro estático `bahiaTseZones` a partir do TSE 2024 `detalhe_votacao_munzona` BA (SHA-256 no cabeçalho); motor `territorySuggestions` + chips opt-in no coordenador `NucleusTerritoryAndZonesFields`; sem migration. Corrigido drift factual: a validação server-side existente chama-se `validateTerritoryAndZones` (não `validateGeographyAndZones`). Reuso de `canonicalizeMunicipalityName` / aliases de A3 na geração do dataset.
|
||||
- 2026-07-18 (as-built / pós-simplify): `TseZoneInput` só controlado; `NucleusTerritoryFields` removido (só `NucleusTerritoryAndZonesFields`); `buildTerritorySuggestions` devolve também `outsideZones`; helpers de membership `isTseZoneOf*` removidos (usar `.includes` nos arrays); caches módulo `citiesByZone` / `zonesByTerritory`; chips com props discriminadas `kind: 'zone'|'city'`; teto UI de 8 + “+N sugestões”.
|
||||
|
||||
## Premissa atualizada (A1 entregue)
|
||||
|
||||
|
|
@ -26,15 +27,13 @@ Como usar (parte deste plano — seção "Zonas Eleitorais (TSE)" + chips de sug
|
|||
|
||||
- **Adotar a estrutura:** banner “Zonas sugeridas pelo cadastro oficial do TSE — confira antes de salvar”; chips/linhas com badge “sugerida”; entrada manual ainda disponível. O design já aponta para **sugestão opt-in**, não para lock read-only.
|
||||
- **Divergência vs design (aceita nesta revisão):** o HTML mostra checkboxes de zonas individuais pré-marcadas; a UX canônica deste plano são chips `{Município} +` / `{Território} +` **acima** do `TseZoneInput` (abaixo do título da seção), que ao clicar fazem união das ZEs faltantes. No campo Municípios, chips `{Município irmão} +` acima do combobox. Não implementar a lista checkbox do design como fonte de verdade.
|
||||
- **Ajustar cores:** paleta antiga no HTML/PNG; implementar com `TseZoneInput` / `NucleusTerritoryFields` + tokens do tema `campaign` (chips TSE `#F1F3F5`/`#3F4854`; chips de sugestão com primário `#C51414` ou outline secundário — seguir o padrão de Badge existente).
|
||||
- **Ajustar cores:** paleta antiga no HTML/PNG; implementar com `TseZoneInput` / `NucleusTerritoryAndZonesFields` + tokens do tema `campaign` (chips TSE via `Badge variant="tse"`; chips de sugestão de município com `variant="outline"`).
|
||||
|
||||
## Contexto
|
||||
|
||||
Hoje o campo **Zonas TSE** (`TseZoneInput` em `src/components/campaign/TseZoneInput.tsx`) é preenchimento manual livre: números 1–999, validados por `parseTseZoneNumbers` (`src/utilities/tseZone.ts`) e `validateNucleusTerritoryAndZones` (`src/collections/ElectoralNucleus.ts`). Não há relação município↔zona no app.
|
||||
Antes deste plano, **Zonas TSE** (`TseZoneInput`) era preenchimento manual livre (1–999 via `parseTseZoneNumbers` / `validateNucleusTerritoryAndZones`) sem relação município↔zona no app. O bloco de território já derivava `regions` via `territoriesForCities` (A1). A2 fecha o grafo de sugestões opt-in: ZEs a partir de município/TI, municípios irmãos do TI, e municípios oficiais de uma ZE digitada.
|
||||
|
||||
O bloco de território (`NucleusTerritoryFields` em `src/components/campaign/NucleusTerritoryFields.tsx`) já deriva `regions` no cliente via `territoriesForCities` quando há municípios (e o servidor rederiva). Falta o restante do grafo de sugestões: completar ZEs a partir de município/TI, sugerir municípios irmãos do mesmo TI, e sugerir municípios a partir de uma ZE adicionada.
|
||||
|
||||
O TSE publica a correspondência município×zona (“Eleitorado por município e zona”, UF BA). A decisão de produto (2026-07-17, UX revisada 2026-07-18) é versionar esse cadastro como dado estático (estilo `src/lib/bahiaTerritories.ts`) e usá-lo para **oferecer sugestões clicáveis**, sem forçar o conjunto no save.
|
||||
O TSE publica a correspondência município×zona. A decisão de produto (2026-07-17, UX revisada 2026-07-18) versiona esse cadastro como dado estático (estilo `bahiaTerritories.ts`) e o usa para **chips clicáveis**, sem forçar o conjunto no save.
|
||||
|
||||
Exemplo canônico (TI oficial `Vale do Jiquiriçá`):
|
||||
|
||||
|
|
@ -47,7 +46,7 @@ Exemplo canônico (TI oficial `Vale do Jiquiriçá`):
|
|||
## Objetivos
|
||||
|
||||
- Mapeamento estático **município (BA) ↔ Zonas TSE** em `src/lib/bahiaTseZones.ts`, com fixture independente `tests/fixtures/bahia-tse-zones.official.json` e teste `tests/int/bahiaTseZones.int.spec.ts` (espelha `bahiaTerritories`).
|
||||
- Helpers bidirecionais: `tseZonesForCity`, `tseZonesForTerritory` (união), `citiesForTseZone` (reverso), `isTseZoneOfCity` / `isTseZoneOfTerritory`.
|
||||
- Helpers bidirecionais: `tseZonesForCity`, `tseZonesForTerritory` (união, cache por TI), `citiesForTseZone` (reverso, índice módulo).
|
||||
- No formulário de criar/editar núcleo, **três faixas de sugestão opt-in** (chips `{rótulo} +`), sempre acima do input alvo e abaixo do título/label do campo:
|
||||
1. **→ Zonas TSE:** por município selecionado com ZEs faltantes; por TI selecionado (derivado ou manual) com ao menos uma ZE da união faltando.
|
||||
2. **→ Municípios:** irmãos do(s) TI(s) tocado(s) ainda não selecionados; municípios oficiais de cada ZE já selecionada ainda não em `cities`.
|
||||
|
|
@ -63,7 +62,7 @@ Exemplo canônico (TI oficial `Vale do Jiquiriçá`):
|
|||
- **TI a partir do município continua automático** via A1 (`territoriesForCities` / rederivação no servidor). Isso **não** é chip de sugestão — é derivação. Remover municípios restaura edição manual de `regions`.
|
||||
- **Mapeamento como dado estático versionado.** `src/lib/bahiaTseZones.ts` a partir do TSE 2024 `detalhe_votacao_munzona` (UF BA — cobertura eleição-validada dos 417 municípios; preferido aos resultados 2022 por risco de remanejamento de zonas), cabeçalho com URL, data de extração e SHA-256 — estilo `bahiaTerritories.ts`. Nomes TSE reconciliados via `canonicalizeMunicipalityName`. Fixture independente nunca lido a partir do módulo TypeScript.
|
||||
- **Sem migration.** Campo `tseZones` já é `array<{ zoneNumber, label }>`.
|
||||
- **Estado compartilhado.** Hoje `NucleusTerritoryFields` e `TseZoneInput` são siblings com estado independente em `NucleusForm`. Subir estado de `regions` / `cities` / `neighborhoods` / `tseZones` para um coordenador cliente (ex. `NucleusTerritoryAndZonesFields`) que emite hidden inputs e calcula sugestões.
|
||||
- **Estado compartilhado.** `NucleusForm` usa o coordenador `NucleusTerritoryAndZonesFields` (estado de `regions` / `cities` / `neighborhoods` / `tseZones`, hidden inputs, sugestões); `TseZoneInput` é controlado por esse coordenador.
|
||||
- **União, não replace.** Clique em 「Itiruçu +」 = `tseZones ∪ tseZonesForCity('Itiruçu')`. Clique em 「Vale do Jiquiriçá +」 = união de todas as ZEs de `citiesForTerritory('Vale do Jiquiriçá')`. Clique em 「Maracás +」 = adiciona o município (respeitando `MAX_NUCLEUS_CITIES`).
|
||||
- **Quando mostrar o chip de ZE por município:** município ∈ `cities` e existe `z ∈ tseZonesForCity(m)` com `z ∉ tseZones` atuais. Chip some quando o conjunto estiver completo.
|
||||
- **Quando mostrar o chip de ZE por TI:** TI ∈ `displayRegions` (derivado ou manual) e existe ao menos uma ZE em `tseZonesForTerritory(ti)` ausente de `tseZones`. Se o município já tem chip próprio, o chip do TI ainda aparece quando a união do TI tem ZEs além das dos municípios já selecionados (ex.: só Itiruçu selecionado → chip do município + chip do TI para puxar o resto do Vale).
|
||||
|
|
@ -75,15 +74,15 @@ Exemplo canônico (TI oficial `Vale do Jiquiriçá`):
|
|||
- **i18n e naming** seguem o AGENTS.md: identificadores em inglês (`bahiaTseZones`, `tseZonesForCity`, `citiesForTseZone`, `TerritorySuggestionChips`, `NucleusTerritoryAndZonesFields`), strings visíveis em pt-BR (`「Itiruçu +」`, aria-labels “Adicionar zonas TSE de Itiruçu”).
|
||||
- **Ordem de execução:** depois de A1 (já entregue). Nasce contra `cities[]`.
|
||||
|
||||
## Questões em aberto
|
||||
## Questões resolvidas na entrega
|
||||
|
||||
- **Versão do cadastro TSE.** **Recomendação:** versão mais recente validada em 2026 do “Eleitorado por município e zona” BA, data + SHA-256 no cabeçalho e no fixture. Reavaliar a cada eleição.
|
||||
- **Teto de chips visíveis.** Um TI tem até ~30 municípios; uma ZE compartilhada pode listar vários. **Recomendação:** mostrar até **8** chips por faixa; se houver mais, chip “+N municípios” / “+N sugestões” que expande a lista (ou abre o combobox com filtro pré-aplicado). Definir N com produto se 8 for baixo em uso real.
|
||||
- **Prioridade quando as duas fontes sugerem o mesmo município** (irmão do TI e município da ZE). **Recomendação:** deduplicar por nome; uma única chip.
|
||||
- **Cidade com zero zonas no dataset.** **Recomendação:** falhar fechado no helper (array vazio) e **não** mostrar chip 「{cidade} +」; permitir input manual sem travar. Log/teste garante cobertura dos 417 municípios na fixture.
|
||||
- **ZE digitada fora do conjunto da geografia atual.** **Recomendação:** não bloquear save; opcional `FieldDescription` âmbar “Zona X não aparece no cadastro dos municípios/TIs selecionados” — só UX, sem `APIError`.
|
||||
- **Núcleos existentes.** **Recomendação:** carregar zonas/municípios armazenados; sugestões aparecem só para o que ainda falta. Sem backfill.
|
||||
- **Bairros e sugestões.** Bairro não altera o conjunto oficial município→zona. **Recomendação:** chips de ZE por município/TI ignoram bairros (mesmo com bairro, sugerir o conjunto municipal completo; o usuário remove ZEs irrelevantes manualmente).
|
||||
- **Versão do cadastro TSE.** TSE 2024 `detalhe_votacao_munzona` BA (SHA-256 no cabeçalho do módulo e no fixture). Reavaliar a cada eleição.
|
||||
- **Teto de chips visíveis.** Até **8** por faixa; “+N sugestões” expande o restante.
|
||||
- **Prioridade irmão TI vs cidade da ZE.** Deduplica por nome; preferência ao `kind: 'sibling'` quando ambas as fontes sugerem a mesma cidade.
|
||||
- **Cidade com zero zonas.** Helper devolve array vazio; sem chip; input manual livre. Fixture cobre os 417 municípios.
|
||||
- **ZE fora da geografia.** Não bloqueia save; `FieldDescription` âmbar via `outsideZones` do motor de sugestões.
|
||||
- **Núcleos existentes.** Carrega valores armazenados; chips só para o que falta. Sem backfill.
|
||||
- **Bairros.** Ignorados nas sugestões de ZE (conjunto municipal completo).
|
||||
- **Cache/ISR.** Dado estático no bundle; sem `unstable_cache`.
|
||||
|
||||
## Abordagem proposta
|
||||
|
|
@ -108,21 +107,22 @@ flowchart LR
|
|||
Hidden --> Server
|
||||
```
|
||||
|
||||
Componentes:
|
||||
Componentes (as-built):
|
||||
|
||||
- **`src/lib/bahiaTseZones.ts`** (novo): `bahiaMunicipalityTseZones` (cidade → `number[]` ordenado único), `tseZonesForCity(city)`, `tseZonesForTerritory(territory)` (= união sobre `citiesForTerritory`), `citiesForTseZone(zoneNumber)` (reverso, nomes canônicos ordenados), predicados `isTseZoneOfCity` / `isTseZoneOfTerritory`. Cabeçalho com proveniência TSE.
|
||||
- **`tests/fixtures/bahia-tse-zones.official.json`** + **`tests/int/bahiaTseZones.int.spec.ts`**: cobertura dos 417 municípios de `CitiesByState.BA`, zonas em 1–999, reverso consistente com o mapa direto, união por TI bate com `citiesForTerritory`, conteúdo = fixture.
|
||||
- **`src/lib/territorySuggestions.ts`** (novo, puro, testável): funções que, dado `{ cities, regions, tseZones }`, retornam `{ zoneSuggestions: { kind: 'city'|'territory', label: string, zonesToAdd: number[] }[], citySuggestions: { kind: 'sibling'|'zone', label: string, city: string }[] }` já filtradas (só o que falta) e ordenadas. Sem React.
|
||||
- **`TerritorySuggestionChips`** (em `src/components/campaign/TerritorySuggestionChips.tsx`): lista de botões/Badges `{label} +` com `aria-label` descritivo; `onAccept(suggestion)`.
|
||||
- **`NucleusTerritoryAndZonesFields`** (novo ou evolução de `NucleusTerritoryFields` + `TseZoneInput` em `src/components/campaign/`): coordenador cliente; monta chips acima de Municípios e acima de Zonas TSE; ao aceitar, atualiza estado e esconde chips resolvidos; `TseZoneInput` vira controlado (`value`/`onChange`) em vez de só `defaultValues`.
|
||||
- **`NucleusForm.tsx`**: passa a usar o coordenador no lugar dos dois siblings soltos.
|
||||
- **`src/lib/schemas/nucleus.ts` / `ElectoralNucleus.ts` / `nucleusUi.ts`**: **não** adicionar regra de igualdade zonas==oficiais. Manter validação atual de geografia e parse de ZEs. (A revisão 2026-07-18 remove a validação estrita prevista na versão anterior deste plano.)
|
||||
- **`src/lib/bahiaTseZones.ts`**: `bahiaMunicipalityTseZones` (cidade → `number[]` ordenado único), `tseZonesForCity`, `tseZonesForTerritory` (cache `zonesByTerritory`), `citiesForTseZone` (índice `citiesByZone`). Cabeçalho com proveniência TSE.
|
||||
- **`tests/fixtures/bahia-tse-zones.official.json`** + **`tests/int/bahiaTseZones.int.spec.ts`**: cobertura dos 417 municípios, zonas 1–999, reverso/união consistentes, conteúdo = fixture.
|
||||
- **`src/lib/territorySuggestions.ts`**: `buildTerritorySuggestions({ cities, regions, tseZones })` → `{ zoneSuggestions, citySuggestions, outsideZones }` (só o que falta; ordenado pt-BR). Sem React.
|
||||
- **`TerritorySuggestionChips`**: props discriminadas `kind: 'zone'|'city'`; Badges `{rótulo} +`; teto 8 + expandir.
|
||||
- **`NucleusTerritoryAndZonesFields`**: coordenador cliente (território + ZEs + chips + aviso âmbar); emite hidden inputs.
|
||||
- **`TseZoneInput`**: controlado (`value` / `onChange`); parse via `sortedUniqueZoneNumbers` / `parseTseZoneNumbers`.
|
||||
- **`NucleusForm.tsx`**: usa o coordenador (não mais siblings soltos).
|
||||
- **Validação server-side:** sem igualdade zonas==oficiais; geografia + parse de ZEs inalterados.
|
||||
- **Sem migration, sem collection, sem server action.**
|
||||
|
||||
## Dependências
|
||||
|
||||
- **A1** ([territorio-multi-municipio-bairro.md](territorio-multi-municipio-bairro.md)) — dura, já entregue: `cities[]` / `regions` derivados / `territoriesForCities` / `citiesForTerritory`.
|
||||
- Reusa `bahiaTerritories.ts`, `CitiesByState.BA`, `parseTseZoneNumbers`, `NucleusTerritoryFields` / `TseZoneInput` / `StrictCombobox`, padrão de fixture de `bahiaTerritories`.
|
||||
- Reusa `bahiaTerritories.ts`, `CitiesByState.BA`, `parseTseZoneNumbers` / `sortedUniqueZoneNumbers`, `TseZoneInput` / `StrictCombobox`, padrão de fixture de `bahiaTerritories`.
|
||||
- **[baseline-eleitoral-tse.md](baseline-eleitoral-tse.md)** — dependência suave na outra direção: A4 se beneficia de `tseZones` bem preenchidos; o baseline não bloqueia este plano (`electionTally` já resolve cidade↔zona).
|
||||
- Polígonos GeoJSON / B4 continuam fora.
|
||||
|
||||
|
|
@ -139,11 +139,12 @@ Componentes:
|
|||
## Referências
|
||||
|
||||
- `docs/roadmap.md` (Trilha A → A2; Janela 1 ordem 3)
|
||||
- `src/components/campaign/NucleusForm.tsx`, `NucleusTerritoryFields.tsx`, `TseZoneInput.tsx` — formulário a coordenar
|
||||
- `src/lib/bahiaTerritories.ts`, `tests/fixtures/bahia-identity-territories.official.json`, `tests/int/bahiaTerritories.int.spec.ts` — padrão a espelhar
|
||||
- `src/components/campaign/NucleusForm.tsx`, `NucleusTerritoryAndZonesFields.tsx`, `TerritorySuggestionChips.tsx`, `TseZoneInput.tsx`
|
||||
- `src/lib/bahiaTseZones.ts`, `src/lib/territorySuggestions.ts`
|
||||
- `src/lib/bahiaTerritories.ts`, `tests/fixtures/bahia-identity-territories.official.json`, `tests/int/bahiaTerritories.int.spec.ts` — padrão espelhado
|
||||
- `src/collections/ElectoralNucleus.ts` — `validateNucleusTerritoryAndZones`
|
||||
- `src/lib/schemas/nucleus.ts` — `validateTerritoryAndZones`
|
||||
- `src/utilities/tseZone.ts` / `nucleusUi.ts` — parse de ZEs
|
||||
- `src/utilities/tseZone.ts` — `parseTseZoneNumbers` / `sortedUniqueZoneNumbers`
|
||||
- `docs/design-refs/latest/Formulario-Territorio.{png,html}` — referência visual (banner + “sugerida”)
|
||||
- Portal de Dados Abertos do TSE — “Eleitorado por município e zona” (UF BA): https://dadosabertos.tse.jus.br/
|
||||
- AGENTS.md — naming, Bahia implícita, overrideAccess nas queries (este plano não introduz queries novas)
|
||||
- Portal de Dados Abertos do TSE — resultados 2024 / `detalhe_votacao_munzona` (UF BA): https://dadosabertos.tse.jus.br/
|
||||
- AGENTS.md — naming, Bahia implícita; seção Territory do MVP de núcleos documenta A2
|
||||
|
|
|
|||
|
|
@ -56,9 +56,9 @@ MVP de território + reporte implementado e enviado (ondas 1–8 + refactors de
|
|||
- **C1 Compartilhar página** — diálogo no detalhe do núcleo com destinatários WhatsApp (coordenação geral, coordenadores, lideranças engajadas) + copiar link; envia só o link, não concede acesso. [Plano](plans/compartilhar-pagina.md).
|
||||
- **D1 PWA `/campanha`** — manifest + service worker escopados em `/campanha`, shell offline, toast de instalação (Android/iOS) e limpeza de cache no logout; fundação do push (D2). [Plano](plans/pwa-campanha.md).
|
||||
|
||||
### Ciclo 2+ — A2 entregue em 2026-07-18
|
||||
### Ciclo 2+ — A2 entregue e mesclado em `main` (2026-07-18)
|
||||
|
||||
- **A2 Zonas TSE + sugestões cruzadas** — cadastro estático `bahiaTseZones` (TSE 2024 `detalhe_votacao_munzona` BA, 417 municípios), motor puro `territorySuggestions`, coordenador `NucleusTerritoryAndZonesFields` com chips `{rótulo} +` opt-in (município/TI → ZEs; irmãos do TI e cidades da ZE → Municípios). Sem migration, sem igualdade forçada no save. [Plano](plans/zonas-por-municipio.md).
|
||||
- **A2 Zonas TSE + sugestões cruzadas** — cadastro estático `bahiaTseZones` (TSE 2024 `detalhe_votacao_munzona` BA, 417 municípios), motor puro `territorySuggestions` (inclui `outsideZones`), coordenador `NucleusTerritoryAndZonesFields` com chips `{rótulo} +` opt-in (município/TI → ZEs; irmãos do TI e cidades da ZE → Municípios), `TseZoneInput` controlado. Sem migration, sem igualdade forçada no save. [Plano](plans/zonas-por-municipio.md).
|
||||
|
||||
### Referências de design (UX Pilot, 2026-07-18)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue