diff --git a/.cursor/rules/projects/nucleos-eleitorais.mdc b/.cursor/rules/projects/nucleos-eleitorais.mdc index cb2435e4..0627fbc6 100644 --- a/.cursor/rules/projects/nucleos-eleitorais.mdc +++ b/.cursor/rules/projects/nucleos-eleitorais.mdc @@ -18,7 +18,7 @@ alwaysApply: false - **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. - **Trilha E registrada (2026-07-19) — mapa de projeção por município:** plano E1–E5 para equiparar e superar as planilhas "Mapa de projeção de votos Solla 2026" (`docs/sheets/`): E1 metas em cenários (`voteGoals` Bom/Regular/Mínimo) + `priority` no núcleo (migration compartilhada com E3), E2 série histórica TSE 2014/2018 + `computeVoteTrend` derivado (sem migration), E3 `dobradinhaNotes`/`nextSteps` staff-only na inteligência, E4 import único `pnpm db:seed:mapa` (sem criar `Contact`/`leadership`), E5 Salvador por bairro registrado como futuro. Somente documentação/plano — nada implementado. Plano: [`docs/plans/mapa-projecao-municipios.md`](../../../docs/plans/mapa-projecao-municipios.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). +- Pendência operacional (não de código): textos provisórios Onda 0 provisionados por migration ([`docs/plans/onda-0.md`](../../../docs/plans/onda-0.md)); substituição pela assessoria jurídica eleitoral + hold de PII real de lideranças/apoiadores até lá; 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. @@ -107,6 +107,6 @@ alwaysApply: false ## Bloqueadores e próximas frentes -- **Bloqueador obrigatório de produção:** antes de dados reais ou convites, a assessoria jurídica eleitoral deve documentar a base do art. 11 da LGPD, aprovar o texto versionado específico e cadastrar `Consent.key = 'lideranca-autopreenchimento'`. Em 2026-07-17 esse texto entrou num **lote jurídico único** com os demais consents da campanha (`apoiador-cadastro`, `apoiador-intencao-voto`, `campanha-notificacoes-push`) e o Aviso de Privacidade — ver "Onda 0" do roadmap. +- **Bloqueador obrigatório de produção (PII real):** antes de lideranças/apoiadores reais ou import em massa, a assessoria jurídica eleitoral deve aprovar os textos definitivos (substituindo os provisórios do [Onda 0 MVP](../../../docs/plans/onda-0.md)). Os textos provisórios já sobem automaticamente via migration; o hold é de **titulares reais**, não de engenharia. - Em 2026-07-17 o roadmap foi repriorizado orientado ao calendário eleitoral (Res. TSE 23.760/2026: propaganda a partir de 16/08, 1º turno 04/10), com ordem de execução, trilhas paralelas (A dados eleitorais/território, B superfícies de coordenação, C operação de campo, D plataforma) e grafo de dependências. Decisões de sequenciamento: lançar o MVP antes da migração multi-município; multi-município antes de zonas-por-municipio/eventos/demandas; baseline TSE independente de zonas-por-municipio (dependência rebaixada a suave); WhatsApp Business API movido a fora de escopo (vedação Meta + Res. 23.610 art. 33). - Backlog consolidado do produto (admin RBAC, mapa, eventos, demandas, apoiadores, listas globais, site, white-label): [`docs/roadmap.md`](../../../docs/roadmap.md). diff --git a/docs/plans/escala-dry-pos-onda0.md b/docs/plans/escala-dry-pos-onda0.md new file mode 100644 index 00000000..8e7d6411 --- /dev/null +++ b/docs/plans/escala-dry-pos-onda0.md @@ -0,0 +1,117 @@ +# Escala e DRY pós-Onda 0 (Consent / privacidade / cache) + +Status: registrado no roadmap (fases pendentes) +Atualizado em: 2026-07-19 +Item do roadmap: [docs/roadmap.md](../roadmap.md) (Onda 0+, fill-in de engenharia pós-MVP) +Responsável: — + +## Contexto + +A Onda 0 ([onda-0.md](onda-0.md)) entregou textos provisórios de `Consent`, global `privacy-policy`, rota `/privacidade`, migrations `20260719_054706_*` / `20260719_054707_*`, CLI `pnpm db:seed:consent` e duas passagens `/simplify` (2026-07-19). O cleanup aplicado cobriu: migration de dados só via SQL, constantes `ONDA0_CONSENT_ENTRIES` / `ONDA0_PRIVACY_POLICY_BODY`, batch `find` no provision Payload, `DELETE … IN` no down SQL, memoização de `getCachedGlobal`, singleton em `/privacidade`, resolução de conflito no roadmap. + +Os três revisores do `/simplify` (quality / performance / reuse) deixaram débitos **maiores que cleanup pontual**, registrados aqui para não se perderem entre deploy e revisão jurídica. + +1. **Cache de globals após writes fora do runtime Payload.** `provisionOnda0ConsentAndPrivacyDb` (deploy) e `seed-consent.mjs` com `skipRevalidation` não disparam `revalidateTag('global_privacy-policy')`. `/privacidade` pode servir `published`/body stale até TTL ou até um edit no admin. O endpoint `POST /api/revalidate` só busta `posts`. +2. **Chaves de Consent duplicadas.** `lideranca-autopreenchimento` já vive em `campaignInvite.ts`; `apoiador-*` em `campaignConsent.ts` (`server-only`). `onda0ConsentTexts.ts` ainda repete literais (só a chave de liderança foi unificada). `campanha-notificacoes-push` só existe em Onda 0 até D2. +3. **Cobertura de testes incompleta no caminho de produção.** Int tests exercitam `provisionOnda0ConsentAndPrivacy` (Payload API); o deploy roda `provisionOnda0ConsentAndPrivacyDb`. Reconciliation chama o down do seed mas não asserta estado de `privacy_policy` / ausência das chaves. +4. **DRY de superfícies públicas Lexical.** `/privacidade` e artigos (`/[type]/[category]/[slug]`) convertem Lexical→HTML com padrões CSS distintos (`prose` vs `[&_…]`). Layout editorial (`SiteHeader` + `Footer`) duplica `(home)/layout.tsx` sem shell compartilhado. +5. **UX do link de privacidade.** `Footer` sempre mostra `/privacidade`; com `published: false` a rota 404a (mesmo padrão da ficha de apoiador antes do seed). +6. **Documentação redundante.** `scripts/consent-texts/README.md` só aponta para `onda0ConsentTexts.ts` e `onda-0.md`. + +**Já resolvido no simplify (não reabrir):** tags em `unstable_cache` para globals; factory memoizada por `(slug, depth)`; duplicate fetch metadata+page em `/privacidade`; N+1 no provision Payload; batch DELETE no down SQL; migration schema sem `payload`/`req` mortos. + +## Objetivos + +- Deploy e `pnpm db:seed:consent` deixam `/privacidade` e globals cacheados consistentes sem exigir edit manual no admin. +- Uma fonte de verdade para literais de chave `Consent` testável em unit tests (sem `server-only` no módulo de chaves). +- Int tests cobrem o caminho SQL/migration que produção usa. +- Reduzir duplicação de layout e renderização Lexical no site público quando surgir a próxima página institucional (`Pages`). +- Guardrails: sem migration obrigatória; sem alterar textos jurídicos; hold de PII real permanece até assessoria. + +## Decisões travadas + +- **Um plano O0+, quatro fases ordenadas.** Mesmo racional de A7/B5/C10: um registro no roadmap, PRs por fase. Ordem: revalidate/cache (correção operacional) → chaves Consent (DRY) → testes SQL → DRY UI público (só quando `Pages` ou segunda página institucional justificar). +- **Dependência suave da Onda 0 engenharia ✓.** Não bloqueia lote jurídico nem smoke com dados fictícios; bloqueia confiança em cache pós-deploy e paridade de testes. +- **Módulo de chaves sem `server-only`.** `src/lib/consentKeys.ts` (ou equivalente) importável por `onda0ConsentTexts.ts` (unit) e re-exportado por `campaignConsent.ts` / `campaignInvite.ts` — não o inverso. +- **Revalidate de globals no mesmo espírito de `posts`.** Estender `src/app/(frontend)/api/revalidate/route.ts` com tag opcional ou lista fixa de globals públicos; documentar no runbook Onda 0. +- **Cortável:** Fase 4 (Lexical/layout shell) até existir `Pages` ou bio/propostas; Fase 5 (Footer condicional) é barata mas opcional se o seed sempre publica em prod. +- **i18n e naming** (AGENTS.md): identificadores em inglês (`consentKeys`, `revalidateGlobalTag`), strings visíveis em pt-BR inalteradas. + +## Questões em aberto + +- **Revalidate automático no fim da migration vs endpoint manual?** **Recomendação:** endpoint primeiro (paridade com `posts` seed); hook pós-migrate no build só se o time esquecer o passo em produção — documentar curl no [onda-0.md](onda-0.md). +- **Unificar seed CLI no caminho SQL?** **Recomendação:** manter Payload no CLI (valida hooks/admin) mas chamar `revalidateGlobal('privacy-policy')` ao final; migration continua SQL-only. +- **Extrair `LexicalProse` agora ou com `Pages`?** **Recomendação:** adiar Fase 4 até o primeiro consumidor além de `/privacidade` + artigo — evitar abstração de uma página. + +## Abordagem proposta + +```mermaid +flowchart TD + O0["Onda 0 MVP ✓"] --> F1 + F1["Fase 1 — revalidate globals
(API + runbook)"] + F1 --> F2["Fase 2 — consentKeys.ts
(DRY literais)"] + F2 --> F3["Fase 3 — int tests SQL/migration"] + F3 --> F4["Fase 4 — Lexical + EditorialLayout
(quando Pages)"] + F3 --> F5["Fase 5 — Footer link condicional"] +``` + +### Fase 1 — Cache de globals pós-migration/seed + +- Estender `src/app/(frontend)/api/revalidate/route.ts`: aceitar `tag` query/body (`global_privacy-policy`, lista allowlist) ou segundo secret header; manter default `posts` para compatibilidade. +- Após `provisionOnda0ConsentAndPrivacy` em `scripts/seed-consent.mjs`, chamar `revalidateGlobal('privacy-policy')` via Local API helper ou fetch local ao endpoint (mesmo padrão documentado para post-seed). +- Atualizar checklist em [onda-0.md](onda-0.md) § Smoke: `curl` pós-deploy quando migration de dados rodar sem edit admin. +- Opcional: helper `revalidatePublicGlobals()` em `src/utilities/globals.ts` com tags conhecidas (`privacy-policy`, futuro `metadata`). + +### Fase 2 — Chaves Consent únicas + +- Novo `src/lib/consentKeys.ts` exportando `CAMPAIGN_INVITE_CONSENT_KEY`, `SUPPORTER_REGISTRATION_CONSENT_KEY`, `SUPPORTER_VOTE_INTENTION_CONSENT_KEY`, `CAMPAIGN_PUSH_CONSENT_KEY` (constantes literais). +- `campaignInvite.ts` / `campaignConsent.ts` importam e re-exportam (compatibilidade). +- `onda0ConsentTexts.ts` monta `ONDA0_CONSENT_KEYS` / `ONDA0_CONSENT_KEY_LIST` a partir do módulo. +- Quando D2 iniciar, `get/requireCampaignPushConsent` usa `CAMPAIGN_PUSH_CONSENT_KEY` do mesmo módulo. + +### Fase 3 — Testes do caminho SQL + +- Int spec: `provisionOnda0ConsentAndPrivacyDb` + `removeOnda0ConsentAndPrivacyDb` contra `teqo_test`, ou invocar migration `054707` up/down diretamente. +- Assertar: quatro chaves presentes após up; `privacy_policy.published === true`; após down chaves removidas e `published === false`. +- Em `campaignMigrationReconciliation.int.spec.ts`: após `onda0Seed.down`, assertar ausência das chaves Onda 0 (opcional: count `privacy_policy`). + +### Fase 4 — DRY site público Lexical/layout _(adiar até `Pages`)_ + +- Componente `LexicalArticle` ou reutilizar classes do artigo de post em `/privacidade`. +- `EditorialPageLayout` extraindo `(home)/layout.tsx` e `privacidade/layout.tsx`. +- `unstable_cache` wrapper para HTML convertido keyed por `updatedAt` + tag global. + +### Fase 5 — Footer e metadata _(opcional)_ + +- `Footer` server component: `getCachedGlobal('privacy-policy')` depth 0, `select`/campo `published` — esconder link se false. +- `generateMetadata` de `/privacidade`: sufixo `| ${metadata.title}` do global `metadata` (cosmético). + +**Migration:** nenhuma nas fases 1–5. + +## Dependências + +- **Dura:** Onda 0 engenharia entregue (migrations + `/privacidade` + provision utilities). +- **Suave:** collection `Pages` (AGENTS Known Gap #3) para justificar Fase 4; D2 para `requireCampaignPushConsent`. +- Reusa: `getCachedGlobal` / `revalidateGlobal` (`src/utilities/globals.ts`), `provisionOnda0ConsentAndPrivacyDb` (`src/utilities/onda0Provision.ts`), padrão `POST /api/revalidate` de posts. + +## Não escopo + +- Substituir textos provisórios por versão jurídica final — continua no lote Onda 0 / assessoria. +- Migrar fluxos públicos legados (`consent: 2` em `submitWhatsapp.ts`) — AGENTS Known Gap #2; plano separado. +- RBAC admin `users` — Known Gap #1. +- Links Lexical clicáveis em `/privacidade` no corpo do consent footer (`/privacidade` como texto plano é aceitável no MVP). +- Unificar provision Payload e SQL num único caminho — dual path é intencional (migration sem hooks vs CLI com Payload). + +## Referências + +- `docs/roadmap.md` — § Onda 0, Fill-ins, Site público +- `docs/plans/onda-0.md` — runbook e provisionamento +- `src/lib/onda0ConsentTexts.ts` — textos canônicos +- `src/utilities/onda0Provision.ts` — SQL vs Payload +- `src/utilities/globals.ts` — cache de globals +- `src/app/(frontend)/api/revalidate/route.ts` — revalidate posts +- `src/app/(frontend)/privacidade/page.tsx` — página pública +- `src/components/Footer.tsx` — link privacidade +- `src/utilities/campaignConsent.ts` / `campaignInvite.ts` — chaves runtime +- `tests/int/onda0Provision.int.spec.ts` / `campaignMigrationReconciliation.int.spec.ts` +- AGENTS.md — Consent por chave estável, `revalidateGlobal`, naming diff --git a/docs/plans/onda-0-ripd-nota-mvp.md b/docs/plans/onda-0-ripd-nota-mvp.md new file mode 100644 index 00000000..5489181f --- /dev/null +++ b/docs/plans/onda-0-ripd-nota-mvp.md @@ -0,0 +1,23 @@ +# Onda 0 — nota interna RIPD (MVP) + +Status: rascunho operacional (não substitui RIPD formal) +Atualizado em: 2026-07-19 +Relacionado: [onda-0.md](onda-0.md) + +## Quando um RIPD formal será necessário + +A assessoria jurídica eleitoral deve avaliar a necessidade de Relatório de Impacto à Proteção de Dados Pessoais (RIPD) quando a campanha passar a tratar em **larga escala**: + +- cadastro nominal de apoiadores com dados de contato; +- **intenção de voto** (dado sensível — art. 11 LGPD); +- eventual cruzamento territorial / inteligência eleitoral. + +## O que este MVP não faz + +- Não produz o RIPD. +- Não substitui parecer da assessoria eleitoral. +- Não autoriza coleta de titulares reais — apenas textos provisórios para destravar a plataforma. + +## Próximo passo jurídico + +Após aprovação dos textos definitivos de Consent e da Política de Privacidade, a assessoria deve confirmar se RIPD é obrigatório antes da primeira importação em massa ou captura sistemática de intenção de voto. diff --git a/docs/plans/onda-0.md b/docs/plans/onda-0.md new file mode 100644 index 00000000..21afff4a --- /dev/null +++ b/docs/plans/onda-0.md @@ -0,0 +1,110 @@ +# Onda 0 — go-live MVP (Consent + privacidade) + +Status: implementado (engenharia) +Atualizado em: 2026-07-19 +Item do roadmap: [docs/roadmap.md](../roadmap.md) (Onda 0 — Caminho crítico para `/campanha` em produção) +Responsável: — + +## Contexto + +O MVP de Núcleos e o cadastro nominal de apoiadores (C2) estão em `main` e falham fechado sem as chaves estáveis de `Consent` e sem o Aviso de Privacidade publicado. A assessoria jurídica eleitoral ainda não entregou os textos finais; para desbloquear engenharia, QA e smoke operacional, a Onda 0 provisiona **textos provisórios** automaticamente em local e produção via migrations, com hold explícito de PII real até revisão jurídica. + +## Objetivos + +- Provisionar automaticamente (local + prod via `pnpm migrate` / build Vercel) as quatro chaves de `Consent` e o Global `privacy-policy` publicado. +- Expor `/privacidade` no site público e corrigir links mortos (Footer, ficha de apoiador). +- Manter fail-closed do runtime; não cadastrar lideranças/apoiadores **reais** antes da assessoria eleitoral substituir os textos. +- Oferecer `pnpm db:seed:consent` como espelho idempotente para re-aplicar textos após edição no repo. + +## Decisões travadas + +- **Assessoria provisória no MVP (2026-07-19).** A implementação redige textos utilizáveis em pt-BR com banner de status provisório. Não substituem parecer jurídico nem RIPD formal. +- **Provisionamento automático.** Migration de schema (`privacy-policy` global) + migration de dados (`seed_onda0_consent_and_privacy`) aplicadas no deploy; sem passo manual de colar no admin. +- **Hold de PII real.** Não importar CSV real, não convidar lideranças reais, não cadastrar apoiadores reais até substituição dos textos pela assessoria eleitoral. +- **Fonte canônica dos textos:** [`src/lib/onda0ConsentTexts.ts`](../../src/lib/onda0ConsentTexts.ts) + [`src/utilities/onda0Provision.ts`](../../src/utilities/onda0Provision.ts). +- **i18n e naming** seguem o AGENTS.md: identificadores em inglês (`privacy-policy`, `provisionOnda0ConsentAndPrivacy`); strings visíveis em pt-BR. + +## Chaves de Consent + +| Chave | Uso | +| ----------------------------- | ------------------------------------------------------ | +| `lideranca-autopreenchimento` | Convites / autopreenchimento de liderança | +| `apoiador-cadastro` | Cadastro nominal de apoiador | +| `apoiador-intencao-voto` | Intenção de voto (dado sensível — art. 11 LGPD) | +| `campanha-notificacoes-push` | Opt-in push (D2; chave provisionada, UI push pendente) | + +## Provisionamento + +### Automático (local + produção) + +1. `20260719_054706_add_privacy_policy_global` — schema do Global. +2. `20260719_054707_seed_onda0_consent_and_privacy` — upsert Consent ×4 + `privacy-policy` (`published: true`). + +Aplicado por `pnpm migrate` localmente e por `payload migrate` no `pnpm build` da Vercel. + +### CLI espelho (re-seed) + +```bash +pnpm db:seed:consent +# Neon (somente re-seed consciente pós-revisão jurídica): +ALLOW_REMOTE_DB=true pnpm db:seed:consent +``` + +Refusa host não-local sem `ALLOW_REMOTE_DB=true` (mesma família de guard que `db:seed:tse`). + +## Substituição pelos textos finais + +1. Assessoria eleitoral aprova textos definitivos. +2. Atualizar [`src/lib/onda0ConsentTexts.ts`](../../src/lib/onda0ConsentTexts.ts). +3. Nova migration de dados **ou** `ALLOW_REMOTE_DB=true pnpm db:seed:consent` em produção. +4. Aceites com `contentHash` antigo exigem novo checkbox (comportamento existente). +5. Só então liberar coleta de titulares reais (lideranças, apoiadores, import CSV). + +## Smoke (dados fictícios apenas) + +**Local (PR):** + +1. `pnpm migrate` (ou `db:seed:consent` se iterando textos). +2. Login `/campanha` → criar núcleo de teste. +3. Convite com Consent visível (contato fictício). +4. Apoiador de teste (+ intenção de voto opcional). +5. GET `/privacidade` com banner provisório. + +**Produção (pós-deploy):** + +1. Confirmar env: `NEXT_PUBLIC_SITE_URL` HTTPS, `PAYLOAD_SECRET`, `DATABASE_URL`, `BLOB_*`. +2. Migrations aplicadas no build. +3. Smoke com **dados fictícios**; não importar CSV real nem convidar titulares reais. + +## Onboarding staff (sem PII de campo) + +- Criar `campaignUser` `geral`/`coordenador` via `/admin`. +- Estruturar núcleos de teste, agenda, baseline TSE. +- Treinamento de campo com titulares reais **após** revisão jurídica. + +## RIPD + +Ver [onda-0-ripd-nota-mvp.md](onda-0-ripd-nota-mvp.md) — nota interna; não substitui RIPD formal da assessoria. + +## Não escopo + +- D2 push / VAPID. +- Known Gap #2 (Consent por ID nos fluxos públicos). +- Collection `Pages`. +- RBAC em `users`. +- Seed de lideranças/apoiadores reais. + +## Referências + +- [`docs/roadmap.md`](../roadmap.md) § Onda 0 +- [`docs/plans/escala-dry-pos-onda0.md`](escala-dry-pos-onda0.md) — débitos técnicos pós-`/simplify` (revalidate globals, chaves Consent, testes SQL) +- [`docs/plans/cadastro-nominal-apoiadores.md`](cadastro-nominal-apoiadores.md) +- [`docs/plans/notifications.md`](notifications.md) +- [`src/collections/Consent.ts`](../../src/collections/Consent.ts) +- [`src/globals/PrivacyPolicy.ts`](../../src/globals/PrivacyPolicy.ts) +- AGENTS.md — Consent por chave, fail-closed, checklist deploy + +## Revisões + +- **2026-07-19:** Plano criado na implementação Onda 0 — textos provisórios MVP, provisionamento automático local+prod, hold de PII real. +- **2026-07-19:** Débitos do `/simplify` registrados em [escala-dry-pos-onda0.md](escala-dry-pos-onda0.md) (O0+). diff --git a/docs/roadmap.md b/docs/roadmap.md index e7e54960..f7b24dee 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap — Teqo -Atualizado em: 2026-07-19 (MVP + Ciclo 2 deployados; A2 entregue; A4 Baseline no produto + Gap vs 2022 implementado e mesclado em `main` (Fases 2–4 + simplify); A7 registrado — escala/DRY pós-A4 do `/simplify`; C2 engenharia pronta e mesclada em `main`; C3 Planos de Ação implementado e mesclado em `main`; C6 Escala e DRY pós-C2 implementado e mesclado em `main` (Fases 1–5 + simplify); C7 parcial 2026-07-19 (FormData território, contadores lista, locks, contactSearchQuery) — débitos pós-simplify registrados no plano; C8 Escala e DRY pós-C6 implementado e mesclado em `main` (Fases 1–4 + duas passagens `/simplify`); C9 Escala e DRY pós-C8 implementado (Fases 1–4 + simplify); C10 registrado — escala/DRY pós-C9 do `/simplify`; B2 fundação de geometrias do mapa entregue; B5 registrado — escala/DRY pós-B2 do `/simplify`; **Trilha E registrada — E1–E5 mapa de projeção por município**, equiparar/superar as planilhas "Mapa de projeção de votos 2026" — [plano](plans/mapa-projecao-municipios.md)) +Atualizado em: 2026-07-19 (MVP + Ciclo 2 deployados; A2 entregue; A4 Baseline no produto + Gap vs 2022 implementado e mesclado em `main` (Fases 2–4 + simplify); A7 registrado — escala/DRY pós-A4 do `/simplify`; C2 engenharia pronta e mesclada em `main`; C3 Planos de Ação implementado e mesclado em `main`; C6 Escala e DRY pós-C2 implementado e mesclado em `main` (Fases 1–5 + simplify); C7 parcial 2026-07-19 (FormData território, contadores lista, locks, contactSearchQuery) — débitos pós-simplify registrados no plano; C8 Escala e DRY pós-C6 implementado e mesclado em `main` (Fases 1–4 + duas passagens `/simplify`); C9 Escala e DRY pós-C8 implementado (Fases 1–4 + simplify); C10 registrado — escala/DRY pós-C9 do `/simplify`; B2 fundação de geometrias do mapa entregue; B5 registrado — escala/DRY pós-B2 do `/simplify`; **Trilha E registrada — E1–E5 mapa de projeção por município**, equiparar/superar as planilhas "Mapa de projeção de votos 2026" — [plano](plans/mapa-projecao-municipios.md); **Onda 0 MVP** — Consent/privacidade provisórios auto-provisionados (migrations) + `/privacidade`; hold de PII real até jurídico — [plano](plans/onda-0.md); **O0+ registrado** — escala/DRY pós-Onda 0 do `/simplify` — [plano](plans/escala-dry-pos-onda0.md)) 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. @@ -31,7 +31,7 @@ Registro canônico no repositório dos planos futuros e débitos conhecidos. Sta O MVP de Núcleos está **entregue e deployado** (ondas 1–8 + refactors; 2026-07-18), junto com A1 (território multi-município/bairro), A2 (zonas TSE + sugestões cruzadas — sem migration), A3 (baseline TSE 2022 — modelo + import), A4 (baseline no produto + Gap vs 2022 — mesclado em `main`), B1 (overview da lista), C1 (compartilhar página), C3 (planos de ação / agenda) e D1 (PWA). C2 (cadastro nominal de apoiadores) tem a engenharia pronta e mesclada em `main`, mas segue bloqueada em produção pelo lote jurídico (Consent keys). As migrations (`20260718_010733_consolidate_campaign_schema`, `20260718_190559_territorio_multi_municipio_bairro`, `20260718_195854_add_election_results`, `20260718_222656_add_supporter`, `20260718_222832_add_action_plan`, `20260719_011015_add_supporter_import_batch`) aplicam 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: +1. **Lote jurídico único de LGPD/Consent** _(externo — assessoria jurídica eleitoral; textos finais substituem os provisórios do [Onda 0 MVP](plans/onda-0.md); PII real de lideranças/apoiadores permanece em hold até lá)_. 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). - Textos de `apoiador-cadastro` e `apoiador-intencao-voto` (bloqueadores do cadastro nominal — [detalhes](plans/cadastro-nominal-apoiadores.md)). - Texto de `campanha-notificacoes-push` (opt-in de push — [detalhes](plans/notifications.md)). @@ -42,6 +42,8 @@ O MVP de Núcleos está **entregue e deployado** (ondas 1–8 + refactors; 2026- 3. **Ativação com dados reais assim que (1) liberar a chave de liderança.** (A decisão de 2026-07-17 de não esperar a migração multi-município ficou superada: A1 foi entregue e deployado antes da ativação — coordenadores já estruturam núcleos reais durante as convenções, 20/07–05/08, com o território definitivo.) 4. **Onboarding do time real** (usuários `geral`/`coordenador`, primeiros núcleos, treinamento básico de campo). +**O0+ Escala e DRY pós-Onda 0 registrado (2026-07-19)** — débitos do `/simplify` que não entraram no cleanup: revalidate de globals pós-migration/seed, módulo único de chaves `Consent`, testes do caminho SQL, DRY Lexical/layout no site público (adiar até `Pages`), link condicional no Footer. Não bloqueia lote jurídico nem smoke com dados fictícios. [Plano](plans/escala-dry-pos-onda0.md). + ## Campanha (`/campanha`) ### Ciclo 1 — Núcleos (entregue) @@ -110,6 +112,7 @@ Os designs gerados pelo UX Pilot para os próximos ciclos estão em [`docs/desig | C8 Escala e DRY pós-C6 | — (sem design novo; reusa os de C2/C6) | [escala-dry-pos-c6.md](plans/escala-dry-pos-c6.md) | | C9 Escala e DRY pós-C8 | — (sem design novo; reusa os de C2/C6/C8) | [escala-dry-pos-c8.md](plans/escala-dry-pos-c8.md) | | C10 Escala e DRY pós-C9 | — (sem design novo; reusa os de C2/C6/C8/C9) | [escala-dry-pos-c9.md](plans/escala-dry-pos-c9.md) | +| O0+ Escala e DRY pós-Onda 0 | — (sem design novo; reusa `/privacidade` + padrão revalidate `posts`) | [escala-dry-pos-onda0.md](plans/escala-dry-pos-onda0.md) | | B5 Escala e DRY pós-B2 | — (sem design novo; reusa geometrias B2 + padrões de scripts CLI) | [escala-dry-pos-b2.md](plans/escala-dry-pos-b2.md) | | D2 Notificações (sino) | `Notificacoes-PWA` (central de notificações) | [notifications.md](plans/notifications.md) | | D1 PWA `/campanha` | `Notificacoes-PWA` (banner "Instale o app da campanha") | [pwa-campanha.md](plans/pwa-campanha.md) | @@ -126,6 +129,7 @@ flowchart TD subgraph Onda0["Onda 0 — produção"] JUR["Lote jurídico único
(4 Consent keys + aviso privacidade)"] DEPLOY["Checklist deploy + lançamento MVP"] + O0DRY["O0+ Escala e DRY pós-Onda 0
(cache / chaves / testes SQL)"] end subgraph TrilhaA["Trilha A — dados eleitorais e território"] @@ -203,6 +207,7 @@ flowchart TD C9 --> C10 D1 --> D2 JUR -.chave de push.-> D2 + DEPLOY -.engenharia entregue.-> O0DRY A4 --> E2 E1 --> E4 E3 --> E4 @@ -213,7 +218,7 @@ flowchart TD A7 -.mesmo loader do baseline.-> E2 ``` -Itens sem seta de entrada (**paralelizáveis a qualquer momento**): E1, E3 e fill-ins (visitados recentemente, listas globais, reset de senha, higiene PascalCase). **Entregues e mesclados em `main` em 2026-07-18/19** (✓ no grafo): A1, A2, A3, A4, B1, B2, C1, C2, C3, C6, C8, C9 e D1 — ver "Ciclo 2" e "Ciclo 2+" acima. C2 segue com produção bloqueada pelo lote jurídico (Onda 0: Consent keys `apoiador-cadastro` / `apoiador-intencao-voto` + aprovação). Destravados por eles: A5/A6 (após A4 ✓), A7 (após A4 ✓; preferencialmente antes ou com A5/B3/E2), E2 (após A4 ✓), B3 (B1 ✓ + B2 ✓; suave: A4 ✓ para coroplético e B5 F1 para lazy load), B5 (após B2; preferencialmente com B3), C4 (A1 + C3 prontos), C7 (após C3; suave: C6 F2 ✓), C10 (após C9 ✓; cortável se a base nominal permanecer pequena) e D2 push (D1 pronto; falta a chave jurídica). E4 destravará após E1+E3 (campos precisam existir antes do import). +Itens sem seta de entrada (**paralelizáveis a qualquer momento**): E1, E3, **O0+** (após engenharia Onda 0 ✓) e fill-ins (visitados recentemente, listas globais, reset de senha, higiene PascalCase). **Entregues e mesclados em `main` em 2026-07-18/19** (✓ no grafo): A1, A2, A3, A4, B1, B2, C1, C2, C3, C6, C8, C9 e D1 — ver "Ciclo 2" e "Ciclo 2+" acima. C2 segue com produção bloqueada pelo lote jurídico (Onda 0: Consent keys `apoiador-cadastro` / `apoiador-intencao-voto` + aprovação). Destravados por eles: A5/A6 (após A4 ✓), A7 (após A4 ✓; preferencialmente antes ou com A5/B3/E2), E2 (após A4 ✓), B3 (B1 ✓ + B2 ✓; suave: A4 ✓ para coroplético e B5 F1 para lazy load), B5 (após B2; preferencialmente com B3), C4 (A1 + C3 prontos), C7 (após C3; suave: C6 F2 ✓), C10 (após C9 ✓; cortável se a base nominal permanecer pequena) e D2 push (D1 pronto; falta a chave jurídica). E4 destravará após E1+E3 (campos precisam existir antes do import). ### Sequência de execução por janela do calendário @@ -221,7 +226,7 @@ Itens sem seta de entrada (**paralelizáveis a qualquer momento**): E1, E3 e fil | Ordem | Item | Plano | Depende de | Paralelizável com | | ----- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------- | ----------------- | -| 1 | Onda 0 (jurídico em lote + deploy + lançamento) | — | externo | tudo | +| 1 | Onda 0 (textos MVP auto + deploy + hold PII real) | [detalhes](plans/onda-0.md) | engenharia entregue; jurídico final | tudo | | 2 | A1 Território multi-município/bairro **(entregue 2026-07-18)** | [detalhes](plans/territorio-multi-municipio-bairro.md) | — | B1, C1, A3 | | 3 | A2 Zonas TSE + sugestões cruzadas (chips opt-in território↔ZE) **(entregue 2026-07-18)** | [detalhes](plans/zonas-por-municipio.md) | A1 (nasce contra `cities[]`) | B1 ✓, C1 ✓, A3 ✓ | | 4 | B1 Overview da lista de núcleos **(entregue 2026-07-18)** | [detalhes](plans/overview-lista-nucleos.md) | — | A1, A2, C1 | @@ -230,18 +235,18 @@ Itens sem seta de entrada (**paralelizáveis a qualquer momento**): E1, E3 e fil **Janela 2 — 05/08 → 16/08 (pré-propaganda): base nominal + inteligência + agenda prontas para o arranque.** -| Ordem | Item | Plano | Depende de | Paralelizável com | -| ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ---------------------------------- | --------------------- | -| 7 | C2 Cadastro nominal de apoiadores _(engenharia pronta e mesclada em `main` 2026-07-18 — deploy + Consent keys ainda pendentes)_ | [detalhes](plans/cadastro-nominal-apoiadores.md) | produção: lote jurídico (Onda 0) | A4, C3, C6, C7 | -| 8 | C6 Escala e DRY pós-C2 (import em massa, KPI, shells compartilhados com núcleos) **(implementado e mesclado em `main` 2026-07-19 — Fases 1–5 + simplify)** | [detalhes](plans/escala-dry-pos-c2.md) | C2 (merge) | A4, C3, C7, C8 | -| 9 | A4 Baseline no produto + insight Gap vs 2022 **(implementado e mesclado em `main` 2026-07-18 — Fases 2–4 + simplify)** | [detalhes](plans/baseline-eleitoral-tse.md) | A3 + B1 (suave: A2) | C2, C3, C6, C7, C8 | -| 10 | C3 Eventos / agenda de mobilização (`actionPlan`) **(implementado e mesclado em `main` 2026-07-18)** | [detalhes](plans/eventos-agenda-mobilizacao.md) | A1 | C2, A4, C6, C7, C8 | -| 11 | C7 Escala e DRY pós-C3 _(parcial 2026-07-19: FormData território, contadores lista, locks, `contactSearchQuery`; pendente: composição territorial, shell de contato, leituras por aba, short-circuit de hooks)_ | [detalhes](plans/escala-dry-pos-c3.md) | C3 (suave: C6 F2) | A4, C6, C8, E1+E3, E2 | -| 12 | C8 Escala e DRY pós-C6 (locks bulk em 1 RT, leituras/`pg_trgm`, helpers drizzle, forms DRY) **(implementado e mesclado em `main` 2026-07-19 — Fases 1–4 + simplify)** _(cortável se a base nominal permanecer pequena)_ | [detalhes](plans/escala-dry-pos-c6.md) | C6 (merge) | A4, C3, C7, E1+E3, E2, C9 | +| Ordem | Item | Plano | Depende de | Paralelizável com | +| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ---------------------------------- | -------------------------- | +| 7 | C2 Cadastro nominal de apoiadores _(engenharia pronta e mesclada em `main` 2026-07-18 — deploy + Consent keys ainda pendentes)_ | [detalhes](plans/cadastro-nominal-apoiadores.md) | produção: lote jurídico (Onda 0) | A4, C3, C6, C7 | +| 8 | C6 Escala e DRY pós-C2 (import em massa, KPI, shells compartilhados com núcleos) **(implementado e mesclado em `main` 2026-07-19 — Fases 1–5 + simplify)** | [detalhes](plans/escala-dry-pos-c2.md) | C2 (merge) | A4, C3, C7, C8 | +| 9 | A4 Baseline no produto + insight Gap vs 2022 **(implementado e mesclado em `main` 2026-07-18 — Fases 2–4 + simplify)** | [detalhes](plans/baseline-eleitoral-tse.md) | A3 + B1 (suave: A2) | C2, C3, C6, C7, C8 | +| 10 | C3 Eventos / agenda de mobilização (`actionPlan`) **(implementado e mesclado em `main` 2026-07-18)** | [detalhes](plans/eventos-agenda-mobilizacao.md) | A1 | C2, A4, C6, C7, C8 | +| 11 | C7 Escala e DRY pós-C3 _(parcial 2026-07-19: FormData território, contadores lista, locks, `contactSearchQuery`; pendente: composição territorial, shell de contato, leituras por aba, short-circuit de hooks)_ | [detalhes](plans/escala-dry-pos-c3.md) | C3 (suave: C6 F2) | A4, C6, C8, E1+E3, E2 | +| 12 | C8 Escala e DRY pós-C6 (locks bulk em 1 RT, leituras/`pg_trgm`, helpers drizzle, forms DRY) **(implementado e mesclado em `main` 2026-07-19 — Fases 1–4 + simplify)** _(cortável se a base nominal permanecer pequena)_ | [detalhes](plans/escala-dry-pos-c6.md) | C6 (merge) | A4, C3, C7, E1+E3, E2, C9 | | 13 | C9 Escala e DRY pós-C8 (filtro unificado Payload↔SQL, `contactSearchQuery` no aggregate, loader único `/apoiadores`, forms restantes) **(implementado 2026-07-19 — Fases 1–4 + simplify)** _(cortável se a base nominal permanecer pequena)_ | [detalhes](plans/escala-dry-pos-c8.md) | C8 (merge) | A4, C3, C7, E1+E3, E2, C10 | -| 14 | C10 Escala e DRY pós-C9 (dedup access lista, scope único de núcleos do coordenador, `errorProps` nos forms) _(cortável se a base nominal permanecer pequena; Fase 3 DRY pode entrar isolada)_ | [detalhes](plans/escala-dry-pos-c9.md) | C9 (merge) | A4, C3, C7, E1+E3, E2 | -| 15 | E1 Metas em cenários + prioridade + E3 dobradinhas/encaminhamentos manuais (migration compartilhada em `electoralNucleus`) | [detalhes](plans/mapa-projecao-municipios.md) | — | tudo da janela | -| 16 | E2 Série histórica TSE 2014/2018 + tendência automática (seed por ano; sem migration) | [detalhes](plans/mapa-projecao-municipios.md) | A4 ✓ (suave: A7 F1 — mesmo loader) | E1+E3, C7, C8, C9, C10 | +| 14 | C10 Escala e DRY pós-C9 (dedup access lista, scope único de núcleos do coordenador, `errorProps` nos forms) _(cortável se a base nominal permanecer pequena; Fase 3 DRY pode entrar isolada)_ | [detalhes](plans/escala-dry-pos-c9.md) | C9 (merge) | A4, C3, C7, E1+E3, E2 | +| 15 | E1 Metas em cenários + prioridade + E3 dobradinhas/encaminhamentos manuais (migration compartilhada em `electoralNucleus`) | [detalhes](plans/mapa-projecao-municipios.md) | — | tudo da janela | +| 16 | E2 Série histórica TSE 2014/2018 + tendência automática (seed por ano; sem migration) | [detalhes](plans/mapa-projecao-municipios.md) | A4 ✓ (suave: A7 F1 — mesmo loader) | E1+E3, C7, C8, C9, C10 | **Janela 3 — 16/08 → set (campanha de rua): inteligência ampliada, visualização e engajamento.** @@ -267,12 +272,13 @@ Itens sem seta de entrada (**paralelizáveis a qualquer momento**): E1, E3 e fil **Fill-ins (qualquer janela, quando houver folga; nenhum bloqueia nada):** +- **O0+ Escala e DRY pós-Onda 0** (revalidate globals, chaves Consent, testes SQL provision, DRY Lexical/layout) — engenharia pós-MVP; paralelizável com smoke Onda 0. → [detalhes](plans/escala-dry-pos-onda0.md) - **Visitados recentemente** (client-side, `localStorage`). → [detalhes](plans/visitados-recentemente.md) - **Listas globais** (lideranças, atualizações, territórios no nível raiz). _(plano MVP; sem plano detalhado ainda)_ - **Reset de senha self-service + foto de perfil** (UX adiada do ciclo 1). _(plano MVP)_ - **Higiene de código:** varredura PascalCase dos componentes legados. _(notebook Núcleos)_ -**Cortes seguros se o prazo apertar** (nesta ordem): E5 Salvador por bairro (já registrado como futuro), B4 camada de zonas, B5 F2 (cache CLI — duplicação pequena), E4 import da planilha (os campos E1/E3 podem ser preenchidos à mão pela coordenação; o import só acelera), A7 F2–F3 (`cityCode` / Alert DRY — só se a geografia real dos núcleos permanecer 1–2 municípios), E2 série 2014/2018 (reduzível a só 2018 se o formato TSE de 2014 atrasar; a tendência vira 2 pontos), B3 mapa Leaflet (preferir levar B5 F1 lazy load junto se B3 entrar), C4 demandas, D2 push (mantendo o sino), C6 fases 3–5 (import bulk / preview token / KPI — só se a base nominal permanecer pequena), C7 fases 4–5 restantes (leituras por aba / short-circuit de hooks / feed O(n) — só se a agenda permanecer pequena; contadores + locks já entregues), C8 (escala/DRY pós-C6 — só se a base nominal permanecer pequena; Fases 3–4 DRY podem entrar isoladas), C9 (escala/DRY pós-C8 — só se a base nominal permanecer pequena; Fases 3–4 DRY de forms podem entrar isoladas), C10 (escala/DRY pós-C9 — só se a base nominal permanecer pequena; Fase 3 `errorProps` pode entrar isolada; Fase 1 access dedup é a mais valiosa se coordenadores usarem `/apoiadores` com frequência), fill-ins (D1 PWA já entregue, saiu da lista de cortes). Preferir manter C6 fases 1–2, C7 fases 1–2, C8 fases 1–2 (perf import/lista), C9 Fase 1 (filtro unificado — evita drift KPI/lista), C10 Fase 1 (access dedup na lista) e **A7 F1** (agregar federal no detalhe) se A5/B3/E2 forem entrar — o custo de I/O do loader do A4 é herdado por eles. **Não cortáveis:** Onda 0 (jurídico/Consent), C2 cadastro de apoiadores, C3 eventos/agenda, A4 baseline + gap, **E1+E3 metas/prioridade/estratégia manual** — são respectivamente o risco legal, a base de dados, a operação da propaganda, o instrumento de alocação de esforço e a paridade mínima com a planilha que a campanha já usa. +**Cortes seguros se o prazo apertar** (nesta ordem): E5 Salvador por bairro (já registrado como futuro), B4 camada de zonas, B5 F2 (cache CLI — duplicação pequena), E4 import da planilha (os campos E1/E3 podem ser preenchidos à mão pela coordenação; o import só acelera), A7 F2–F3 (`cityCode` / Alert DRY — só se a geografia real dos núcleos permanecer 1–2 municípios), E2 série 2014/2018 (reduzível a só 2018 se o formato TSE de 2014 atrasar; a tendência vira 2 pontos), B3 mapa Leaflet (preferir levar B5 F1 lazy load junto se B3 entrar), C4 demandas, D2 push (mantendo o sino), C6 fases 3–5 (import bulk / preview token / KPI — só se a base nominal permanecer pequena), C7 fases 4–5 restantes (leituras por aba / short-circuit de hooks / feed O(n) — só se a agenda permanecer pequena; contadores + locks já entregues), C8 (escala/DRY pós-C6 — só se a base nominal permanecer pequena; Fases 3–4 DRY podem entrar isoladas), C9 (escala/DRY pós-C8 — só se a base nominal permanecer pequena; Fases 3–4 DRY de forms podem entrar isoladas), C10 (escala/DRY pós-C9 — só se a base nominal permanecer pequena; Fase 3 `errorProps` pode entrar isolada; Fase 1 access dedup é a mais valiosa se coordenadores usarem `/apoiadores` com frequência), **O0+ Fase 4–5** (Lexical/layout shell e Footer condicional — Fases 1–3 preferíveis antes de tráfego real em `/privacidade`), fill-ins (D1 PWA já entregue, saiu da lista de cortes). Preferir manter C6 fases 1–2, C7 fases 1–2, C8 fases 1–2 (perf import/lista), C9 Fase 1 (filtro unificado — evita drift KPI/lista), C10 Fase 1 (access dedup na lista), **O0+ Fase 1** (revalidate globals pós-migration) e **A7 F1** (agregar federal no detalhe) se A5/B3/E2 forem entrar — o custo de I/O do loader do A4 é herdado por eles. **Não cortáveis:** Onda 0 (jurídico/Consent), C2 cadastro de apoiadores, C3 eventos/agenda, A4 baseline + gap, **E1+E3 metas/prioridade/estratégia manual** — são respectivamente o risco legal, a base de dados, a operação da propaganda, o instrumento de alocação de esforço e a paridade mínima com a planilha que a campanha já usa. ### Itens consolidados/removidos nesta revisão (2026-07-17) @@ -285,20 +291,20 @@ Itens sem seta de entrada (**paralelizáveis a qualquer momento**): E1, E3 e fil ## Bloqueadores atuais -| Item | Status | Fonte | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------- | -| 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 | +| Item | Status | Fonte | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------- | +| Lote jurídico único LGPD (4 textos de `Consent` + Aviso de Privacidade + avaliação de RIPD) — textos **provisórios MVP** já provisionados por migration ([onda-0.md](plans/onda-0.md)); substituição final + liberação de PII real aguarda assessoria | **caminho crítico para dados reais** | notebook Núcleos; AGENTS checklist; planos cadastro-nominal, notifications e onda-0 | +| 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 | ## Site público -**Já entregue:** sistema de notícias/publicações (`post`/`tag`), listagens e artigos, seed a partir de jorgesolla.com.br, cache com tag `posts`. +**Já entregue:** sistema de notícias/publicações (`post`/`tag`), listagens e artigos, seed a partir de jorgesolla.com.br, cache com tag `posts`; **página `/privacidade`** via global `privacy-policy` (texto provisório Onda 0 — revisão jurídica pendente). **Próximos** (não bloqueiam `/campanha`, exceto a política de privacidade, que entra no lote jurídico da Onda 0): -- Página de política de privacidade institucional (LGPD) — **prioridade alta**, sai do lote jurídico da Onda 0. _(plano-arquitetura §3.1)_ +- Página de política de privacidade institucional (LGPD) — **MVP entregue** (`/privacidade`); textos finais e polish pós-simplify (revalidate globals, DRY Lexical) em [escala-dry-pos-onda0.md](plans/escala-dry-pos-onda0.md). _(plano-arquitetura §3.1)_ - Modelar e popular `Pages` para conteúdo institucional (biografia, mandato, propostas). _(AGENTS Known Gap #3; plano-arquitetura §3)_ - Tornar editáveis título/subtítulo (e demais textos) da home via global/`Pages`. _(AGENTS Known Gap #3)_ - Agenda pública e multimídia: preferir links oficiais (Câmara, YouTube, Flickr). _(plano-arquitetura §3.1)_ diff --git a/package.json b/package.json index 6494f862..86bcad93 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "db:pull": "node scripts/db-pull.mjs", "db:seed:posts": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm --import=./scripts/seed-loader.mjs\" node scripts/seed-posts.mjs", "db:seed:tse": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm --import=./scripts/seed-loader.mjs\" node scripts/seed-tse-results.mjs", + "db:seed:consent": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm --import=./scripts/seed-loader.mjs\" node scripts/seed-consent.mjs", "build:geometries": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm\" node scripts/build-bahia-geometries.mjs", "dev": "node scripts/guard-dev-db.mjs && cross-env NODE_OPTIONS=--no-deprecation next dev", "devsafe": "rm -rf .next && node scripts/guard-dev-db.mjs && cross-env NODE_OPTIONS=--no-deprecation next dev", diff --git a/scripts/consent-texts/README.md b/scripts/consent-texts/README.md new file mode 100644 index 00000000..e9aa780a --- /dev/null +++ b/scripts/consent-texts/README.md @@ -0,0 +1,10 @@ +# Consent texts (Onda 0) + +Canonical source: [`src/lib/onda0ConsentTexts.ts`](../../src/lib/onda0ConsentTexts.ts). + +Provisioning runs via: + +- Migration `20260719_054707_seed_onda0_consent_and_privacy` (local + produção no deploy) +- CLI `pnpm db:seed:consent` (re-aplicar após editar os textos) + +Keys: `lideranca-autopreenchimento`, `apoiador-cadastro`, `apoiador-intencao-voto`, `campanha-notificacoes-push`, plus Global `privacy-policy`. diff --git a/scripts/seed-consent.mjs b/scripts/seed-consent.mjs new file mode 100644 index 00000000..266feec9 --- /dev/null +++ b/scripts/seed-consent.mjs @@ -0,0 +1,40 @@ +/** + * Re-applies Onda 0 provisional Consent keys and privacy-policy global. + * Idempotent upsert — same logic as migration 20260719_054707_seed_onda0_consent_and_privacy. + * + * Safety: refuses non-local DATABASE_URL unless ALLOW_REMOTE_DB=true. + * + * Usage: + * pnpm db:seed:consent + * ALLOW_REMOTE_DB=true pnpm db:seed:consent + */ + +import { config as loadEnv } from 'dotenv' +import { getPayload } from 'payload' + +import { assertLocalDatabase } from './assert-local-database.mjs' + +loadEnv({ path: '.env.local' }) +loadEnv({ path: '.env' }) + +assertLocalDatabase( + 'seed:consent', + 'This script upserts provisional Consent/privacy texts. Use migrations for prod deploy.', +) + +const config = (await import('../src/payload.config.ts')).default +const { provisionOnda0ConsentAndPrivacy } = await import('../src/utilities/onda0Provision.ts') + +const payload = await getPayload({ config }) + +try { + await provisionOnda0ConsentAndPrivacy(payload) + console.log('[seed:consent] OK — provisional Consent keys and privacy-policy global updated.') +} catch (error) { + console.error(`\n[seed:consent] ${error instanceof Error ? error.message : String(error)}\n`) + process.exit(1) +} finally { + if (typeof payload.db?.destroy === 'function') { + await payload.db.destroy() + } +} diff --git a/src/app/(frontend)/privacidade/layout.tsx b/src/app/(frontend)/privacidade/layout.tsx new file mode 100644 index 00000000..10d0b0fd --- /dev/null +++ b/src/app/(frontend)/privacidade/layout.tsx @@ -0,0 +1,13 @@ +import { Footer } from '@/components/Footer' +import { SiteHeader } from '@/components/SiteHeader' +import type { ReactNode } from 'react' + +export default function PrivacyLayout({ children }: { children: ReactNode }) { + return ( +
+ +
{children}
+
+ ) +} diff --git a/src/app/(frontend)/privacidade/page.tsx b/src/app/(frontend)/privacidade/page.tsx new file mode 100644 index 00000000..6aed56fa --- /dev/null +++ b/src/app/(frontend)/privacidade/page.tsx @@ -0,0 +1,41 @@ +import { getCachedGlobal } from '@/utilities/globals' +import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html' +import type { Metadata } from 'next' +import { notFound } from 'next/navigation' + +const getCachedPrivacyPolicy = getCachedGlobal('privacy-policy') + +export async function generateMetadata(): Promise { + const policy = await getCachedPrivacyPolicy() + if (!policy.published) return {} + + return { + title: 'Política de Privacidade | Jorge Solla', + description: + 'Aviso de Privacidade e tratamento de dados pessoais da campanha Jorge Solla (plataforma Teqo).', + } +} + +export default async function PrivacyPolicyPage() { + const policy = await getCachedPrivacyPolicy() + + if (!policy.published) { + notFound() + } + + const html = convertLexicalToHTML({ data: policy.body }) + + return ( +
+
+

+ Política de Privacidade +

+
+
+
+ ) +} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 71e38bcd..d941a5eb 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -1,7 +1,12 @@ +import Link from 'next/link' + import { SocialLinks } from './socialLinks' export const Footer = () => ( -