From a5903d52ad40124f5079d32c28616c371812d443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Ign=C3=A1cio=20Fontoura=20Solla?= Date: Sat, 18 Jul 2026 15:04:32 -0300 Subject: [PATCH] Align project docs with the shipped nuclei MVP. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update AGENTS, README, roadmap, and the Núcleos notebook so status, territory scope, tests, and the LGPD production blocker match main. --- .cursor/rules/projects/nucleos-eleitorais.mdc | 19 ++++--------- AGENTS.md | 28 ++++++++++--------- README.md | 20 +++++++++---- docs/roadmap.md | 12 ++++---- 4 files changed, 42 insertions(+), 37 deletions(-) diff --git a/.cursor/rules/projects/nucleos-eleitorais.mdc b/.cursor/rules/projects/nucleos-eleitorais.mdc index ddfef5d4..e291691d 100644 --- a/.cursor/rules/projects/nucleos-eleitorais.mdc +++ b/.cursor/rules/projects/nucleos-eleitorais.mdc @@ -7,16 +7,9 @@ alwaysApply: false ## Status -- Onda 1 (auth/RBAC e fundação visual): concluída e aprovada em 2026-07-16. -- Onda 2 (domínio Núcleo): implementada em 2026-07-16; aguardando integração/revisão. -- Onda 3B (domínio Liderança): implementada em 2026-07-16; migration aplicada nos bancos locais de desenvolvimento e teste. -- Onda 3A (UI Núcleo): concluída e aprovada em 2026-07-16. -- Onda 4A (UI Lideranças): implementada em 2026-07-16 com lista responsiva, filtros, ficha/formulário, contato principal e serialização específica por papel. -- Onda 4B (Inteligência do Núcleo): implementada em 2026-07-17 com fluxo seguro de sugerir/confirmar estimativas, edição de forças/riscos/perfis/dobrada/contato principal e DTOs mínimos por papel. -- Onda 6B (domínio de convites): implementada em 2026-07-17 com Consent por chave estável, convites WhatsApp com hash/expiração/revogação, consumo transacional de uso único, autopreenchimento restrito e criação/recuperação de acesso. -- Onda 6A (dashboard e atualizações): implementada e revisada em 2026-07-17 com agregados só de núcleos ativos, semana exata `America/Bahia`, feed paginado por URL e contato contextual da coordenação. -- Onda 7 (UI de convites): implementada em 2026-07-17 com ação WhatsApp por liderança, bloqueio por Consent/status e rota pública dinâmica de autopreenchimento/acesso/recuperação. -- Onda 8 (hardening): implementada em 2026-07-17 com E2E por papel, negativa de escopo, convite público/WhatsApp sem navegação real, verificação responsiva 360/390/768/1440, correção de alvos de toque no login, documentação e checklist de deploy. +- **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`. +- 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. - Em 2026-07-17, o contato principal passou a usar busca remota limitada e escopada no detalhe do núcleo, sem serializar a lista completa de lideranças. - Em 2026-07-17, o feed paginado de atualizações e a prévia das três mais recentes passaram a usar loaders separados por aba, sem consulta de prévia fora da visão geral. @@ -32,15 +25,15 @@ alwaysApply: false - Em 2026-07-17, a Refactor Phase 2D reduziu `actions/invite.ts` a uma orquestração de 44 linhas e separou repositório SQL, criação e resgate em módulos `server-only`, preservando 488 testes de integração. - Em 2026-07-17, a Refactor Phase 4A consolidou as dez migrations locais não publicadas de `20260717_*` em `20260718_010733_consolidate_campaign_schema`, mantendo intacta a história rastreada até `20260716_010420_add_campaign_user`; os bancos locais foram reconciliados sem alterar dados de aplicação. - Em 2026-07-18, o follow-up Phase 4C fixou o Playwright em dois workers por padrão, adicionou guardas automáticos de erros do navegador/console/5xx, fortaleceu privacidade HTML+Flight por papel e tornou o login por teclado uma interação real com Enter e cookie verificado. -- Em 2026-07-18, a exceção concorrente de `SupportStatusBadge` foi rastreada a projeções em voo nas quais o acesso de campo podia omitir `supportStatus`; o agregado do dashboard agora valida o enum e omite registros incompletos sem inventar um status padrão. +- Em 2026-07-18, a projeção do dashboard omite `supportStatus` ausente por redação de acesso e lança invariante genérico se um valor não-vazio inválido chegar à fronteira; agregação e badge são exaustivos no enum. ## Decisões - Núcleo Eleitoral é a unidade operacional; Zona TSE é uma referência oficial adicional. - Todo núcleo exige geografia; natureza organizativa e setor são dimensões complementares. - Exclusão no app é substituída por arquivamento. -- `lastUpdateAt` existe como placeholder até o domínio de atualizações. -- `Contact` tem e-mail opcional no banco, telefone normalizado/indexado e acesso por escopo; os schemas públicos continuam exigindo e-mail. +- `lastUpdateAt` é mantido pelo domínio de atualizações (`nucleusUpdate`) sob advisory lock; não é mais placeholder. +- `Contact` tem e-mail e cidade opcionais no banco, telefone normalizado/indexado e acesso por escopo; os schemas públicos continuam exigindo e-mail (e cidade onde aplicável). - `Leadership` é a junção única contato↔núcleo; acesso de liderança e contato exige vínculo `engajado`, e avaliação interna não é serializada para a titular. - A UI de liderança usa view models separados: coordenação recebe avaliação interna; papel `lideranca` recebe somente a própria ficha sem `supportStatus`, `notes` ou `consentNote`. - A deduplicação de `Contact` por telefone usa `pg_advisory_xact_lock(hashtextextended(phone, 0))` na própria sessão Drizzle da transação Payload, evitando duplicatas entre instâncias concorrentes. diff --git a/AGENTS.md b/AGENTS.md index 0b68e65c..346932d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ You are an expert Payload CMS developer. When working with Payload projects, fol ## Teqo Project Context (read this first) -Teqo is the digital platform for deputado federal Jorge Solla (PT-BA): public site + editorial CMS + (soon) an internal `/campanha` campaign-management area, all in this one Next.js + Payload app. Full architecture/decision doc lives outside this repo at `plano-arquitetura-campanha-2026.md` (Cowork workspace) — check it for the "why" behind decisions below. +Teqo is the digital platform for deputado federal Jorge Solla (PT-BA): public site + editorial CMS + an internal `/campanha` campaign-management area, all in this one Next.js + Payload app. Full architecture/decision doc lives outside this repo at `plano-arquitetura-campanha-2026.md` (Cowork workspace) — check it for the "why" behind decisions below. **Locked decisions — do not relitigate without a new concrete reason:** @@ -16,7 +16,7 @@ Teqo is the digital platform for deputado federal Jorge Solla (PT-BA): public si - `Contact` is the normalized "person" record (name, email, phone, state/city via `src/lib/cities.ts`, CEP). Don't create a parallel "person" collection for new features (e.g. líderanças/apoiadores) — add a join collection that relates to `Contact`, the same way `Signature` and `Subscription` do. - Every action that writes to more than one collection (see `src/app/(frontend)/actions/*.ts`) wraps the writes in a Payload transaction (`payload.db.beginTransaction/commitTransaction/rollbackTransaction`) and passes `req: { transactionID }` to every `payload.create`/`payload.update` call. Follow this pattern for any new multi-collection write. -- LGPD consent is tracked via a `Consent` collection (richText, versioned) referenced by relationship from `Signature`/`Subscription`. Any new form/opt-in flow should link to a `Consent` document the same way — don't invent a new consent mechanism. +- LGPD consent is tracked via a `Consent` collection (richText, versioned, optional stable `key`) referenced by relationship from `Signature`/`Subscription`/campaign `leadership`. New campaign opt-in flows resolve Consent by stable `key` (e.g. `lideranca-autopreenchimento`) and fail closed when missing — don't invent a parallel consent mechanism or hardcode document IDs for new work. - Cache invalidation: collections/globals call `revalidateDocumentById` (`src/utilities/documents.ts`) / `revalidateGlobal` (`src/utilities/globals.ts`) in an `afterChange` hook. Add the same hook to any new collection/global that backs a public page. Collections whose listing is cached under a shared tag also call the listing helper (`revalidateCollectionListing`, e.g. `revalidatePostsListing()` → `revalidateTag('posts')`) — see "Posts & Tags" below. - Admin UI is organized via `admin.group` (e.g. `'Abaixo-assinados'`, `'Contatos'`, `'Configurações'`, `'Paginas'`) — group new collections consistently instead of leaving them ungrouped. - i18n: Payload admin defaults to `pt` (not `pt-BR` — see `payload.config.ts`, `payload/i18n/pt`). @@ -30,15 +30,16 @@ Teqo is the digital platform for deputado federal Jorge Solla (PT-BA): public si - To copy production content locally, use `pnpm db:pull` (`scripts/db-pull.mjs`): it only reads prod, only writes local, and excludes supporter PII (`contact`/`signature`/`subscription` row data). - To (re)populate news content, `pnpm db:seed:posts` fetches articles live from jorgesolla.com.br into the LOCAL db (same non-local `DATABASE_URL` guard as `pnpm dev`). It writes from a CLI process outside the deployed runtime, so after seeding — or after ANY direct-DB change — you must bust the production `posts` cache via `POST /api/revalidate`. See "Posts & Tags" below. -## Known Gaps (as of 2026-07-15 — resolve before relying on this file for onboarding new devs) +## Known Gaps (as of 2026-07-18 — resolve before relying on this file for onboarding new devs) Backlog consolidado (bloqueadores, site, campanha, white-label): [`docs/roadmap.md`](docs/roadmap.md). 1. **Payload admin RBAC is still absent.** The campaign auth collection now has `role`, but the `users` admin collection still has no roles; every admin user has full Payload admin access. Add admin roles before opening `/admin` to a broader team. 2. **Legacy public consent flows still hardcode document IDs** (e.g. `consent: 2` in `submitWhatsapp.ts`). The campaign leadership flow does not: it requires `Consent.key = 'lideranca-autopreenchimento'` and fails closed when missing. Migrating the older public flows remains separate work. 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-15):** local Postgres now runs via `docker-compose.yml` (Postgres 17, matching prod, auto-creating `teqo_test`); Payload migrations are set up with prod baselined; dev/test database guards prevent accidental production access (see the two database skills). The `post`/`tag` collections and the public news/article/listing routes shipped and are 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`. 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) @@ -92,17 +93,18 @@ 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. -- **Role scope:** `geral` sees/manages all nuclei 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. +- **Territory (current cycle):** Bahia is implicit. Nuclei use single `region` / `city` / `neighborhood` / `locality` / `territoryNotes` validated against official Bahia identity territories (`src/lib/bahiaTerritories.ts`). Multi-município/bairro arrays remain a planned follow-up (`docs/plans/territorio-multi-municipio-bairro.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`. -- **Invites:** only SHA-256 token hashes are persisted. Public invite pages are dynamic, `no-store`, `noindex,nofollow`, `no-referrer`, and have no third-party application scripts. Login invites are allowed only for engaged leaderships. -- **Vote estimates:** proposals never overwrite the confirmed value; only a coordinator assigned to the nucleus or `geral` may confirm. +- **Invites:** only SHA-256 token hashes are persisted. Absolute invite URLs require a valid `NEXT_PUBLIC_SITE_URL` (exact HTTPS DNS origin in production). Public invite pages are dynamic, `no-store`, `noindex,nofollow`, `no-referrer`, and have no third-party application scripts. Login invites are allowed only for engaged leaderships. +- **Vote estimates:** proposals never overwrite the confirmed value; each proposal carries a server-generated UUID version compared under advisory lock on confirm. Only a coordinator assigned to the nucleus or `geral` may confirm. ### Local verification and deploy checklist 1. Confirm `DATABASE_URL` is `postgresql://teqo:teqo@localhost:5432/teqo` and `.env.test` points to `teqo_test`; never use Neon for development, tests, E2E, or local builds. 2. Run `pnpm db:start`, `pnpm migrate:status`, and `DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test pnpm migrate:status`; every migration must show `Ran: Yes`. -3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed, `pnpm exec tsc --noEmit`, `pnpm lint`, `pnpm test:int`, `pnpm test:e2e`, and `pnpm build` against the local database. +3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed, `pnpm exec tsc --noEmit`, `pnpm lint`, `pnpm test` (unit + int), `pnpm test:e2e` (or `pnpm test:all`), and `pnpm build` against the local database. Payload CLI scripts (`migrate`, `generate:*`, `payload`) and the build migrate step use `--conditions=react-server` so `server-only` modules load correctly outside Next.js. 4. Scan every edited runtime source file with Aikido and resolve new findings before handoff. Do not expand hardening work into unrelated pre-existing findings without an explicit scope change. 5. Review Vercel environment variables and migration SQL before deploy; `pnpm build` applies pending migrations to production automatically. 6. **Mandatory production blocker:** do not insert real leadership/support data or enable invites until electoral counsel documents the LGPD art. 11 basis, approves the specific versioned consent text, and an admin creates it with exactly `Consent.key = 'lideranca-autopreenchimento'`. The app intentionally fails closed when this key is absent. @@ -132,11 +134,11 @@ src/ │ ├── (frontend)/ # Frontend routes + server actions (src/app/(frontend)/actions/*.ts) │ ├── (payload)/ # Payload admin routes │ └── (campaign)/ # Internal campaign-management area (auth barrier, see "Campaign auth") -├── collections/ # Collection configs (Users, CampaignUser, Media, Petition, Signature, Consent, Contact, Subscription, Post, Tag) +├── collections/ # Users, CampaignUser, CampaignInvite, ElectoralNucleus, Leadership, NucleusUpdate, Media, Petition, Signature, Consent, Contact, Subscription, Post, Tag ├── globals/ # Global configs (SiteSettings, HomePage, Metadata) -├── components/ # Custom React components (incl. components/ui from shadcn) -├── lib/ # cities.ts, zod schemas (lib/schemas/*) -├── utilities/ # documents.ts / globals.ts / posts.ts (revalidation + posts helpers) +├── components/ # Custom React components (campaign/*, ui from shadcn) +├── lib/ # cities.ts, bahiaTerritories.ts, formData.ts, zod schemas (lib/schemas/*) +├── utilities/ # campaign*, nucleus*, posts, documents, globals, locks, etc. └── payload.config.ts # Main config ``` diff --git a/README.md b/README.md index 17a20fb9..7160e382 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,11 @@ The goal is to help political teams build direct, durable relationships with the ### Phase 1: Jorge Solla Website -Initial delivery focuses on Jorge Solla's public website and communication workflows: +Initial delivery focuses on Jorge Solla's public website, editorial CMS, and the internal `/campanha` tool: -- Public-facing content and updates -- Institutional pages and biography -- Media and campaign communication assets +- Public-facing content and updates (news/`post` + `tag` live) +- Institutional pages and biography (still pending a `Pages` collection) +- Internal campaign operations: electoral nuclei, leaderships, estimates, updates, WhatsApp invites - Editorial operations through Payload CMS ### Phase 2: White-Label Platform @@ -84,7 +84,11 @@ Tests use the isolated `teqo_test` database (config in `.env.test`). Prepare its DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test pnpm migrate ``` -Then run `pnpm test` (or `pnpm test:int` / `pnpm test:e2e`). +Then run: + +- `pnpm test` — unit + integration +- `pnpm test:e2e` — Playwright (requires the test DB schema and a free port) +- `pnpm test:all` — unit + integration + E2E ## Tech Stack @@ -93,6 +97,12 @@ Then run `pnpm test` (or `pnpm test:int` / `pnpm test:e2e`). - PostgreSQL (via `@payloadcms/db-postgres`) - TypeScript +## Campaign (`/campanha`) + +Internal campaign tool for electoral nuclei, local leaderships, vote estimates, field updates, and WhatsApp invites. Authenticated separately from `/admin` via the `campaignUser` collection (`geral` / `coordenador` / `lideranca`). Operational status and decisions: [`.cursor/rules/projects/nucleos-eleitorais.mdc`](.cursor/rules/projects/nucleos-eleitorais.mdc). Conventions and deploy checklist: `AGENTS.md` (“Campaign auth” / “Campaign nuclei MVP”). + +**Production blocker:** do not load real leadership data or enable invites until counsel-approved `Consent.key = 'lideranca-autopreenchimento'` exists. Absolute invite URLs require `NEXT_PUBLIC_SITE_URL` as an exact HTTPS DNS origin in production. + ## Roadmap Backlog and future plans live in [`docs/roadmap.md`](docs/roadmap.md) (blockers, public site, `/campanha` next cycles, admin RBAC, white-label). diff --git a/docs/roadmap.md b/docs/roadmap.md index 0ad0a188..9abb2785 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap — Teqo -Atualizado em: 2026-07-17 (revisão de priorização orientada ao calendário eleitoral) +Atualizado em: 2026-07-18 (código do MVP de Núcleos enviado; priorização eleitoral mantida) Registro canônico no repositório dos planos futuros e débitos conhecidos. Status operacional do ciclo atual de Núcleos fica em [`.cursor/rules/projects/nucleos-eleitorais.mdc`](../.cursor/rules/projects/nucleos-eleitorais.mdc); este arquivo lista o que ainda é futuro ou bloqueador, **em ordem de execução**, com dependências e paralelismo explícitos. @@ -29,7 +29,7 @@ Registro canônico no repositório dos planos futuros e débitos conhecidos. Sta ## Onda 0 — Caminho crítico para `/campanha` em produção -O MVP de Núcleos está **code-complete** (ondas 1–8). O que separa a vertical da produção não é engenharia — é jurídico + operação de deploy. Estes itens começam **imediatamente** e correm em paralelo a qualquer trilha de engenharia: +O MVP de Núcleos está **entregue em código** (ondas 1–8 + refactors; commit em `main` em 2026-07-18). A migration `20260718_010733_consolidate_campaign_schema` aplica automaticamente no `pnpm build` da Vercel. O que ainda separa a vertical de uso operacional com dados reais é jurídico + smoke pós-deploy: 1. **Lote jurídico único de LGPD/Consent** _(externo — assessoria jurídica eleitoral; é o caminho crítico da vertical inteira)_. Uma única rodada cobrindo: - Base do art. 11 da LGPD + texto versionado de `Consent.key = 'lideranca-autopreenchimento'` (bloqueador do MVP de Núcleos; o app falha fechado sem a chave). @@ -38,15 +38,15 @@ O MVP de Núcleos está **code-complete** (ondas 1–8). O que separa a vertical - **Aviso de Privacidade / política de privacidade institucional** (obrigação do controlador antes de coleta em massa; também é item do site público). - Avaliação de necessidade de RIPD (tratamento em larga escala + dado sensível). - Racional: fatiar em rodadas separadas multiplica o lead time externo; quatro textos + aviso numa rodada só. -2. **Checklist de verificação e deploy** do AGENTS.md (migrations `Ran: Yes`, types, lint, testes int/E2E, build local, envs Vercel — incl. `REVALIDATE_SECRET`, `NEXT_PUBLIC_SITE_URL` HTTPS). -3. **Lançamento do MVP assim que (1) liberar a chave de liderança + (2) fechar.** Decisão de sequenciamento: **não** esperar a migração multi-município para lançar. Prós de lançar antes: coordenadores estruturam núcleos reais durante as convenções (20/07–05/08), cada semana vale capacidade de campo; a migration multi-município é in-place com backfill seguro e pode rodar com dados reais pequenos. Contras: uma migration a mais pós-lançamento. O contra é barato; o pró é tempo de campanha, que não volta. +2. **Smoke pós-deploy** _(após o build Vercel aplicar a migration)_: conferir `NEXT_PUBLIC_SITE_URL` HTTPS exato, login `/campanha`, criar núcleo de teste, e só então cadastrar o Consent de liderança. Checklist completo no AGENTS.md. +3. **Ativação com dados reais assim que (1) liberar a chave de liderança.** Decisão de sequenciamento: **não** esperar a migração multi-município. Prós: coordenadores estruturam núcleos reais durante as convenções (20/07–05/08); a migration multi-município é in-place com backfill seguro e pode rodar com dados reais pequenos. Contras: uma migration a mais pós-lançamento. O contra é barato; o pró é tempo de campanha, que não volta. 4. **Onboarding do time real** (usuários `geral`/`coordenador`, primeiros núcleos, treinamento básico de campo). ## Campanha (`/campanha`) ### Ciclo 1 — Núcleos (entregue) -MVP de território + reporte implementado (ondas 1–8): auth isolada `campaignUser` (`geral` / `coordenador` / `lideranca`), núcleos com slug canônico, lideranças, estimativas sugerir/confirmar, atualizações semanais, convites WhatsApp (`wa.me`, hash, uso único), dashboard e hardening. Detalhes: [`.cursor/rules/projects/nucleos-eleitorais.mdc`](../.cursor/rules/projects/nucleos-eleitorais.mdc). +MVP de território + reporte implementado e enviado (ondas 1–8 + refactors de composição/infra/fixtures): auth isolada `campaignUser` (`geral` / `coordenador` / `lideranca`), núcleos com slug canônico, designação de coordenador, lideranças, estimativas sugerir/confirmar com versão UUID, atualizações semanais, convites WhatsApp (`wa.me`, hash, uso único), dashboard e hardening. Território atual ainda é `region`/`city`/`neighborhood` unitários; multi-município permanece no item A1. Detalhes: [`.cursor/rules/projects/nucleos-eleitorais.mdc`](../.cursor/rules/projects/nucleos-eleitorais.mdc). ### Grafo de dependências @@ -173,7 +173,7 @@ Itens sem seta de entrada (**paralelizáveis a qualquer momento**): A1, A3, B1, | Item | Status | Fonte | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------- | -| Lote jurídico único LGPD (4 textos de `Consent` + Aviso de Privacidade + avaliação de RIPD) — ver Onda 0. O app falha fechado sem as chaves | **caminho crítico; iniciar já** | notebook Núcleos; AGENTS checklist; planos cadastro-nominal e notifications | +| Lote jurídico único LGPD (4 textos de `Consent` + Aviso de Privacidade + avaliação de RIPD) — ver Onda 0. Código do MVP já em `main`; o app falha fechado sem as chaves | **caminho crítico; iniciar já** | notebook Núcleos; AGENTS checklist; planos cadastro-nominal e notifications | | RBAC em `users` — todo usuário do admin Payload tem acesso total; necessário antes de abrir `/admin` a equipe maior. Nota: o import de apoiadores foi desenhado no `/campanha` justamente para não depender disso | pendente | AGENTS Known Gap #1 | | Fluxos públicos ainda hardcodam ID de Consent (ex. `consent: 2` em `submitWhatsapp.ts`); migrar para chave estável como na campanha | pendente | AGENTS Known Gap #2; plano-arquitetura | | Collection `Pages` inexistente; hero/copy da home ainda hardcoded — bio, propostas e páginas institucionais | pendente (não bloqueia `/campanha`) | AGENTS Known Gap #3 |