Document coordenador geral interview and register A10 plus WhatsApp D3–D5.
Capture field evidence from the anchor session, update CUSTOMER/roadmap plans, and add vote-scenario and internal WhatsApp bridge specs with interview audio and transcripts.
This commit is contained in:
parent
d93e2aa72f
commit
21228dc022
12 changed files with 679 additions and 78 deletions
|
|
@ -7,6 +7,8 @@ alwaysApply: false
|
|||
|
||||
## Status
|
||||
|
||||
- **Programa WhatsApp interno D3–D5 registrado (2026-07-23):** comunicação 1:1 staff↔lideranças via bridge não oficial (não WABA / não massa); **número pessoal** de cada assessor/CG com pareamento QR no app (parece chat pessoal — pedido produto); fatias D3 multi-sessão → D4 envio pela sessão do ator → D5 inbox da própria sessão→rascunhos; baixa/média prioridade; Consent `campanha-whatsapp-canal`. Plano-mestre: [`whatsapp-interno-campanha.md`](../../../docs/plans/whatsapp-interno-campanha.md).
|
||||
- **A10 registrado no roadmap (2026-07-23):** cenários pessimista/média/otimista nas estimativas staff (`votePledge` + `plaza.expectedVotes` + lista/mapa); liderança continua com um `declaredVotes`; E8 passa a depender de A10. Plano: [`cenarios-estimativa-votos.md`](../../../docs/plans/cenarios-estimativa-votos.md). Fonte: feedback coordenação geral (áudio em `docs/general-coordinator-interview/`).
|
||||
- **Discovery "Inteligência de campanha" aprovado (2026-07-21):** ciclo literatura→persona→entrevista sintética concluído e colaborado; embasamento canônico em [`docs/research/`](../../../docs/research/) (compêndio ~67 fontes, persona, relatório com kernel Rumelt + playbook de 25 padrões dado→decisão P/T/I/J/K + anti-goals). Programa de produto registrado no roadmap como **E8–E15 + B13 + C12** ([plano-mestre `inteligencia-campanha.md`](../../../docs/plans/inteligencia-campanha.md)) — conta da cadeira, fila de alocação, registro-fundação (trajetória de pledge/sinais/decisões), classificação relativa, escala relativa no mapa, níveis N0–N4, motor de sugestões (humano no loop), camada TI, planejador de giros, backtest. Decisões de produto travadas: % estadual absoluto é anti-métrica; denominadores usam válidos projetados (nunca aptos); sugestão nunca decide sozinha; nível de envolvimento não vaza para fora do staff.
|
||||
- **B12 entregue em código (2026-07-21; deploy pendente com remodelagem):** `fitToKeys` + `interactiveKeys` no Mapa das Praças (`plazasByIbgeCode`); `canonicalMapKeysKey`; hover stroke-only (fill inalterado); clear síncrono no mouseout; fit ao footprint filtrado sem re-zoom em Ano/Escala. Plano: [`aproximar-mapa-pracas.md`](../../../docs/plans/aproximar-mapa-pracas.md).
|
||||
- **B6 entregue em código (2026-07-21; deploy pendente com remodelagem):** `BahiaMap` setStyle incremental — layer GeoJSON estável entre troca de ano/métrica/escala; `pathByKeyRef` + restyle O(2) no hover/select; `fitBounds` só em `mode`/`highlightKeys`; helpers puros em `bahiaMapStyle.ts` + unit `bahiaMapStyle.unit.spec.ts`. Plano: [`escala-dry-pos-b3.md`](../../../docs/plans/escala-dry-pos-b3.md). Fecha débitos de perf de **B10** e **B11**.
|
||||
|
|
@ -66,6 +68,8 @@ alwaysApply: false
|
|||
|
||||
## Decisões
|
||||
|
||||
- Em 2026-07-23, **WhatsApp interno 1:1 (D3–D5):** bridge não oficial; **sessão = número pessoal** de cada `coordinator`/`advisor` (QR no `/campanha`, estilo WhatsApp Web); liderança recebe no chat de sempre; Teqo = system of record; humano no loop em D5; sidecar multi-sessão. **Rejeitado:** WABA; massa; chip institucional único; auto-commit; inbox cruzado CG↔assessores na v1. Cortável (`wa.me`).
|
||||
- Em 2026-07-23, **estimativas staff em três cenários (A10):** pessimista / média (`central`) / otimista no pledge e no total da Praça; liderança declara um valor; default de leitura = média; não fundir com `voteGoals`. **Rejeitado:** um único estimado; expor faixa à liderança; três choropleths. Desbloqueia E8.
|
||||
- Em 2026-07-21, **embasamento de produto/domínio via `docs/research/`:** decisões de análise territorial e mapa citam o relatório de discovery (aprovado 2026-07-21) antes de inventar métrica nova. Regras derivadas: leitura relativa (quantis/LQ/rank/captura) sobre % absoluto; limiares defesa/ataque 35/20/10 degeneram para DF e serão recalibrados com âncoras relativas (E10); roll-off (brancos+nulos DF − majoritária) é o headroom de orientação; abstenção estrutural corrige denominador (válidos projetados) e não é mercado; agregação regional default = Território de Identidade com salvaguardas MAUP (praça manda no voto, TI na logística). **Rejeitado:** cartogramas; pesquisa estadual de intenção para DF; persuasão de território hostil como funcionalidade.
|
||||
- Em 2026-07-21, **toolchain TypeScript dual:** `typescript` → `@typescript/typescript6@6.0.2` (API/editor/ESLint + `tsc6`); `typescript-7` → `typescript@7.0.2` (Go `tsc` para `pnpm typecheck`). Motivo: typescript-eslint peer `<6.1.0` e API estável só em TS ~7.1. Não usar `@typescript/native-preview`/`tsgo` (canal nightly). `typecheck:tsc6` existe para paridade. Ambient CSS/SCSS em `src/css-modules.d.ts` (TS6 `noUncheckedSideEffectImports`).
|
||||
- Em 2026-07-21, **decisão de produto (Field Desk):** em `/campanha`, superfícies staff de visualização que mostram dados mutáveis devem oferecer edição no contexto quando o access permitir (“edit where you see”) — princípio 3 de [`PRODUCT.md`](../../../PRODUCT.md); regra Cursor [`.cursor/rules/campanha-edit-where-you-see.mdc`](../campanha-edit-where-you-see.mdc). Precedente: **B9** ([`edicao-rapida-lista-pracas.md`](../../../docs/plans/edicao-rapida-lista-pracas.md) — `PlazaList*Control` + `plazaStaffFormActions`). **Rejeitado:** lista/detalhe só-leitura com toda mutação em `/editar`; data-grid / planilha full-row; service genérico de “quick edit”. `/editar` e Sheets longos ficam para formulários multi-campo, campos raros e fluxos com nota/confirmação. Liderança continua no escopo já definido (sem campos staff-only).
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -56,6 +56,9 @@ next-env.d.ts
|
|||
# IBGE demographics download cache (pnpm build:demographics)
|
||||
/data/demographics/
|
||||
|
||||
# Transcription intermediates (source audio lives under docs/)
|
||||
output/**/*.wav
|
||||
|
||||
# Playwright
|
||||
node_modules/
|
||||
/test-results/
|
||||
|
|
|
|||
|
|
@ -16,40 +16,44 @@
|
|||
|
||||
## Phase Status
|
||||
|
||||
| Phase | Skill | Status | Artifact | Date |
|
||||
| ----- | ------------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------- | ---------- |
|
||||
| 1 | jobs-to-be-done | done | CUSTOMER.md | 2026-07-19 |
|
||||
| 2 | ux-heuristics | deferred: após primeiros testes de quarta (~2026-07-22) — auditoria expert sem usuários seria especulação | DESIGN.md, EXPERIMENTS.md | 2026-07-19 |
|
||||
| 3 | design-everyday-things | pending | DESIGN.md, EXPERIMENTS.md | |
|
||||
| 4 | refactoring-ui | pending | DESIGN.md, EXPERIMENTS.md | |
|
||||
| 5 | microinteractions | pending | DESIGN.md, EXPERIMENTS.md | |
|
||||
| 6 | made-to-stick | pending | POSITIONING.md, EXPERIMENTS.md | |
|
||||
| 7 | influence-psychology | skipped: sem paywall/upgrade/trial in-app | — | 2026-07-19 |
|
||||
| 8 | high-perf-browser | pending | DESIGN.md, EXPERIMENTS.md | |
|
||||
| 9 | steve-jobs-design-review | pending | PRODUCT.md, DESIGN.md, EXPERIMENTS.md | |
|
||||
| opt | continuous-discovery | done (cadência + roteiro; evidência chega ~2026-07-22) | CUSTOMER.md | 2026-07-19 |
|
||||
| opt | improve-retention | deferred: após Fase 1 + primeiros testes — risco é Little Hire | PRODUCT.md | |
|
||||
| Phase | Skill | Status | Artifact | Date |
|
||||
| ----- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------- | ---------- |
|
||||
| 1 | jobs-to-be-done | done | CUSTOMER.md | 2026-07-19 |
|
||||
| 2 | ux-heuristics | pending — destravada 2026-07-23 (evidência da sessão âncora em CUSTOMER.md; Bloco B de uso observado ainda sem registro) | DESIGN.md, EXPERIMENTS.md | 2026-07-23 |
|
||||
| 3 | design-everyday-things | pending | DESIGN.md, EXPERIMENTS.md | |
|
||||
| 4 | refactoring-ui | pending | DESIGN.md, EXPERIMENTS.md | |
|
||||
| 5 | microinteractions | pending | DESIGN.md, EXPERIMENTS.md | |
|
||||
| 6 | made-to-stick | pending | POSITIONING.md, EXPERIMENTS.md | |
|
||||
| 7 | influence-psychology | skipped: sem paywall/upgrade/trial in-app | — | 2026-07-19 |
|
||||
| 8 | high-perf-browser | pending | DESIGN.md, EXPERIMENTS.md | |
|
||||
| 9 | steve-jobs-design-review | pending | PRODUCT.md, DESIGN.md, EXPERIMENTS.md | |
|
||||
| opt | continuous-discovery | done (cadência + roteiro; evidência chega ~2026-07-22) | CUSTOMER.md | 2026-07-19 |
|
||||
| opt | improve-retention | deferred: após Fase 1 + primeiros testes — risco é Little Hire | PRODUCT.md | |
|
||||
|
||||
Statuses: pending · in-progress · awaiting-evidence · done · deferred: \<reason\> · skipped: \<reason\>
|
||||
|
||||
## Key Decisions
|
||||
|
||||
| Date | Phase | Decision | Rationale |
|
||||
| ---------- | ------ | ------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| 2026-07-19 | Intake | Escopo = `/campanha` (não site público) | Job e atrito são de operação de campanha |
|
||||
| 2026-07-19 | Intake | Fase 7 skipped | Sem superfícies de upsell in-app |
|
||||
| 2026-07-19 | Intake | continuous-discovery opcional recomendada | Evidência = nenhuma; testes humanos esta semana |
|
||||
| 2026-07-19 | Intake | improve-retention deferred pós-Fase 1 | Sintoma clássico de abandono por dados defasados; método completo depois do job + evidência |
|
||||
| 2026-07-19 | Intake | Plano de fases confirmado | Inclui `docs/roadmap.md` como registro vivo de ideias/pendências |
|
||||
| 2026-07-19 | Intake | Roadmap = backlog de build | Findings deste journey → EXPERIMENTS + itens/planos no roadmap quando forem para build |
|
||||
| 2026-07-19 | 1 | Job = campanha dep. federal pela Bahia | Abrangência estadual; cargo federal |
|
||||
| 2026-07-19 | 1 | Pior dimensão = emocional | Confiança no quadro atualizado; risco de ansiedade com dados mortos |
|
||||
| 2026-07-19 | 1 | Leak = Little Hire | Big Hire coberto por onboarding presencial + seed de dados iniciais |
|
||||
| 2026-07-19 | 2 | Fase 2 deferred | Esperar testes com coordenador geral + assistente (~quarta) antes da auditoria heurística |
|
||||
| 2026-07-19 | opt CD | Outcome = Little Hire até 16/08 | Sinal: ≥1 update espontâneo em 7 dias pós-quarta |
|
||||
| 2026-07-19 | opt CD | Quarta = Mom Test 25 + observe 25 | História do passado + primeira sessão sem pitch |
|
||||
| 2026-07-19 | opt CD | Cadência presencial semanal + remoto ad hoc | Coordenador geral disponível remotamente quando necessário |
|
||||
| 2026-07-19 | opt CD | Sala = product + onboarding lead + CG | CG é participante; interviewers = product + onboarding |
|
||||
| Date | Phase | Decision | Rationale |
|
||||
| ---------- | ------ | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 2026-07-19 | Intake | Escopo = `/campanha` (não site público) | Job e atrito são de operação de campanha |
|
||||
| 2026-07-19 | Intake | Fase 7 skipped | Sem superfícies de upsell in-app |
|
||||
| 2026-07-19 | Intake | continuous-discovery opcional recomendada | Evidência = nenhuma; testes humanos esta semana |
|
||||
| 2026-07-19 | Intake | improve-retention deferred pós-Fase 1 | Sintoma clássico de abandono por dados defasados; método completo depois do job + evidência |
|
||||
| 2026-07-19 | Intake | Plano de fases confirmado | Inclui `docs/roadmap.md` como registro vivo de ideias/pendências |
|
||||
| 2026-07-19 | Intake | Roadmap = backlog de build | Findings deste journey → EXPERIMENTS + itens/planos no roadmap quando forem para build |
|
||||
| 2026-07-19 | 1 | Job = campanha dep. federal pela Bahia | Abrangência estadual; cargo federal |
|
||||
| 2026-07-19 | 1 | Pior dimensão = emocional | Confiança no quadro atualizado; risco de ansiedade com dados mortos |
|
||||
| 2026-07-19 | 1 | Leak = Little Hire | Big Hire coberto por onboarding presencial + seed de dados iniciais |
|
||||
| 2026-07-19 | 2 | Fase 2 deferred | Esperar testes com coordenador geral + assistente (~quarta) antes da auditoria heurística |
|
||||
| 2026-07-19 | opt CD | Outcome = Little Hire até 16/08 | Sinal: ≥1 update espontâneo em 7 dias pós-quarta |
|
||||
| 2026-07-19 | opt CD | Quarta = Mom Test 25 + observe 25 | História do passado + primeira sessão sem pitch |
|
||||
| 2026-07-19 | opt CD | Cadência presencial semanal + remoto ad hoc | Coordenador geral disponível remotamente quando necessário |
|
||||
| 2026-07-19 | opt CD | Sala = product + onboarding lead + CG | CG é participante; interviewers = product + onboarding |
|
||||
| 2026-07-23 | opt CD | Sessão âncora realizada e processada | Mom Test 7/7 no Bloco A; evidência em CUSTOMER.md (Interview Evidence + Snapshot); Bloco B perdido no áudio — usabilidade (O2/O3/O4) segue sem evidência |
|
||||
| 2026-07-23 | opt CD | OST: O1/O5 confirmadas; O6–O8 novas | Dossiê pré-agenda, registro datado de deltas de voto, ilhas de comunicação — histórias reais (Cairu, Amélia Rodrigues, Santa Inês, Salvador 10×) |
|
||||
| 2026-07-23 | opt CD | Âncora de prioridade = % do voto do deputado | Derruba % do eleitorado local como critério da mesa; calibra E8/E10/B13 no roadmap |
|
||||
| 2026-07-23 | 2 | Fase 2 destravada (pending) | Evidência real disponível; auditoria heurística deve mirar os fluxos do delta-do-dia e da fila de prioridade |
|
||||
|
||||
## Next Actions
|
||||
|
||||
|
|
@ -57,8 +61,10 @@ Statuses: pending · in-progress · awaiting-evidence · done · deferred: \<rea
|
|||
- [x] Fase 1 → `docs/CUSTOMER.md` (2026-07-19)
|
||||
- [x] Fase 2 deferred até evidência de quarta
|
||||
- [x] continuous-discovery → cadência + OST + roteiro em `CUSTOMER.md` (2026-07-19)
|
||||
- [ ] Rodar sessão de quarta (Mom Test + observe) → preencher Interview Evidence + Snapshot
|
||||
- [ ] Medir sinal Little Hire: ≥1 update espontâneo em 7 dias (sem cobrar)
|
||||
- [ ] Retomar Fase 2 (ux-heuristics) após quarta, com achados reais
|
||||
- [ ] Cruzar findings com `docs/roadmap.md` (FD2, onboarding, Onda 0…)
|
||||
- [x] Rodar sessão âncora (Mom Test) → Interview Evidence + Snapshot preenchidos (2026-07-23; transcrição diarizada em `output/transcribe/general-coordinator-interview-20260723/`)
|
||||
- [ ] Resgatar Bloco B (uso observado) das notas dos entrevistadores — o áudio dessa parte se perdeu; sem ele O2/O3/O4 seguem sem evidência
|
||||
- [ ] Cobrar o compromisso: planilha/tabela de prioridades prometida pelo coordenador geral ("Eu te passo em uma tabela")
|
||||
- [ ] Medir sinal Little Hire: ≥1 update espontâneo até 2026-07-30 (sem cobrar)
|
||||
- [ ] Rodar Fase 2 (ux-heuristics) sobre os fluxos que a entrevista apontou: registrar o delta do dia (O7), fila/prioridade (E9), dossiê da Praça (O6)
|
||||
- [ ] Cruzar findings com `docs/roadmap.md`: entrevista reforça C12 (registro-fundação, não cortável), E9 ("coluna da vergonha" = Salvador 10×), A10 (piso 150k + faixas) e calibra E8/E10/B13 (âncora = % do voto do deputado); registrar gap de dados novo no plano-mestre de inteligência: emendas parlamentares por município (O6)
|
||||
- [ ] Não shipar polish visual (Fase 4) sem finding de Fases 1–3
|
||||
|
|
|
|||
135
docs/plans/cenarios-estimativa-votos.md
Normal file
135
docs/plans/cenarios-estimativa-votos.md
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# Cenários de estimativa de votos (pessimista / média / otimista)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-23
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha A, item A10)
|
||||
Impeccable: B — encaixe em forms/lista/mapa/dashboard já existentes (sem rota nova)
|
||||
Appetite: ~2 dias eng; migration (pledge + praça) + helpers de agregação + seletor de cenário nas superfícies staff
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (princípios 3 Edit where you see, 4 Depth/simplicity, 5 Intelligence) / `DESIGN.md` (register `product`) · tema `data-theme='campaign'` · shells `PledgeEstimateForm`, `PlazaStrategyForm`, `PlazaList*Control`, `PlazaMapPanel`, `StaffPlazaVotesDisplay`.
|
||||
|
||||
Na implementação (`implement-roadmap-item`): craft compacto → critique → polish (três inputs + seletor; sem redesign de lista/mapa).
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** Assessor calibra a faixa realista da liderança sob pressão de campo; Coordenador Geral lê a banda na reunião (chão vs. caso-base vs. teto).
|
||||
- **Job principal:** gravar e ler **três estimativas staff** (pessimista / média / otimista) no pledge e no total da Praça, com a liderança continuando a declarar **um** número.
|
||||
- **Estratégia de cor:** Restrained.
|
||||
- **Edit where you see:** sim — Popover/lista e painel de pledges reusam actions existentes; mapa só troca cenário de leitura (não edita no mapa).
|
||||
- **Anti-goals:** fundir com `voteGoals` (Bom/Regular/Mínimo); expor qualquer cenário à liderança; três choropleths simultâneos; planilha full-row com 3 colunas editáveis sempre montadas.
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
- **Vou apresentar dados?** Sim, superfície neste item (lista, detalhe, mapa 2026, dashboard) + aggregate para E8/E9.
|
||||
- **Decisões desbloqueadas:**
|
||||
- Assessor: “esta liderança fecha o chão pessimista ou só o otimista?” → renegociar pledge / abrir demanda / aceitar risco.
|
||||
- Coordenador: “na banda média a conta da cadeira fecha? e no pessimista?” → alocar recurso da semana vs. aceitar déficit.
|
||||
- Staff no mapa: “onde o cenário médio (ou o chão) está fraco no footprint filtrado?” → priorizar visita/giro.
|
||||
- **Forma escolhida:** (1) **três números + contexto** nos forms de edição; (2) **lista/tabela** com valor do cenário ativo + faixa secundária `pessimista–otimista` quando os três existem; (3) **mapa** com seletor de cenário (default média) reusando o padrão Ano/Escala do `PlazaMapPanel` — um choropleth por vez. **Rejeitado:** fan/range chart; três camadas de mapa ao mesmo tempo; gauge SaaS; fundir rótulos com metas Bom/Regular/Mínimo; % estadual absoluto.
|
||||
- **Profile:** numérico absoluto (votos); granularidade liderança×praça e praça; ~1–N pledges por praça, 436 praças; absoluto (relativo entra em E8/B13).
|
||||
- **Anti-goals de dado:** sem % estadual como KPI; sem confundir cenário de estimativa com meta de planejamento (`voteGoals`); sem mostrar estimativas a `leader`.
|
||||
|
||||
## Contexto
|
||||
|
||||
Hoje a assimetria da remodelagem é 1×1: liderança declara `declaredVotes`; assessor grava um `estimatedVotes` staff-only; total da Praça é um `expectedVotes` (A9) com fallback `expectedVotes ?? (estimatedVotes ?? declaredVotes)`. Feedback da coordenação geral (2026-07-23): **a estimativa do assessor na prática é uma faixa de três cenários** — pessimista, média e otimista — e o mesmo padrão vale para o total da Praça, a lista e o mapa. Só a liderança continua com um único valor declarado.
|
||||
|
||||
`voteGoals` (Bom / Regular / Mínimo) já é triplo, mas é **meta de planejamento**, não estimativa operacional — fundir os dois apagaria a distinção travada em A9/E1.
|
||||
|
||||
E8 (conta da cadeira) ainda não começou e planeja Σ `estimatedVotes ?? declaredVotes`; sem A10, E8 congela a semântica de um cenário só e força retrabalho.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Schema staff-only em `votePledge` e `plaza` com três números nullable (pessimista / média / otimista); `declaredVotes` permanece singular e leader-visible.
|
||||
- Toda superfície staff que hoje lê/escreve estimativa única passa a três cenários (forms, lista, detalhe, mapa 2026, overview/dashboard).
|
||||
- Agregação e fallback **por cenário**, independente: `expected[S] ?? Σ (estimated[S] ?? declared)`.
|
||||
- Default de produto para superfícies de um número (mapa, KPI, cobertura futura): **média (`central`)**.
|
||||
- `leader` nunca lê nenhum dos três (field access + view models) — assimetria da remodelagem intacta.
|
||||
- Guardrails: **uma migration** (pledge + praça); sem Consent; sem collection nova; sem alterar `voteGoals`.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Item A10 próprio (trilha A), não absorver em A9 entregue nem em E8.** Schema + semântica de agregação em N call sites são caros de reverter; E8 consome o resultado. (Pedido coordenação 2026-07-23 + classificação roadmap-item.) **Rejeitado:** “fase 2” informal de A9 sem ID; só UI fake com três inputs gravando o mesmo campo; empurrar para dentro de E8 (mistura meta com estimativa).
|
||||
- **Três campos staff por superfície de estimativa; liderança declara um.** Identificadores: grupo `estimatedVotes` / `expectedVotes` com subcampos `pessimistic` | `central` | `optimistic`; labels pt-BR Pessimista / Média / Otimista. **Rejeitado:** reusar Bom/Regular/Mínimo (`voteGoals`); `low/mid/high` opacos na UI; expor faixa à liderança; `median` no código (implica mediana estatística).
|
||||
- **Backfill na migration: escalar antigo → `central`; pessimista/otimista null.** Preserva o significado do número único já lançado. **Rejeitado:** copiar o mesmo valor nos três (inventa certeza); zerar tudo (apaga trabalho de campo).
|
||||
- **Ordem quando os três estão preenchidos: pessimista ≤ central ≤ otimista** (mesmo espírito de `voteGoals` / `voteGoals.ts`). **Rejeitado:** sem invariante (quebra leitura da faixa); forçar os três sempre preenchidos na v1 (fria adoção).
|
||||
- **Fallback por cenário:** `effective[S] = estimated[S] ?? declared`; total Praça `resolve[S] = expected[S] ?? Σ effective[S]`. Cenário ausente no pledge cai no declarado **daquele** pledge; Praça sem `expected[S]` usa só a soma daquele cenário. **Rejeitado:** “se falta pessimista, usar média” (mistura cenários); um único fallback compartilhado entre S.
|
||||
- **Default de leitura = `central` (média); seletor de cenário no mapa e na lista/overview** (URL ou estado local no padrão Ano/Escala). **Rejeitado:** default otimista (infla a conta); três mapas lado a lado; só tooltip com faixa sem trocar o fill.
|
||||
- **Access:** inalterado — `canRead/ManageCampaignStaffField` nos grupos; leader redacted. **Rejeitado:** só coordinator escreve otimista.
|
||||
- **i18n e naming:** `pessimistic` / `central` / `optimistic`, `VoteEstimateScenario`, `resolvePlazaStaffVoteTotalForScenario`, `aggregatePledgesByPlaza` passa a expor totais por cenário; strings “Pessimista” / “Média” / “Otimista”.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Seletor de cenário na URL (`?estimate=central`) ou só estado local no cliente?** **Opções:** A) query param (shareable, SSR) | B) estado local | C) cookie. **Recomendação:** A no mapa/lista (espelha `year`/`scale` e sobrevive a refresh RSC); default omitido = central. _(assumido — validar com produto)_
|
||||
- **Partial fill: salvar só média?** **Opções:** A) sim, nullable independente | B) exigir os três. **Recomendação:** A — ordem só quando ≥2 preenchidos e comparáveis; UI sugere preencher a faixa.
|
||||
- **KPI do dashboard mostra um número ou a faixa?** **Opções:** A) só média + link | B) `pessimista–otimista` tipográfico | C) três KPIs. **Recomendação:** B (número médio + faixa secundária); C estoura density.
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
mig["migration tri_estimate"] --> pledge["votePledge.estimatedVotes group"]
|
||||
mig --> plaza["plaza.expectedVotes group"]
|
||||
pledge --> agg["votePledgeData por cenário"]
|
||||
plaza --> resolve["resolvePlazaStaffVoteTotalForScenario"]
|
||||
agg --> resolve
|
||||
resolve --> list["PlazaList + overview"]
|
||||
resolve --> map["PlazaMapPanel + ?estimate="]
|
||||
resolve --> dash["dashboard / E8"]
|
||||
forms["PledgeEstimateForm + Plaza*Votes"] --> pledge
|
||||
forms --> plaza
|
||||
leader["DeclareVotesForm"] -.só declared.-> pledge
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`VotePledge` / `Plaza`** (`src/collections/VotePledge.ts`, `Plaza.ts`): substituir scalars por groups com três `number` min 0; access staff nos groups; hook de audit `estimatedAt`/`estimatedBy` quando qualquer subcampo muda; validação de ordem (reusar padrão de `voteGoals.ts` → `voteEstimate.ts`).
|
||||
- **Migration** `pnpm migrate:create add_vote_estimate_scenarios`: criar colunas do group; `UPDATE … SET central = estimated_votes` (e idem `expected_votes`); dropar scalars; sem Consent.
|
||||
- **`src/lib/schemas/votePledge.ts` / `plaza.ts`:** schemas Zod dos três; action `setVotePledgeEstimate` / `setPlazaExpectedVotes` passam a aceitar o trio (nullable).
|
||||
- **`src/utilities/votePledgeData.ts`:** `PlazaPledgeAggregate` ganha `effectiveByScenario` + `declaredTotal`; `resolvePlazaStaffVoteTotal` vira variante por cenário; `rollupPlazaStaffVotes` default `central`. Depth: **não** criar `VoteEstimateService` — estender o módulo profundo existente.
|
||||
- **`plazaMapData.ts` + `PlazaMapPanel`:** bundle 2026 indexado por cenário (ou recompute client-side a partir de três maps); seletor “Cenário” ao lado de Ano/Escala; readout mostra valor do cenário + faixa se completa.
|
||||
- **UI:** `PledgeEstimateForm` três inputs; `PlazaStrategyForm` / `PlazaListExpectedVotesControl` trio (Popover pode empilhar 3 campos); `StaffPlazaVotesDisplay` / `PlazaPledgesPanel` / `PlazaList` leem cenário ativo + faixa; `DeclareVotesForm` intacto.
|
||||
- **Testes:** int assimetria (leader não vê nenhum dos três); unit ordem + fallback por cenário; int migration backfill → central; mapa/lista usam default central.
|
||||
- **Depth check:** reusar `plazaStaffFormActions`, `campaignAccess`, shells B9; espelhar invariante de `voteGoals.ts` em helper fino compartilhado só se ≥2 call sites.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **Dura:** R2 / A9 (schema e superfícies de estimativa) — código entregue; deploy da remodelagem deve estar aplicado antes de dados reais.
|
||||
- **Dependentes (atualizar):** **E8** passa a depender de A10 (cobertura usa cenário, default média); **C12** suave (versions capturam o group); E9/B13 via E8.
|
||||
- **Reusa:** `votePledgeData.ts`, `plazaMapData.ts`, `plazaStaffFormActions.ts`, `voteGoals.ts` (padrão de ordem), `PledgeEstimateForm`, `PlazaMapPanel`.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Conta da cadeira / cobertura ÷ meta → [E8](conta-da-cadeira.md).
|
||||
- Versions/trajetória de pledge → [C12](registro-fundacao.md).
|
||||
- Escala relativa do mapa (quantis/LQ) → [B13](escala-relativa-mapa.md).
|
||||
- Alterar `voteGoals` ou fundir labels; previsão estatística; import CSV de faixas.
|
||||
- Expor estimativas à liderança; auto-preencher faixa a partir do declarado.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Fundir com Bom/Regular/Mínimo.** Se alguém “aproveitar o group que já existe”: apaga meta×estimativa. **Mitigação:** labels e identificadores distintos; teste que `voteGoals` não é lido pelo resolver de estimativa.
|
||||
- **Três choropleths / chart de incerteza.** Explode UX e appetite. **Mitigação:** um seletor, um fill; faixa só no readout/lista.
|
||||
- **Exigir os três sempre + workflow de “confirmar faixa”.** Volta ao sugerir→confirmar rejeitado na remodelagem. **Mitigação:** nullable + ordem condicional.
|
||||
- **Service layer / event sourcing da faixa.** **Mitigação:** group + helpers em `votePledgeData`.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Nota por cenário** (hoje uma `estimateNote`). Revisitar se assessores pedirem justificar só o pessimista vs. o otimista.
|
||||
- **Aviso soft vs `voteGoals.minimum` no cenário pessimista.** Revisitar após E8 com totais preenchidos (mesmo gatilho de A9).
|
||||
- **Delta semanal por cenário.** Revisitar com C12 (versions no group).
|
||||
|
||||
## Referências
|
||||
|
||||
- `docs/roadmap.md` (Trilha A / A10 → E8)
|
||||
- `docs/plans/estimativa-votos-praca.md` — A9 (scalar a evoluir)
|
||||
- `docs/plans/remodelagem-pracas.md` — assimetria declared×estimated
|
||||
- `docs/plans/conta-da-cadeira.md` — consumidor E8 (atualizar dep)
|
||||
- `src/collections/VotePledge.ts`, `src/collections/Plaza.ts`
|
||||
- `src/utilities/votePledgeData.ts`, `src/utilities/plazaMapData.ts`, `src/lib/voteGoals.ts`
|
||||
- `src/components/campaign/PledgeEstimateForm.tsx`, `PlazaListExpectedVotesControl.tsx`, `PlazaMapPanel.tsx`, `DeclareVotesForm.tsx`
|
||||
- AGENTS.md — staff-only estimates, naming, migrations, overrideAccess
|
||||
- `PRODUCT.md` / `DESIGN.md` — Field Desk; princípio 5 (métricas locais)
|
||||
- Feedback coordenação geral 2026-07-23 (áudio em `docs/general-coordinator-interview/`)
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# E8 — Conta da cadeira (metas derivadas, potencial por praça, cobertura)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-21
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (seção "Inteligência de campanha", E8; plano-mestre [inteligencia-campanha.md](inteligencia-campanha.md))
|
||||
Atualizado em: 2026-07-21 _(deps: A10 registrado 2026-07-23)_
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (seção "Inteligência de campanha", E8; plano-mestre [inteligencia-campanha.md](inteligencia-campanha.md); **depende de A10**)
|
||||
Impeccable: B — cobertura/metas encaixadas no dashboard, na lista de Praças e no detalhe (sem rota nova)
|
||||
Appetite: ~2 dias eng; migration pequena (global `campaignGoals`) + utilities derivadas + encaixes de UI
|
||||
Responsável: —
|
||||
|
|
@ -27,7 +27,7 @@ O relatório de discovery ([§5.1](../research/relatorio-entrevista-persona-camp
|
|||
|
||||
- Global `campaignGoals` (admin group `Campanha`, staff-only): meta estadual de votos, margem, ano-base, nota.
|
||||
- Utilities derivadas por praça (sem persistir o derivado): válidos projetados do cargo (série 2014/2018/2022), teto do campo (majoritário 1º turno), taxa de captura, share intracampo (via `campoParties.ts`), roll-off DF×majoritária, potencial e meta sugerida (decomposição proporcional ao potencial).
|
||||
- Cobertura: Σ (`estimatedVotes ?? declaredVotes`) ÷ meta por praça e agregada; delta semanal (exige C12 para série; até lá, delta vs. snapshot em memória do último load é aceitável mostrar como "—").
|
||||
- Cobertura: Σ (estimativa do cenário ativo — default **média/`central`** via A10 — `?? declaredVotes`) ÷ meta por praça e agregada; delta semanal (exige C12 para série; até lá, delta vs. snapshot em memória do último load é aceitável mostrar como "—").
|
||||
- Encaixes: linha de cobertura no dashboard (`campaignDashboardData.ts`), colunas meta/cobertura na lista (`PlazaList`/`PlazaListOverview`), card no detalhe da Praça.
|
||||
- Access: tudo staff-only (`coordinator`/`advisor`); `leader` não vê metas — mesmo padrão de `voteGoals`.
|
||||
- Sanity check por TI: Σ metas das praças do TI vs. participação histórica do TI no voto (aviso, não bloqueio).
|
||||
|
|
@ -67,15 +67,15 @@ Componentes:
|
|||
|
||||
- **`src/lib/campoParties.ts`**: mapa ano→partidos do campo (2014/2018/2022/2026), com fixture de teste.
|
||||
- **`src/utilities/plazaPotential.ts`**: deriva por praça (reusa `plazaElectionGeography.ts` para células cidade×zona e `plazaElectoralBaseline.ts` para a série); expõe `projectedValidVotes`, `fieldCeiling`, `captureRate`, `intraFieldShare`, `rollOff`, `suggestedGoal`.
|
||||
- **`src/utilities/goalCoverage.ts`**: cobertura por praça/agregado consumindo `aggregatePledgesByPlaza` (regra `estimatedVotes ?? declaredVotes` de `votePledgeData.ts`) + `voteGoals`/meta sugerida; sanity por TI via `bahiaTerritories.ts`.
|
||||
- **`src/utilities/goalCoverage.ts`**: cobertura por praça/agregado consumindo `aggregatePledgesByPlaza` (regra por cenário de [A10](cenarios-estimativa-votos.md) / `votePledgeData.ts`, default `central`) + `voteGoals`/meta sugerida; sanity por TI via `bahiaTerritories.ts`.
|
||||
- **Global `CampaignGoals`** (`src/globals/CampaignGoals.ts`): campos `stateGoal`, `margin`, `baseYear`, `note`; access staff-read/coordinator-write; hook `revalidateGlobal`.
|
||||
- **UI:** linha nova em `CampaignMetricStrip` do dashboard; coluna/ordenar por cobertura em `PlazaList` (reusa padrão B9 para editar meta via `plazaStaffFormActions`); card "Conta da cadeira" no detalhe.
|
||||
- **Migration**: `pnpm migrate:create add_campaign_goals_global` (tabela do global). Sem mudança em collections.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Dura: deploy da remodelagem (schema `plaza`/`votePledge` em produção). Suave: C12 (delta semanal real da cobertura usa trajetória de pledges; até lá, sem histórico).
|
||||
- Reusa: `plazaElectoralBaseline.ts`, `plazaElectionGeography.ts`, `votePledgeData.ts`, `plazaPageData.ts` (`loadPlazaListPageBundle`), `campaignDashboardData.ts`, `plazaStaffFormActions.ts`, `campaignAccess.ts`.
|
||||
- Dura: **A10** (cenários pessimista/média/otimista — [plano](cenarios-estimativa-votos.md)); deploy da remodelagem (schema `plaza`/`votePledge` em produção). Suave: C12 (delta semanal real da cobertura usa trajetória de pledges; até lá, sem histórico).
|
||||
- Reusa: `plazaElectoralBaseline.ts`, `plazaElectionGeography.ts`, `votePledgeData.ts` (agregação por cenário pós-A10), `plazaPageData.ts` (`loadPlazaListPageBundle`), `campaignDashboardData.ts`, `plazaStaffFormActions.ts`, `campaignAccess.ts`.
|
||||
|
||||
## Não escopo
|
||||
|
||||
|
|
|
|||
|
|
@ -46,18 +46,18 @@ Regras transversais (de §6.2/§6.4, viram comportamento do produto): **barato-a
|
|||
|
||||
## Fatias (funcionalidades faltantes)
|
||||
|
||||
| ID | Fatia (plano) | Entrega | OST | Classe | Appetite | Janela |
|
||||
| --- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------ | -------- | --------- |
|
||||
| E8 | **[Conta da cadeira](conta-da-cadeira.md)** | Global `campaignGoals`; potencial por praça (válidos projetados, captura do campo, share intracampo via `campoParties.ts`, roll-off, headroom); decomposição meta estadual→praça com sanity por TI; cobertura Σ`estimatedVotes ?? declaredVotes`÷meta no dashboard/lista; denominadores sempre válidos projetados (I-B) | O-A | B | ~2d | 2 |
|
||||
| E9 | **[Fila de alocação](fila-de-alocacao.md)** | Lista de decisão com as 7 colunas (§5.2 do relatório: votos em jogo, meta/cobertura/delta, classe, LQ/captura, rede+frescor, competição, tendência); ordenação default por déficit descoberto; ordenação de risco (defesa por frescor); "coluna da vergonha" | O-B, O-F | B | ~1,5d | 2 |
|
||||
| C12 | **[Registro-fundação](registro-fundacao.md)** | Versions em `votePledge`; sinais tipados no `plazaUpdate`; `actionPlan.origin`+`resourceCost`; collection `allocationDecision`; garantia "nunca sobrescrever" | O-E, O-G(base) | B | ~2d | 2 |
|
||||
| E10 | **[Classificação territorial relativa](classificacao-territorial-relativa.md)** | Recalibra defesa/ataque/indecisa/perdida com âncoras relativas (LQ vs. share estadual próprio; múltiplos do share da cadeira marginal), multi-eixo (dominância+importância+competição+teto do campo); substitui limiares 35/20/10 de `electionInsights.ts` para DF | O-D | B | ~1d | 3 |
|
||||
| B13 | **[Escala relativa no mapa](escala-relativa-mapa.md)** | Modo de escala novo: quantis do próprio candidato (5 classes) default, LQ e rank na praça; símbolo proporcional por votos em jogo sobre o polígono; % dos válidos mantido como opção; roll-off como métrica adicional | O-C | B | ~2d | 3 |
|
||||
| E14 | **[Níveis de envolvimento N0–N4](niveis-de-envolvimento.md)** | `engagementLevel` com critérios, promoção/rebaixamento com histerese e sinais de reversão (via `allocationDecision`); vocabulário duplo (nível nunca exposto fora do staff — access) | O-K | B | ~1d | 3 |
|
||||
| E11 | **[Motor de sugestões v1](motor-de-sugestoes.md)** | Computa subconjunto dos padrões (P1, P2, P3, P5, P6, P7, K-A/K-B; demais por fase 2) por praça; sugestão com menu/estatuto/leitura-alternativa; aceitar/descartar grava `allocationDecision`; triagem 1–5 + histerese; "pauta do silêncio" mensal | O-H | C | ~3d | 3 |
|
||||
| E12 | **[Camada TI](camada-territorios-identidade.md)** | Rollups por Território de Identidade com salvaguardas MAUP (razão de agregados, mediana+amplitude+praça crítica, Metropolitano decomposto); benchmark intra-TI (T4); gatilhos T1–T3/T5 no motor = fase 2 de E11 | O-I | B | ~1,5d | 3 |
|
||||
| E13 | **[Planejador de presença/giros](planejador-de-giros.md)** | Elegibilidade (5 condições ✓/—), fase do calendário (construção/consolidação/ativação), compositor de giro (J-C âncora+satélites+semente), visita pedida×justificada (J-B, usa `actionPlan.origin`) | O-J | C | ~1,5d | 3 |
|
||||
| E15 | **[Backtest pós-eleição](backtest-pos-eleicao.md)** | Pledge (trajetória) vs. voto realizado por zona; calibração de limiares E10/E14; relatório de aprendizado | O-G | A | ~1d | pós-04/10 |
|
||||
| ID | Fatia (plano) | Entrega | OST | Classe | Appetite | Janela |
|
||||
| --- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------ | -------- | --------- |
|
||||
| E8 | **[Conta da cadeira](conta-da-cadeira.md)** | Global `campaignGoals`; potencial por praça (válidos projetados, captura do campo, share intracampo via `campoParties.ts`, roll-off, headroom); decomposição meta estadual→praça com sanity por TI; cobertura Σ(estimativa do cenário — default média via **A10**)÷meta no dashboard/lista; denominadores sempre válidos projetados (I-B) | O-A | B | ~2d | 2 |
|
||||
| E9 | **[Fila de alocação](fila-de-alocacao.md)** | Lista de decisão com as 7 colunas (§5.2 do relatório: votos em jogo, meta/cobertura/delta, classe, LQ/captura, rede+frescor, competição, tendência); ordenação default por déficit descoberto; ordenação de risco (defesa por frescor); "coluna da vergonha" | O-B, O-F | B | ~1,5d | 2 |
|
||||
| C12 | **[Registro-fundação](registro-fundacao.md)** | Versions em `votePledge`; sinais tipados no `plazaUpdate`; `actionPlan.origin`+`resourceCost`; collection `allocationDecision`; garantia "nunca sobrescrever" | O-E, O-G(base) | B | ~2d | 2 |
|
||||
| E10 | **[Classificação territorial relativa](classificacao-territorial-relativa.md)** | Recalibra defesa/ataque/indecisa/perdida com âncoras relativas (LQ vs. share estadual próprio; múltiplos do share da cadeira marginal), multi-eixo (dominância+importância+competição+teto do campo); substitui limiares 35/20/10 de `electionInsights.ts` para DF | O-D | B | ~1d | 3 |
|
||||
| B13 | **[Escala relativa no mapa](escala-relativa-mapa.md)** | Modo de escala novo: quantis do próprio candidato (5 classes) default, LQ e rank na praça; símbolo proporcional por votos em jogo sobre o polígono; % dos válidos mantido como opção; roll-off como métrica adicional | O-C | B | ~2d | 3 |
|
||||
| E14 | **[Níveis de envolvimento N0–N4](niveis-de-envolvimento.md)** | `engagementLevel` com critérios, promoção/rebaixamento com histerese e sinais de reversão (via `allocationDecision`); vocabulário duplo (nível nunca exposto fora do staff — access) | O-K | B | ~1d | 3 |
|
||||
| E11 | **[Motor de sugestões v1](motor-de-sugestoes.md)** | Computa subconjunto dos padrões (P1, P2, P3, P5, P6, P7, K-A/K-B; demais por fase 2) por praça; sugestão com menu/estatuto/leitura-alternativa; aceitar/descartar grava `allocationDecision`; triagem 1–5 + histerese; "pauta do silêncio" mensal | O-H | C | ~3d | 3 |
|
||||
| E12 | **[Camada TI](camada-territorios-identidade.md)** | Rollups por Território de Identidade com salvaguardas MAUP (razão de agregados, mediana+amplitude+praça crítica, Metropolitano decomposto); benchmark intra-TI (T4); gatilhos T1–T3/T5 no motor = fase 2 de E11 | O-I | B | ~1,5d | 3 |
|
||||
| E13 | **[Planejador de presença/giros](planejador-de-giros.md)** | Elegibilidade (5 condições ✓/—), fase do calendário (construção/consolidação/ativação), compositor de giro (J-C âncora+satélites+semente), visita pedida×justificada (J-B, usa `actionPlan.origin`) | O-J | C | ~1,5d | 3 |
|
||||
| E15 | **[Backtest pós-eleição](backtest-pos-eleicao.md)** | Pledge (trajetória) vs. voto realizado por zona; calibração de limiares E10/E14; relatório de aprendizado | O-G | A | ~1d | pós-04/10 |
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
|
|
@ -103,11 +103,11 @@ flowchart TD
|
|||
E11 -.decisões acumuladas.-> E15
|
||||
```
|
||||
|
||||
Reuso obrigatório (depth check): `plazaElectoralBaseline.ts`/`plazaElectionGeography.ts` (células cidade×zona), `votePledgeData.ts` (`estimatedVotes ?? declaredVotes`), `aggregatePledgesByPlaza`/`loadPlazaListPageBundle` (A9+), `BahiaMap`+`bahiaMapStyle.ts` (B6/B11/B12), `bahiaTerritories.ts` (TI), `electionInsights.ts` (substituição E10), `CampaignPageShell`/`CampaignMetricStrip`, `campaignAccess.ts`, `withPayloadTransaction`.
|
||||
Reuso obrigatório (depth check): `plazaElectoralBaseline.ts`/`plazaElectionGeography.ts` (células cidade×zona), `votePledgeData.ts` (agregação por cenário pós-**A10** — [cenarios-estimativa-votos.md](cenarios-estimativa-votos.md); default `central`), `aggregatePledgesByPlaza`/`loadPlazaListPageBundle` (A9+), `BahiaMap`+`bahiaMapStyle.ts` (B6/B11/B12), `bahiaTerritories.ts` (TI), `electionInsights.ts` (substituição E10), `CampaignPageShell`/`CampaignMetricStrip`, `campaignAccess.ts`, `withPayloadTransaction`.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Duras: deploy da remodelagem (schema `plaza`/`votePledge` em produção); E8→E9/E10/E12/E13; C12→E11/E15. Suaves: A6 dobradinha (melhora J-B/E13); dados reais da Onda 0 (o motor roda com dados fictícios no smoke, mas só vale com rede real).
|
||||
- Duras: deploy da remodelagem (schema `plaza`/`votePledge` em produção); **A10→E8** (faixa pessimista/média/otimista antes da cobertura); E8→E9/E10/E12/E13; C12→E11/E15. Suaves: A6 dobradinha (melhora J-B/E13); dados reais da Onda 0 (o motor roda com dados fictícios no smoke, mas só vale com rede real); A10→C12 (versions capturam o group).
|
||||
- Nenhuma dependência de site público/admin.
|
||||
|
||||
## Não escopo
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ Coordenadores e lideranças precisam ser avisados de eventos da campanha (convit
|
|||
- **Push web** via Push API + Service Worker + VAPID (entrega em segundo plano, inclusive com o app fechado).
|
||||
- **Sino de notificações in-app** dentro do `/campanha` (central de avisos lidos/não lidos).
|
||||
|
||||
Notificações por WhatsApp Business API ficam **fora** deste plano — já existem como item separado no roadmap (linha 57), exigem Meta + templates e serão reavaliadas quando o volume justificar.
|
||||
Notificações por WhatsApp Business API ficam **fora** deste plano (Meta + Res. TSE 23.610 art. 33). Canal WhatsApp **1:1 interno** staff↔lideranças (bridge não oficial) é o programa **D3–D5** — [whatsapp-interno-campanha.md](whatsapp-interno-campanha.md) — complementar a push/sino, não substituto.
|
||||
|
||||
## Objetivos
|
||||
|
||||
|
|
|
|||
127
docs/plans/whatsapp-canal-fundacao.md
Normal file
127
docs/plans/whatsapp-canal-fundacao.md
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
# D3 — Fundação do canal WhatsApp interno
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-23
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha D, item D3; programa [whatsapp-interno-campanha.md](whatsapp-interno-campanha.md))
|
||||
Impeccable: B — pareamento QR self-service por staff (sem inbox/compose de produto)
|
||||
Appetite: ~3 dias eng; sidecar multi-sessão + migration + Consent + UI QR/status; **sem** envio/inbox de produto
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (princípios 1 soberania, 2 clareza, 4 depth/simplicity) / `DESIGN.md` (register `product`) · tema `data-theme='campaign'` · shells `CampaignPageShell` · superfície de perfil existente (`/campanha/perfil` se houver).
|
||||
|
||||
Na implementação (`implement-roadmap-item`): craft compacto → critique → polish (fluxo QR + estados; sem redesign de CRM).
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** Assessor ou Coordenador Geral no celular/notebook quer **ligar o próprio Zap** uma vez e seguir falando com lideranças “como sempre” — só que o Teqo passa a enxergar o fio.
|
||||
- **Job principal:** parear a conta WhatsApp **pessoal** do `campaignUser` via QR no app e manter a sessão viva no sidecar.
|
||||
- **Estratégia de cor:** Restrained; sessão caída = `destructive`/`cadence-overdue`; aviso ToS em muted, não modal de terror.
|
||||
- **Edit where you see:** pairing self-service no **perfil** do staff (ação rara); não no CRM de lideranças.
|
||||
- **Anti-goals:** número institucional único; admin pareando o chip de outra pessoa; chat UI; inbox; sync de histórico completo; esconder risco ToS/ban.
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
- **Vou apresentar dados?** Sim, superfície mínima neste item (status **da minha** sessão).
|
||||
- **Decisões desbloqueadas:**
|
||||
- Assessor/CG: “meu WhatsApp está conectado ao Teqo? preciso escanear de novo?”
|
||||
- (Aggregate para D4/D5: sessão do ator healthy → habilitar compose/inbox daquela pessoa.)
|
||||
- **Forma escolhida:** **número + contexto** (estado `conectado | aguardando QR | erro` + telefone mascarado da conta pareada + `lastSeenAt`). **Rejeitado:** dashboard de N sessões para o time inteiro na v1 (CG pode ter lista read-only “quem conectou” — Adiado); chart de uptime; KPI de conversas.
|
||||
- **Profile:** categórico; 1 sessão por `campaignUser` staff.
|
||||
- **Anti-goals de dado:** sem vanity de volume de msgs nesta fatia.
|
||||
|
||||
## Contexto
|
||||
|
||||
O programa exige que a liderança continue vendo o **Zap pessoal** do assessor/CG — não um robô institucional. Isso implica **N sessões** (uma por staff), pareadas no app (QR), não um chip compartilhado.
|
||||
|
||||
Socket WhatsApp não roda em Vercel serverless → sidecar multi-sessão. Sem fundação (sessão↔ `campaignUser`, adapter, log, Consent), D4/D5 não têm onde plugar.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Sidecar Node longo com **um** adapter não oficial, **N sessões** indexadas por `campaignUser.id` (só roles `coordinator` | `advisor`).
|
||||
- Fluxo de pareamento no `/campanha`: `startPairing` → sidecar gera QR → UI mostra QR (refresh se expirar) → evento `paired` → status `connected` + telefone detectado (normalizado).
|
||||
- App Next.js ↔ sidecar via API autenticada (HMAC); o Next **não** abre socket WA.
|
||||
- Collections: `whatsappSession` (**1:1 com `campaignUser`**, unique) + `whatsappMessage` (log com `session` / `campaignUser`, direção, phones, texto, timestamps, match opcional `leadership`).
|
||||
- Consent **`campanha-whatsapp-canal`** (fail-closed) + aceite explícito no UI de pairing (risco ToS/conta pessoal). Texto no lote jurídico.
|
||||
- UI: status + QR + desconectar **só da própria sessão**; `leader` não pareia.
|
||||
- Guardrails: allowlist inbound = phones de lideranças engajadas no CRM (e, para advisor, preferir as das Praças acessíveis — pode afrouxar para “todas lideranças engajadas” se match for só por phone); rate limit por sessão; secrets em env.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Sessão = `campaignUser` staff, número pessoal.** UNIQUE `(campaignUser)`. **Rejeitado:** sessão singleton institucional; parear número digitado sem QR; um CG operar o chip de um assessor.
|
||||
- **Login/pareamento = QR no app (estilo WhatsApp Web).** **Rejeitado:** só deep-link `wa.me`; pairing por SMS inventado; exigir app Companion oficial da Meta como único caminho (se o adapter oferecer código de pareamento sem QR como fallback técnico, ok — UX primária = QR).
|
||||
- **Sidecar multi-sessão fora do Vercel.** Credenciais por sessão em volume do worker. **Rejeitado:** Baileys dentro de Server Action; Cloud API / WABA.
|
||||
- **Um adapter na v1.** Baileys _ou_ Evolution self-hosted — um só. **Rejeitado:** multi-provider.
|
||||
- **Log mínimo pós-`pairedAt`; sem histórico prévio; sem mídia binária na v1.** **Rejeitado:** dump completo do chip; gravar chats com não-CRM.
|
||||
- **Consent `campanha-whatsapp-canal` + copy de risco pessoal.** **Rejeitado:** hardcode de ID; silêncio sobre ban/ToS.
|
||||
- **i18n/naming:** `whatsappSession`, `whatsappMessage`, `startWhatsappPairing`, `whatsappBridge`; UI pt-BR (“Conectar meu WhatsApp”, “Escaneie o QR com o celular”).
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Onde hospedar o sidecar?** **Opções:** Fly.io / Railway / VPS. **Recomendação:** Fly/Railway com volume; runbook na implementação. _(ops)_
|
||||
- **CG vê quem do time está conectado?** **Opções:** não na v1 | lista read-only de status. **Recomendação:** não na v1 (só a própria sessão); lista de saúde do time = Adiado com gatilho no programa.
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Staff["Assessor / CG<br/>/campanha/perfil"]
|
||||
QR["QR pairing<br/>própria sessão"]
|
||||
Next["Next.js<br/>HMAC + queue"]
|
||||
Worker["Sidecar<br/>N sessões WA"]
|
||||
Sess["whatsappSession<br/>1:1 campaignUser"]
|
||||
Log["whatsappMessage"]
|
||||
CRM["leadership via phone"]
|
||||
|
||||
Staff --> QR --> Next
|
||||
Next <--> Worker
|
||||
Worker --> Sess
|
||||
Worker --> Log
|
||||
Log --> CRM
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`whatsappBridge`** (`src/utilities/whatsappBridge.ts`, `server-only`): `startPairing(userId)`, `getSessionStatus(userId)`, `disconnect(userId)`, (D4) `enqueueSend(userId, …)`; timeout curto.
|
||||
- **Sidecar** (`workers/whatsapp-bridge/`): map `campaignUserId → adapter session`; emite QR/status/inbound; allowlist CRM.
|
||||
- **`whatsappSession`**: `user` → `campaignUser` (unique, required), `status`, `pairedPhone`, `pairedAt`, `lastSeenAt`, `lastError`.
|
||||
- **`whatsappMessage`**: `session`/`user`, `direction`, `peerPhone`, `leadership` (nullable), `body`, `providerMessageId`, timestamps.
|
||||
- **UI** `WhatsappPairingPanel` no perfil: QR image/data-URL ou SVG, polling/SSE de status, botão desconectar, aviso Consent/ToS.
|
||||
- **Actions** `startWhatsappPairing` / `disconnectWhatsappSession` / `getWhatsappSessionStatus` — actor só na própria sessão (`user.id`).
|
||||
- **Migration:** `pnpm migrate:create add_whatsapp_channel` — session + message; sem backfill.
|
||||
- **Reuse:** `normalizeBrazilianPhone`, `campaignConsent`, `withPayloadTransaction`, `overrideAccess: false`.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **Dura:** remodelagem deployada (`campaignUser` roles + `leadership`/`Contact`).
|
||||
- **Suave:** Onda 0 (texto Consent); telefones reais nas lideranças.
|
||||
- **Não depende de D2.**
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Compose/envio → [D4](whatsapp-envio-liderancas.md).
|
||||
- Inbox / rascunhos → [D5](whatsapp-sugestao-atualizacoes.md).
|
||||
- Número institucional / WABA / massa → fora.
|
||||
- Mídia no log; grupos; histórico pré-pairing → Adiado.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **N sockets × memória no worker.** Mitigação: lazy connect on pairing; disconnect idle com re-QR; monitorar RSS antes de escalar horizontalmente (sticky sessions por user se shardear — Adiado).
|
||||
- **Staff desconecta no celular e o Teqo não sabe.** Mitigação: health ping + status `erro` + CTA re-parear no compose D4.
|
||||
- **Parear conta errada (pessoal demais / família).** Mitigação: mostrar telefone detectado pós-QR + “Desconectar”; copy de que deve ser o número que as lideranças já usam.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Retention/expurgo do log.** Revisitar quando: volume ou pedido jurídico.
|
||||
- **Lista CG “quem conectou”.** Revisitar quando: onboarding do time travar sem visão de cobertura.
|
||||
- **Advisor-only allowlist por Praça no inbound.** Revisitar se falso positivo de match cruzado aparecer.
|
||||
|
||||
## Referências
|
||||
|
||||
- [whatsapp-interno-campanha.md](whatsapp-interno-campanha.md)
|
||||
- `docs/roadmap.md` — Trilha D
|
||||
- `src/utilities/phone.ts`, `campaignConsent.ts`, `campaignAccess.ts`
|
||||
- `docs/plans/notifications.md` — D2
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
- AGENTS.md — Consent, migrations, campaign auth
|
||||
104
docs/plans/whatsapp-envio-liderancas.md
Normal file
104
docs/plans/whatsapp-envio-liderancas.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# D4 — Envio WhatsApp 1:1 para lideranças
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-23
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha D, item D4; programa [whatsapp-interno-campanha.md](whatsapp-interno-campanha.md))
|
||||
Impeccable: B — compose encaixado no CRM de liderança / detalhe (sem rota de “marketing blast”)
|
||||
Appetite: ~1–2 dias eng; actions + UI compose + templates curtos; reusa sessão pessoal D3
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (princípios 2, 3 Edit where you see, 4) / `DESIGN.md` (register `product`) · tema `campaign` · precedente `LeadershipInviteButtons` / share `wa.me`.
|
||||
|
||||
Na implementação: craft compacto → critique → polish.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** Assessor na estrada cobra atualização; CG manda um “como está Orolândia?” — a liderança deve ver a mensagem **no chat pessoal de sempre** com aquela pessoa.
|
||||
- **Job principal:** enviar **uma** mensagem 1:1 pela **sessão WhatsApp do próprio ator** para **uma** liderança do escopo.
|
||||
- **Estratégia de cor:** Restrained; ícone WA ok; primário Signal Red.
|
||||
- **Edit where you see:** sim — Sheet “Mensagem” no detalhe `/campanha/liderancas/[id]`.
|
||||
- **Anti-goals:** multi-destinatário; enviar pela sessão de outro staff; número institucional; templates de propaganda; matar `wa.me` de convites.
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — superfície de ação (compose). Feedback de entrega = Feel the action (§7), não KPI.
|
||||
|
||||
## Contexto
|
||||
|
||||
Com [D3](whatsapp-canal-fundacao.md), cada staff tem (ou não) a própria sessão pareada. O valor de produto é: **parece Zap pessoal** — a liderança não “fala com a campanha”, fala com o assessor/CG que já conhece.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Action `sendLeadershipWhatsappMessage({ leadershipId, body })`: access OK → resolve phone → exige **`whatsappSession` do `req.user` em `connected`** + Consent → `enqueueSend(actorUserId, …)` → log outbound com `user` = ator.
|
||||
- UI compose: textarea + templates curtos editáveis; se sessão down → CTA “Conectar meu WhatsApp” (perfil D3) **e** fallback `wa.me` (“Abrir no seu WhatsApp”).
|
||||
- Rate limit por ator/hora e por destinatário; **hard reject** N>1 destinatários.
|
||||
- Guardrails: só `coordinator`/`advisor`; advisor só lideranças das Praças acessíveis; nunca `supporter`; nunca enviar usando sessão de terceiro.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Remetente = sessão do ator logado.** A liderança recebe do número pessoal pareado daquela pessoa. **Rejeitado:** “enviar como campanha”; CG disparar pelo chip de um assessor; escolher remetente na UI.
|
||||
- **Um destinatário por envio.** **Rejeitado:** lista/BCC; “todas as lideranças da Praça”.
|
||||
- **Escopo = access da liderança.** **Rejeitado:** telefone livre digitado.
|
||||
- **`wa.me` = fallback + convites.** **Rejeitado:** migrar convites para o bridge nesta fatia.
|
||||
- **i18n/naming:** `sendLeadershipWhatsappMessage`, `LeadershipWhatsappCompose`; copy pt-BR (“Enviar pelo meu WhatsApp”).
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Templates: hardcoded vs collection?** **Opções:** 3–5 strings em código | collection. **Recomendação:** constantes na v1.
|
||||
- **Thread no detalhe?** **Opções:** só compose | compose + últimas N do par **nesta sessão**. **Recomendação:** últimas 10 msgs do par na sessão do ator (não misturar threads de outro assessor). _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
UI["LeadershipWhatsappCompose"]
|
||||
Action["sendLeadershipWhatsappMessage"]
|
||||
Sess["whatsappSession do ator"]
|
||||
Bridge["enqueueSend(actorId, …)"]
|
||||
Log["whatsappMessage outbound"]
|
||||
Lead["leadership phone"]
|
||||
|
||||
UI --> Action
|
||||
Action --> Sess
|
||||
Action --> Lead
|
||||
Action --> Bridge --> Log
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`LeadershipWhatsappCompose`**: Sheet; estados connected / needs-pairing / sending / failed; deep-link perfil se unpaired.
|
||||
- **Action** em `actions/whatsapp.ts`: `overrideAccess: false`; nunca aceita `sessionUserId` do client (só `req.user.id`).
|
||||
- **Reuse D3:** bridge, session, Consent, phone normalize.
|
||||
- **Migration:** nenhuma se D3 já tem `user` no log.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **Dura:** **D3** (sessão pessoal + log + Consent).
|
||||
- **Suave:** telefones reais nas lideranças.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Inbox / rascunhos → [D5](whatsapp-sugestao-atualizacoes.md).
|
||||
- Apoiadores / grupos / auto-resposta → fora.
|
||||
- Push “mensagem enviada” → D2.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Templates ricos.** Mitigação: `{nome}`, `{praca}` no máximo.
|
||||
- **Fila com retry infinito.** Mitigação: retry curto + `failed` visível.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- Bulk UI. Revisitar só com ok jurídico explícito.
|
||||
- CG enviar “em cópia” vendo thread do assessor. Revisitar com o item de inbox agregado do programa (acesso cruzado).
|
||||
|
||||
## Referências
|
||||
|
||||
- [whatsapp-interno-campanha.md](whatsapp-interno-campanha.md), [whatsapp-canal-fundacao.md](whatsapp-canal-fundacao.md)
|
||||
- `src/components/campaign/LeadershipInviteButtons.tsx`
|
||||
- `src/app/(campaign)/campanha/(app)/liderancas/[id]/page.tsx`
|
||||
- `src/utilities/campaignAccess.ts`, `phone.ts`
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
- AGENTS.md — access, transactions
|
||||
92
docs/plans/whatsapp-interno-campanha.md
Normal file
92
docs/plans/whatsapp-interno-campanha.md
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# WhatsApp interno da campanha (programa D3–D5)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-23
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha D, programa D3–D5)
|
||||
Impeccable: N/A — plano-mestre; classes por fatia (D3 A/B, D4 B, D5 C)
|
||||
Appetite: programa ~5–8 dias eng fatiados; **não** implementar como monólito
|
||||
Responsável: —
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A neste plano-mestre — cada fatia declara a sua. O programa existe para **fechar o Little Hire** (confiança no quadro atualizado): o canal real (ZAP) deixa de competir com o Field Desk e passa a alimentá-lo.
|
||||
|
||||
## Contexto
|
||||
|
||||
Feedback de produto (2026-07-23) + evidência de discovery/entrevista: lideranças e staff dificilmente sentirão segurança registrando só no `/campanha` enquanto o trabalho real continua no WhatsApp. O coordenador geral descreve o ZAP como o campo (“tudo após o ZAP”; mensagem > ligação). O [`IMPROVE-APP-PLAN.md`](../IMPROVE-APP-PLAN.md) nomeia o leak: assessores param de atualizar → WhatsApp/planilha → ferramenta defasada → abandono.
|
||||
|
||||
Hoje o produto só usa WhatsApp como **atalho `wa.me`** (convites, share, kit de apoiador): o humano envia no app dele; o Teqo não vê a conversa nem grava o sinal. **D2** (push + sino) cobre avisos _dentro_ do app — complementar, não substituto do canal de campo.
|
||||
|
||||
**Fronteira legal/produto já travada no roadmap:** WhatsApp Business API e disparo em massa continuam **fora de escopo** (Meta veda campanhas políticas; Res. TSE 23.610 art. 33). Este programa é **comunicação 1:1 interna** staff ↔ lideranças já cadastradas (`Contact`/`leadership`) — não propaganda a terceiros nem blast.
|
||||
|
||||
**Decisão de produto (2026-07-23):** o envio deve parecer o canal **pessoal** do assessor/coordenador (é o que a liderança espera), não um número institucional da campanha. Pareamento no próprio `/campanha` (QR, estilo WhatsApp Web).
|
||||
|
||||
## Objetivos do programa
|
||||
|
||||
- Trazer o canal ZAP para _dentro_ do fluxo de poder do Field Desk sem virar o sistema de registro (princípio 1 de `PRODUCT.md`: atalho pragmático; verdade no Teqo).
|
||||
- Cada `coordinator`/`advisor` vincula **o próprio WhatsApp pessoal** e fala com lideranças **como se fosse o chip dele** — a liderança recebe no chat pessoal de sempre.
|
||||
- Fatiar em três entregas shipáveis (D3 → D4 → D5), cada uma com appetite próprio.
|
||||
- Humano no loop em qualquer escrita de domínio (`plazaUpdate`, demanda, pledge) — alinhado a E11/C12.
|
||||
- Fail-closed em Consent, access por papel/Praça, e auditoria mínima das mensagens processadas.
|
||||
|
||||
## Fatias (ordem de execução)
|
||||
|
||||
| ID | Plano | Entrega essencial | Classe | Appetite | Depende de |
|
||||
| --- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ------ | -------- | ----------------------------------- |
|
||||
| D3 | [whatsapp-canal-fundacao.md](whatsapp-canal-fundacao.md) | Bridge multi-sessão + **QR por staff** + log + Consent; **zero** envio em massa | B | ~3d | remodelagem em prod (suave: Onda 0) |
|
||||
| D4 | [whatsapp-envio-liderancas.md](whatsapp-envio-liderancas.md) | Compose/envio 1:1 pela **sessão do ator** (CRM → liderança; parece chat pessoal) | B | ~1–2d | **D3** |
|
||||
| D5 | [whatsapp-sugestao-atualizacoes.md](whatsapp-sugestao-atualizacoes.md) | Inbox inbound **na sessão de quem recebeu** → rascunhos com aceite humano | C | ~2–3d | **D3** (suave: D4, C12) |
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
D3["D3 Fundação<br/>QR por campaignUser"]
|
||||
D4["D4 Envio 1:1<br/>sessão do ator"]
|
||||
D5["D5 Inbox → rascunhos<br/>humano confirma"]
|
||||
D3 --> D4
|
||||
D3 --> D5
|
||||
D4 -.thread/contexto.-> D5
|
||||
```
|
||||
|
||||
## Decisões travadas (programa)
|
||||
|
||||
- **Canal interno 1:1, framework não oficial permitido.** Destinatários = lideranças já no CRM; never blast a apoiadores/eleitores. **Rejeitado:** WhatsApp Business API; disparo em massa / listas (TSE art. 33); bot que responde sozinho a terceiros.
|
||||
- **Número pessoal por staff (`campaignUser`), não linha institucional.** Cada assessor/coordenador geral pareia o próprio WhatsApp; a liderança vê o remetente que já conhece. **Rejeitado:** chip único da campanha (quebra a relação pessoal — pedido explícito de produto 2026-07-23); um único CG enviando “em nome de” todos os assessores.
|
||||
- **Pareamento no app via QR (estilo WhatsApp Web).** Staff abre `/campanha`, inicia pareamento, escaneia com o celular; sessão fica no sidecar. **Rejeitado:** só `wa.me` forever; pedir senha/OTP inventado; forçar app nativo Meta.
|
||||
- **Teqo permanece system of record.** Mensagens são evidência/auditoria; commits de domínio só após ação humana (D5). **Rejeitado:** auto-criar `plazaUpdate`/`votePledge` sem aceite.
|
||||
- **Sidecar obrigatório (multi-sessão).** N sockets (um por staff vinculado); não vive em Vercel serverless. **Rejeitado:** “só mais uma API route” no Next.
|
||||
- **Não substitui D2 nem `wa.me`.** D2 = push in-app; `wa.me` = fallback se a sessão do ator estiver down. **Rejeitado:** remover convites `wa.me` neste programa.
|
||||
- **Risco ToS/ban é pessoal e explícito.** Consent + copy no pairing avisam que o vínculo usa cliente não oficial e pode afetar a conta WhatsApp da pessoa. **Rejeitado:** esconder o risco; usar conta do deputado/candidato como default.
|
||||
|
||||
## Questões em aberto (programa)
|
||||
|
||||
- **Onde vive o QR no IA?** **Opções:** `/campanha/perfil` | `/campanha/configuracoes/whatsapp` | Sheet no compose D4 (“conectar para enviar”). **Recomendação:** `/campanha/perfil` (self-service, cada um liga o seu) + estado “WhatsApp conectado/desconectado” no compose D4 com deep-link para parear. _(shape D3)_
|
||||
- **LLM para classificar inbound em D5?** **Opções:** regras/heurística | LLM com revisão humana | só triagem manual. **Recomendação:** D5 v1 = heurística + templates + edição humana; LLM só com gatilho.
|
||||
|
||||
## Não escopo (programa)
|
||||
|
||||
- Business API / templates Meta / disparo a base nominal → continua fora de escopo.
|
||||
- Chatbot eleitoral público / atendimento a cidadão → fora.
|
||||
- Número institucional / “WhatsApp da campanha” compartilhado → fora (conflita com a decisão de canal pessoal).
|
||||
- Substituir onboarding presencial / seed (Big Hire) → IMPROVE-APP-PLAN.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Evolution API / Baileys / multi-device / grupos WA.** Mitigação: D3 escolhe **um** adapter; multi-**sessão** (N staff) sim; multi-**número por pessoa** e grupos = Adiado.
|
||||
- **“Sincronizar todo o histórico do chip.”** Mitigação: só mensagens após pairing + filtro por telefones do CRM (e, no inbox, só o que chegou naquela sessão).
|
||||
- **Espionar conversas pessoais do staff com não-lideranças.** Mitigação: allowlist estrita de phones do CRM; não indexar chats fora da allowlist.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- Sessão secundária / segundo chip por pessoa. Revisitar quando: pedido explícito de um assessor com dois números de trabalho.
|
||||
- LLM de classificação. Revisitar quando: D5 v1 com ≥50 rascunhos/semana e taxa de descarte >40% por “categoria errada”.
|
||||
- Coordenador ver inbox agregado de todos os assessores. Revisitar quando: CG pedir visão transversal **e** jurídico ok com acesso cruzado a threads pessoais.
|
||||
|
||||
## Referências
|
||||
|
||||
- `docs/roadmap.md` — Trilha D; Fora de escopo (Business API / massa)
|
||||
- Entrevista CG 2026-07-23 (`output/transcribe/general-coordinator-interview-20260723/`) — “ZAP é o campo”
|
||||
- `docs/IMPROVE-APP-PLAN.md` — Little Hire / leak WhatsApp
|
||||
- `PRODUCT.md` §1 soberania + atalho pragmático; §5 humano no loop
|
||||
- `docs/plans/notifications.md` (D2) — complementar
|
||||
- `src/utilities/phone.ts` — `buildWhatsAppUrl` (fallback `wa.me`)
|
||||
- AGENTS.md — Contact, Consent por chave, campaign access, Vercel
|
||||
113
docs/plans/whatsapp-sugestao-atualizacoes.md
Normal file
113
docs/plans/whatsapp-sugestao-atualizacoes.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# D5 — Sugestões de atualização a partir do WhatsApp inbound
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-23
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha D, item D5; programa [whatsapp-interno-campanha.md](whatsapp-interno-campanha.md))
|
||||
Impeccable: C — superfície nova (inbox + revisão de rascunhos); sem design-ref
|
||||
Appetite: ~2–3 dias eng; inbox staff + draft suggestions + aceite → domínio; **sem** auto-commit
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (princípios 1, 2, 5 humano no loop, 7) / `DESIGN.md` (register `product`) · tema `campaign` · shells `CampaignPageShell`, feeds `PlazaUpdateFeed` / forms `PlazaUpdateForm` · paralelo E11 (menu + aceite/descarte).
|
||||
|
||||
Na implementação: shape → craft → critique → polish (classe C).
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** Assessor que já conversou no ZAP com a liderança e precisa **não redigitar** o reporte no Teqo; Coordenador que quer o quadro atualizado sem caçar print no grupo.
|
||||
- **Job principal:** transformar mensagem inbound em **rascunho editável** (`plazaUpdate` / demanda / nota de voto) e só gravar no domínio após aceite humano.
|
||||
- **Estratégia de cor:** Restrained; fila por frescor, não por “AI score”.
|
||||
- **Edit where you see:** sim — aceitar/editar/descartar no card do rascunho; deep-link para a Praça/liderança.
|
||||
- **Anti-goals:** bot que grava sozinho; LLM obrigatório na v1; inbox estilo WhatsApp Web clone; “caça” a mensagens de não-lideranças.
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
- **Vou apresentar dados?** Sim, superfície neste item (fila de rascunhos + origem).
|
||||
- **Decisões desbloqueadas:**
|
||||
- Assessor: “esta mensagem vira atualização da Praça X, demanda, ou só arquivo?”
|
||||
- Assessor/CG: “aceito o texto sugerido ou edito antes de gravar?”
|
||||
- Staff: “descarto (ruído / off-topic) para não poluir o frescor da Praça?”
|
||||
- **Forma escolhida:** **lista/fila** de itens (mensagem → tipo sugerido → preview) + detalhe com editar/aceitar/descartar. **Rejeitado:** dashboard de % auto-classificados; chart de volume WA; mapa de mensagens.
|
||||
- **Profile:** itens categóricos + texto; dezenas/dia típico (não milhares); granularidade liderança×praça.
|
||||
- **Anti-goals de dado:** sem vanity “msgs processadas”; sem auto-atualizar `lastUpdateAt` sem aceite (frescor mentiroso).
|
||||
|
||||
## Contexto
|
||||
|
||||
O Little Hire falha quando o reporte vive só no ZAP ([IMPROVE-APP-PLAN](../IMPROVE-APP-PLAN.md); entrevista CG: canal real = mensagem). D3 grava inbound no log; D4 cobre outbound. Falta o caminho **ZAP → registro** com humano no loop — o mesmo estatuto de E11 (“sugestão nunca decide”).
|
||||
|
||||
Domínio alvo já existe: `plazaUpdate` (`PlazaUpdateForm` / feed), `campaignDemand`, `votePledge.declaredVotes` (liderança) / estimativas staff (assimetria).
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Inbox staff (`/campanha/whatsapp` ou seção no dashboard): só mensagens inbound da **própria sessão** do ator (o Zap pessoal dele); match a `leadership` por phone; unmatched→triagem.
|
||||
- Gerador de **draft** (heurística v1): classifica intent grosso (`atualizacao | demanda | voto | outro`) + sugere Praça (se liderança tem 1 Praça; se N, pede escolha) + texto pré-preenchido editável.
|
||||
- Aceite cria o documento de domínio na transação (`withPayloadTransaction`), liga `whatsappMessage` → entidade criada (auditoria), respeita access/locks existentes (`plazaUpdate` locks).
|
||||
- Descarte registra motivo curto (opcional na v1) e marca mensagem como `ignored` — não some do log.
|
||||
- Guardrails: **nunca** auto-create; inbox = sessão do ator (não ver thread do colega na v1); advisor ainda filtrável às Praças acessíveis; `leader` não usa inbox; sem processar phones fora do CRM.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Humano no loop obrigatório.** Draft ≠ commit. **Rejeitado:** auto-`plazaUpdate` por NLP; webhook que escreve domínio direto.
|
||||
- **Inbox = sessão pessoal do ator.** Alinhado a D3/D4 (canal pessoal). **Rejeitado:** inbox único da “campanha”; CG ler todas as threads dos assessores na v1 (Adiado no programa).
|
||||
- **Heurística v1, não LLM.** Palavras-chave / botões de tipo manual + textarea. **Rejeitado:** LLM na v1 (custo, indeterminismo, appetite); ver Adiado.
|
||||
- **Teqo é o registro; WA é evidência.** `lastUpdateAt` e versões (C12) só mudam no aceite. **Rejeitado:** usar timestamp da msg WA como frescor oficial sem commit.
|
||||
- **i18n/naming:** `whatsappInboundDraft`, `suggestPlazaUpdateFromMessage`, `acceptWhatsappDraft`; rotas/params em inglês; UI pt-BR (“Sugestões do WhatsApp”).
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Rota dedicada vs. painel?** **Opções:** `/campanha/whatsapp` | aba no dashboard | Sheet na liderança. **Recomendação:** rota `/campanha/whatsapp` (inbox) + entrada “Criar a partir do WhatsApp” no detalhe da liderança se houver msgs pendentes. _(shape fecha layout)_
|
||||
- **Quais tipos de domínio na v1?** **Opções:** só `plazaUpdate` | update+demanda | update+demanda+voto. **Recomendação:** `plazaUpdate` + `campaignDemand` na v1; voto (declared/estimated) só se o texto for inequívoco **e** o aceite abrir o form de pledge existente — senão Adiado. _(assumido — validar)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
In["whatsappMessage inbound<br/>(D3 allowlist)"]
|
||||
Match["match Contact → leadership"]
|
||||
Draft["whatsappInboundDraft<br/>heurística + edição"]
|
||||
Human["Aceitar / Editar / Descartar"]
|
||||
Dom["plazaUpdate / campaignDemand<br/>(+ pledge form)"]
|
||||
In --> Match --> Draft --> Human --> Dom
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **Collection ou table `whatsappInboundDraft`** (status `pending|accepted|discarded`, `suggestedKind`, `plaza`, `body`, rel `whatsappMessage`): migration própria desta fatia **ou** campos no `whatsappMessage` se depth preferir menos collections — **recomendação:** collection draft (ciclo de vida claro; não polui log imutável).
|
||||
- **`suggestDraftFromMessage`** puro + action `acceptWhatsappDraft` / `discardWhatsappDraft` reusando creates existentes de update/demanda (não duplicar invariantes).
|
||||
- **UI** `WhatsappInboxPage` + `WhatsappDraftReviewCard`; reusa `PlazaUpdateForm` fields onde der (depth check).
|
||||
- **Suave C12:** se sinais tipados em `plazaUpdate` existirem, o aceite pode preencher `kind`/sinal — senão texto livre como hoje.
|
||||
- **Migration:** `pnpm migrate:create add_whatsapp_inbound_draft`.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **Dura:** **D3** (inbound log + match phone + Consent).
|
||||
- **Suave:** **D4** (thread/contexto outbound); **C12** (sinais tipados); remodelagem/demandas já em prod.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Auto-resposta / chatbot → fora.
|
||||
- Classificação LLM → Adiado com gatilho (programa).
|
||||
- Envio em massa / apoiadores → fora.
|
||||
- Clone de WhatsApp Web (sync de mídia, status, reactions) → fora.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Resolver Praça quando liderança tem N Praças.** Mitigação: obrigar select no draft; sem “adivinhar” por NLP geográfico.
|
||||
- **Mesclar com motor E11.** Mitigação: filas separadas (E11 = território/meta; D5 = inbox de mensagens); no máximo link “abrir Praça”.
|
||||
- **Reprocessar histórico em massa.** Mitigação: só msgs após feature flag / `draftsEnabledAt`.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **LLM classifier.** Revisitar quando: ≥50 drafts/semana e descarte >40% por tipo errado (critério do programa).
|
||||
- **Sugerir `declaredVotes` / cenários A10.** Revisitar quando: assessores pedirem explicitamente e houver padrão estável de msgs numéricas.
|
||||
- **Mídia → anexo em `plazaUpdate`.** Revisitar quando: D3 passar a persistir mídia.
|
||||
|
||||
## Referências
|
||||
|
||||
- [whatsapp-interno-campanha.md](whatsapp-interno-campanha.md), [whatsapp-canal-fundacao.md](whatsapp-canal-fundacao.md), [whatsapp-envio-liderancas.md](whatsapp-envio-liderancas.md)
|
||||
- `src/collections/PlazaUpdate.ts`, `src/components/campaign/PlazaUpdateForm.tsx`
|
||||
- `docs/plans/motor-de-sugestoes.md` (E11) — precedente humano no loop
|
||||
- `docs/IMPROVE-APP-PLAN.md` — Little Hire
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
- AGENTS.md — transactions, access, Consent
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Roadmap — Teqo
|
||||
|
||||
Atualizado em: 2026-07-23 (Admin Payload: export CSV de assinaturas e contatos entregue — [plano](plans/exportar-csv-assinaturas.md))
|
||||
Atualizado em: 2026-07-23 (D3–D5: canal WhatsApp **pessoal** por staff + QR — [plano-mestre](plans/whatsapp-interno-campanha.md); A10 cenários — [plano](plans/cenarios-estimativa-votos.md); export CSV admin entregue)
|
||||
|
||||
Registro canônico dos **próximos** planos e débitos. Histórico de entregas: resumo abaixo + planos em [`docs/plans/`](plans/) + notebook [`.cursor/rules/projects/nucleos-eleitorais.mdc`](../.cursor/rules/projects/nucleos-eleitorais.mdc).
|
||||
|
||||
|
|
@ -21,13 +21,13 @@ Registro canônico dos **próximos** planos e débitos. Histórico de entregas:
|
|||
- Um único app Next.js: site `(frontend)`, admin `(payload)`, `/campanha` `(campaign)`. Sem Rust separado. Vercel por enquanto.
|
||||
- Doações só via CTA QueroApoiar (`apoiar.me/jorgesolla`). Pessoa = `Contact` + joins — nunca cadastro paralelo.
|
||||
- **Praça = unidade operacional pré-definida** (2026-07-20): município, exceto Salvador (19 Praças-zona) e Camaçari (2 Praças-zona); zonas compartilhadas cortadas na divisa municipal. Ninguém cria/edita geografia no app. Substitui o Núcleo Eleitoral — plano [remodelagem-pracas.md](plans/remodelagem-pracas.md).
|
||||
- Roles: `coordinator` ("Coordenador Geral"), `advisor` ("Assessor"), `leader` ("Liderança"); assessor vê só as Praças que administra. Sem disparo em massa (Res. TSE 23.610 art. 33 / Meta).
|
||||
- Roles: `coordinator` ("Coordenador Geral"), `advisor` ("Assessor"), `leader` ("Liderança"); assessor vê só as Praças que administra. Sem disparo em massa (Res. TSE 23.610 art. 33 / Meta). Comunicação **1:1 interna** staff↔lideranças via bridge WhatsApp não oficial = programa **D3–D5** (não é Business API nem blast).
|
||||
|
||||
## Onda 0 — caminho crítico para dados reais
|
||||
|
||||
Textos provisórios de Consent + `/privacidade` auto-provisionados ([onda-0.md](plans/onda-0.md)); **hold de PII real** até o lote jurídico final. Inalterada pela remodelagem (mesmas chaves `Consent`).
|
||||
|
||||
1. **Lote jurídico único** _(externo)_ — textos finais (substituem provisórios) + base LGPD art. 11: `lideranca-autopreenchimento`, `apoiador-cadastro`, `apoiador-intencao-voto`, `campanha-notificacoes-push`, Aviso de Privacidade, avaliação RIPD.
|
||||
1. **Lote jurídico único** _(externo)_ — textos finais (substituem provisórios) + base LGPD art. 11: `lideranca-autopreenchimento`, `apoiador-cadastro`, `apoiador-intencao-voto`, `campanha-notificacoes-push`, **`campanha-whatsapp-canal`** (D3 — armazenamento/processamento do canal interno staff↔lideranças; se o lote já tiver fechado sem esta chave, entra no próximo passe com a assessoria — não abrir rodada jurídica só por ela), Aviso de Privacidade, avaliação RIPD.
|
||||
2. **Smoke pós-deploy** — `NEXT_PUBLIC_SITE_URL` HTTPS, login `/campanha`, Praça de teste; checklist no AGENTS.md.
|
||||
3. **Ativação com dados reais** assim que (1) liberar — lideranças/apoiadores reais e import em massa.
|
||||
4. **Onboarding do time** — usuários `coordinator`/`advisor`, primeiras Praças assumidas, treino de campo.
|
||||
|
|
@ -51,18 +51,18 @@ Feedback da coordenação (2026-07-20) invalidou o modelo de Núcleo: a campanha
|
|||
|
||||
O discovery literatura→persona→entrevista ([relatório aprovado](research/relatorio-entrevista-persona-campanha.md); compêndio com ~67 fontes em [docs/research/](research/)) fixou o kernel: a disputa de DF é conta de quociente fragmentada; o gargalo é converter lealdade de campo em voto nominal praça a praça via rede; % estadual absoluto é anti-métrica. O produto deve entregar **inteligência, não planilha chique**: metas derivadas, leitura relativa, fila de decisão priorizada, sugestões dado→decisão com humano no loop, registro ex-ante auditável. Plano-mestre: [inteligencia-campanha.md](plans/inteligencia-campanha.md) (incl. gaps de dados G1–G10 e desenho canônico da fila).
|
||||
|
||||
| ID | Fatia | Plano | Entrega essencial | Classe | Appetite | Janela | Depende de |
|
||||
| --- | ---------------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------- | --------- | ---------------------------------- |
|
||||
| E8 | Conta da cadeira | [detalhes](plans/conta-da-cadeira.md) | Global `campaignGoals`; potencial por praça (válidos projetados, captura do campo, roll-off, headroom); decomposição meta→praça; cobertura Σpledges÷meta | B | ~2d | 2 | deploy remodelagem |
|
||||
| E9 | Fila de alocação | [detalhes](plans/fila-de-alocacao.md) | Lista de decisão (7 colunas), ordenação por déficit descoberto + risco por frescor, "coluna da vergonha" | B | ~1,5d | 2 | E8 |
|
||||
| C12 | Registro-fundação | [detalhes](plans/registro-fundacao.md) | Versions em `votePledge`; sinais tipados no `plazaUpdate`; `actionPlan.origin`+custo; collection `allocationDecision` | B | ~2d | 2 | deploy remodelagem (paralelo a E8) |
|
||||
| E10 | Classificação territorial relativa | [detalhes](plans/classificacao-territorial-relativa.md) | Âncoras relativas (LQ / share da cadeira marginal), multi-eixo; substitui 35/20/10 para DF | B | ~1d | 3 | E8 |
|
||||
| B13 | Escala relativa no mapa | [detalhes](plans/escala-relativa-mapa.md) | Quantis do candidato (default) + LQ + rank; símbolo proporcional por votos em jogo; % válidos mantido | B | ~2d | 3 | E8, E10 |
|
||||
| E14 | Níveis de envolvimento N0–N4 | [detalhes](plans/niveis-de-envolvimento.md) | `engagementLevel` com histerese e sinais de reversão; staff-only (vocabulário duplo) | B | ~1d | 3 | C12 |
|
||||
| E11 | Motor de sugestões v1 | [detalhes](plans/motor-de-sugestoes.md) | 8 padrões (P1/P2/P3/P5/P6/P7/K-A/K-B), menu com estatuto, aceitar/descartar → `allocationDecision`, triagem 1–5 | C | ~3d | 3 | E8, E9, E10, C12 |
|
||||
| E12 | Camada TI | [detalhes](plans/camada-territorios-identidade.md) | Rollups com salvaguardas MAUP, benchmark intra-TI (gatilhos T no motor = fase 2 de E11) | B | ~1,5d | 3 | E8 |
|
||||
| E13 | Planejador de presença/giros | [detalhes](plans/planejador-de-giros.md) | Elegibilidade (5 condições ✓/—), fases construção/consolidação/ativação, compositor de giro, visita pedida×justificada | C | ~1,5d | 3 | E8, C12 |
|
||||
| E15 | Backtest pós-eleição | [detalhes](plans/backtest-pos-eleicao.md) | Pledge (trajetória) vs. resultado por zona; calibração de limiares | A | ~1d | pós-04/10 | C12, TSE 2026 |
|
||||
| ID | Fatia | Plano | Entrega essencial | Classe | Appetite | Janela | Depende de |
|
||||
| --- | ---------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------- | --------- | ---------------------------------------------- |
|
||||
| E8 | Conta da cadeira | [detalhes](plans/conta-da-cadeira.md) | Global `campaignGoals`; potencial por praça (válidos projetados, captura do campo, roll-off, headroom); decomposição meta→praça; cobertura Σpledges÷meta (cenário default = média) | B | ~2d | 2 | **A10**, deploy remodelagem |
|
||||
| E9 | Fila de alocação | [detalhes](plans/fila-de-alocacao.md) | Lista de decisão (7 colunas), ordenação por déficit descoberto + risco por frescor, "coluna da vergonha" | B | ~1,5d | 2 | E8 |
|
||||
| C12 | Registro-fundação | [detalhes](plans/registro-fundacao.md) | Versions em `votePledge`; sinais tipados no `plazaUpdate`; `actionPlan.origin`+custo; collection `allocationDecision` | B | ~2d | 2 | deploy remodelagem (paralelo a E8; suave: A10) |
|
||||
| E10 | Classificação territorial relativa | [detalhes](plans/classificacao-territorial-relativa.md) | Âncoras relativas (LQ / share da cadeira marginal), multi-eixo; substitui 35/20/10 para DF | B | ~1d | 3 | E8 |
|
||||
| B13 | Escala relativa no mapa | [detalhes](plans/escala-relativa-mapa.md) | Quantis do candidato (default) + LQ + rank; símbolo proporcional por votos em jogo; % válidos mantido | B | ~2d | 3 | E8, E10 |
|
||||
| E14 | Níveis de envolvimento N0–N4 | [detalhes](plans/niveis-de-envolvimento.md) | `engagementLevel` com histerese e sinais de reversão; staff-only (vocabulário duplo) | B | ~1d | 3 | C12 |
|
||||
| E11 | Motor de sugestões v1 | [detalhes](plans/motor-de-sugestoes.md) | 8 padrões (P1/P2/P3/P5/P6/P7/K-A/K-B), menu com estatuto, aceitar/descartar → `allocationDecision`, triagem 1–5 | C | ~3d | 3 | E8, E9, E10, C12 |
|
||||
| E12 | Camada TI | [detalhes](plans/camada-territorios-identidade.md) | Rollups com salvaguardas MAUP, benchmark intra-TI (gatilhos T no motor = fase 2 de E11) | B | ~1,5d | 3 | E8 |
|
||||
| E13 | Planejador de presença/giros | [detalhes](plans/planejador-de-giros.md) | Elegibilidade (5 condições ✓/—), fases construção/consolidação/ativação, compositor de giro, visita pedida×justificada | C | ~1,5d | 3 | E8, C12 |
|
||||
| E15 | Backtest pós-eleição | [detalhes](plans/backtest-pos-eleicao.md) | Pledge (trajetória) vs. resultado por zona; calibração de limiares | A | ~1d | pós-04/10 | C12, TSE 2026 |
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
|
|
@ -88,6 +88,8 @@ flowchart TD
|
|||
R2 --> A9["A9 Estimativa votos ✓"]
|
||||
A9 --> A9plus["A9+ Loader lista ✓"]
|
||||
A9plus --> B9["B9 Edição rápida lista ✓"]
|
||||
A9 --> A10["A10 Cenários estimativa<br/>pessimista/média/otimista"]
|
||||
R2 --> A10
|
||||
R2 -.-> B7["B7 Mapa filtrado ✓"]
|
||||
B7 --> B12["B12 Aproximar mapa<br/>ao filtro ✓"]
|
||||
R2 -.-> B8["B8 Polígonos Praças-zona<br/>(SSA/CMS)"]
|
||||
|
|
@ -95,10 +97,17 @@ flowchart TD
|
|||
R2 -.-> B11["B11 Escala % válidos<br/>no mapa ✓"]
|
||||
A9 -.métrica 2026.-> B10
|
||||
A9 -.numerador 2026.-> B11
|
||||
A10 -.métrica 2026 por cenário.-> B10
|
||||
B10 -.hover densos.-> B6["B6 setStyle incremental ✓"]
|
||||
B11 -.troca escala.-> B6
|
||||
JUR -.chave push.-> D2
|
||||
C2prod --> C5["C5 GOTV (validar)"]
|
||||
R2 --> D3["D3 WA canal<br/>fundação"]
|
||||
JUR -.chave WA canal.-> D3
|
||||
D3 --> D4["D4 WA envio 1:1"]
|
||||
D3 --> D5["D5 WA → rascunhos"]
|
||||
D4 -.thread.-> D5
|
||||
C2prod -.telefones reais.-> D3
|
||||
|
||||
subgraph Intel["Inteligência de campanha — discovery 2026-07-21"]
|
||||
E8i["E8 Conta da cadeira"]
|
||||
|
|
@ -114,7 +123,9 @@ flowchart TD
|
|||
end
|
||||
|
||||
R4 --> E8i
|
||||
A10 --> E8i
|
||||
R5 --> C12i
|
||||
A10 -.versions no group.-> C12i
|
||||
E8i --> E9i --> E11i
|
||||
C12i --> E11i
|
||||
C12i --> E14i
|
||||
|
|
@ -131,23 +142,23 @@ flowchart TD
|
|||
E11i -.decisões acumuladas.-> E15i
|
||||
```
|
||||
|
||||
Paralelizáveis agora: fill-ins (O0+, RS+, …). ~~**A9** / **A9+** / **B9** / **B7** / **B10** / **B11** / **B6** / **B12** / **filtros-auto** / **C8** F4~~ entregues 2026-07-21.
|
||||
Paralelizáveis agora: **A10** (pós-A9; paraleliza com C12 / smoke pós-deploy), fill-ins (O0+, RS+, …). **D3** (baixa/média prioridade; após remodelagem em prod + folga — não bloqueia E8/E9). ~~**A9** / **A9+** / **B9** / **B7** / **B10** / **B11** / **B6** / **B12** / **filtros-auto** / **C8** F4~~ entregues 2026-07-21.
|
||||
|
||||
### Sequência por janela
|
||||
|
||||
**Janela 1 — agora → 05/08 (convenções):** ~~R0 → R1 → R2~~ entregues; ~~**A9** estimativa de votos~~ / ~~**B9** edição rápida na lista~~ / ~~**B7** mapa filtrado~~ / ~~**B10** hover+click-nav~~ / ~~**B11** escala % dos válidos~~ / ~~**B12** aproximar mapa ao footprint filtrado~~ / ~~**filtros-auto** lista de Praças~~ entregues; **deploy da remodelagem** (revisar SQL destrutivo da migração antes do build) + smoke em produção; R6 critique/polish; Onda 0 jurídica em paralelo.
|
||||
**Janela 1 — agora → 05/08 (convenções):** ~~R0 → R1 → R2~~ entregues; ~~**A9** estimativa de votos~~ / ~~**B9** edição rápida na lista~~ / ~~**B7** mapa filtrado~~ / ~~**B10** hover+click-nav~~ / ~~**B11** escala % dos válidos~~ / ~~**B12** aproximar mapa ao footprint filtrado~~ / ~~**filtros-auto** lista de Praças~~ entregues; **deploy da remodelagem** (revisar SQL destrutivo da migração antes do build) + smoke em produção; **A10** cenários pessimista/média/otimista (pode puxar para cá pós-deploy — desbloqueia E8); R6 critique/polish; Onda 0 jurídica em paralelo.
|
||||
|
||||
**Janela 2 — 05/08 → 16/08 (pré-propaganda):** C2 dados reais assim que o jurídico liberar; **E8** conta da cadeira → **E9** fila de alocação, com **C12** registro-fundação em paralelo (migrations cedo, longe do congelamento); D2 se sobrar folga.
|
||||
**Janela 2 — 05/08 → 16/08 (pré-propaganda):** C2 dados reais assim que o jurídico liberar; **A10** (se não fechou na Janela 1) → **E8** conta da cadeira → **E9** fila de alocação, com **C12** registro-fundação em paralelo (migrations cedo, longe do congelamento); D2 se sobrar folga.
|
||||
|
||||
**Janela 3 — 16/08 → set:** A6 dobradinha (pós-TSE 15/08), **E10** classificação relativa → **B13** escala relativa no mapa, **E14** níveis N0–N4, **E11** motor de sugestões v1, **E12** camada TI, **E13** planejador de giros; **B8** polígonos das Praças-zona Salvador/Camaçari (F1 catálogo de bairros shipável antes; F2 dissolve), débitos sobreviventes (abaixo).
|
||||
**Janela 3 — 16/08 → set:** A6 dobradinha (pós-TSE 15/08), **E10** classificação relativa → **B13** escala relativa no mapa, **E14** níveis N0–N4, **E11** motor de sugestões v1, **E12** camada TI, **E13** planejador de giros; **B8** polígonos das Praças-zona Salvador/Camaçari (F1 catálogo de bairros shipável antes; F2 dissolve), débitos sobreviventes (abaixo). Se houver folga **e** Little Hire ainda vazar para o ZAP: começar **D3** (fundação do canal) — não compete com E8/E9 na Janela 2.
|
||||
|
||||
**Janela 4 — set → 04/10:** C5 GOTV _(validar)_, congelamento ~20/09 (só bugfix/dados). **E15** backtest pós-eleição (após 04/10).
|
||||
**Janela 4 — set → 04/10:** C5 GOTV _(validar)_, congelamento ~20/09 (só bugfix/dados). **D4** envio 1:1 e **D5** inbox→rascunhos só se D3 estiver estável **antes** do congelamento (senão Adiar pós-04/10). **E15** backtest pós-eleição (após 04/10).
|
||||
|
||||
### Cortes seguros / não cortáveis
|
||||
|
||||
**Não cortáveis:** Onda 0 (jurídico/Consent); R1–R2 (sem eles a vertical não reflete a operação real); C2 dados reais; assimetria declarado×estimado (relação de campo); ~~**A9**~~ (total esperado da Praça — entregue 2026-07-21); **E8**+**E9**+**C12** (a conta da cadeira, a fila e o registro ex-ante são o mínimo de "inteligência, não planilha" — e C12 é irrecuperável se não registrar durante a campanha).
|
||||
**Não cortáveis:** Onda 0 (jurídico/Consent); R1–R2 (sem eles a vertical não reflete a operação real); C2 dados reais; assimetria declarado×estimado (relação de campo); ~~**A9**~~ (total esperado da Praça — entregue 2026-07-21); **A10** (faixa pessimista/média/otimista — semântica operacional pedida pela coordenação; E8 consome); **E8**+**E9**+**C12** (a conta da cadeira, a fila e o registro ex-ante são o mínimo de "inteligência, não planilha" — e C12 é irrecuperável se não registrar durante a campanha).
|
||||
|
||||
**Cortes seguros** (se o prazo apertar, nesta ordem): **E13** planejador de giros (agenda segue manual com J-A/J-B como guia); **E12** camada TI (rollup manual por lista); **E15** (pós-eleição por definição — cortar = perder o aprendizado 2030); **E11** motor v1 (manter fila E9 sem sugestões); **B13** símbolo proporcional (manter quantis/LQ como escala); **E14** (manter `priority` alta/normal); R4 mapa comparativo (manter tabela comparativa); painel de detalhe por zona no mapa; R3 organizações (manter demandas); resultado de plano com mídia (manter texto); Eleitorado/IBGE na Praça; D2 push (manter sino); A6; **B8** (F2 polígonos; manter F1 bairros na Praça se já entregue — mapa continua agregado no município); débitos/fill-ins. ~~**B9** / **B10** / **B11** / **B6** / **B12**~~ / ~~**export CSV de assinaturas/contatos**~~ (entregues — não cortar).
|
||||
**Cortes seguros** (se o prazo apertar, nesta ordem): **D5** inbox→rascunhos (manter registro manual + `wa.me`); **D4** envio bridge (manter `wa.me`); **D3** fundação do canal (atalho `wa.me` continua); **E13** planejador de giros (agenda segue manual com J-A/J-B como guia); **E12** camada TI (rollup manual por lista); **E15** (pós-eleição por definição — cortar = perder o aprendizado 2030); **E11** motor v1 (manter fila E9 sem sugestões); **B13** símbolo proporcional (manter quantis/LQ como escala); **E14** (manter `priority` alta/normal); R4 mapa comparativo (manter tabela comparativa); painel de detalhe por zona no mapa; R3 organizações (manter demandas); resultado de plano com mídia (manter texto); Eleitorado/IBGE na Praça; D2 push (manter sino); A6; **B8** (F2 polígonos; manter F1 bairros na Praça se já entregue — mapa continua agregado no município); débitos/fill-ins. ~~**B9** / **B10** / **B11** / **B6** / **B12**~~ / ~~**export CSV de assinaturas/contatos**~~ (entregues — não cortar).
|
||||
|
||||
## Já entregue (resumo)
|
||||
|
||||
|
|
@ -185,12 +196,17 @@ Paralelizáveis agora: fill-ins (O0+, RS+, …). ~~**A9** / **A9+** / **B9** / *
|
|||
|
||||
## Próximos — sobreviventes e pós-remodelagem
|
||||
|
||||
- **Programa Inteligência de campanha** (E8–E15, B13, C12) · fatias, gaps de dados e fila canônica no [plano-mestre](plans/inteligencia-campanha.md) · embasamento: [docs/research/](research/) (relatório aprovado 2026-07-21)
|
||||
- **A10** Cenários de estimativa (pessimista / média / otimista) em pledge + total da Praça + lista/mapa · liderança declara um · desbloqueia E8 · Janela 1–2 · não cortável · [plano](plans/cenarios-estimativa-votos.md)
|
||||
- **Programa Inteligência de campanha** (E8–E15, B13, C12) · fatias, gaps de dados e fila canônica no [plano-mestre](plans/inteligencia-campanha.md) · embasamento: [docs/research/](research/) (relatório aprovado 2026-07-21) · **E8 depende de A10**
|
||||
- **A6** dobradinha 2026 automática quando o TSE publicar candidaturas · gatilho externo: pós-15/08 · reenquadrar para Praça · alimenta **E13** · [plano](plans/insight-dobradinha-2026.md)
|
||||
- **B5 F2–F3** cache CLI compartilhado + factory mun/TI (scripts continuam) · [plano](plans/escala-dry-pos-b2.md)
|
||||
- **B8** Polígonos das Praças-zona (Salvador ZE 1–19 / Camaçari ZE 170–171): ~~F1 catálogo zona→bairros + UI na Praça~~ (entregue 2026-07-21); F2 prep catálogo (~½ dia) + dissolve IBGE/malha → TopoJSON no mapa · Janela 3 · cortável (F2) · [plano](plans/poligonos-pracas-zona.md)
|
||||
- **C5** operação dia D / GOTV _(validar com produto)_ · design [`Dia-D-GOTV`](design-refs/latest/Dia-D-GOTV.png) · depende de C2 dados reais
|
||||
- **D2** push + sino in-app · soft: chave `campanha-notificacoes-push` (Onda 0) · [plano](plans/notifications.md)
|
||||
- **Programa WhatsApp interno (D3–D5)** · baixa/média prioridade · 1:1 staff↔lideranças via bridge **não oficial** · **número pessoal** de cada assessor/CG (QR no app, parece chat pessoal) · não Business API / não massa / não chip institucional · fecha Little Hire · plano-mestre [whatsapp-interno-campanha.md](plans/whatsapp-interno-campanha.md)
|
||||
- **D3** fundação multi-sessão (QR por `campaignUser` + log + Consent `campanha-whatsapp-canal`) · [plano](plans/whatsapp-canal-fundacao.md)
|
||||
- **D4** envio 1:1 pela sessão do ator · depende de D3 · [plano](plans/whatsapp-envio-liderancas.md)
|
||||
- **D5** inbox da própria sessão → rascunhos (`plazaUpdate`/demanda) com humano no loop · depende de D3 · [plano](plans/whatsapp-sugestao-atualizacoes.md)
|
||||
- **R6** critique/polish visual da vertical remodelada (ciclo /impeccable completo por superfície; smoke visual coordenador feito em 2026-07-21) · gatilho: antes de 16/08 · (opcional: mover filtros acima do mapa)
|
||||
- **Fill-ins:** O0+ ([plano](plans/escala-dry-pos-onda0.md)) · RS+ auth read leve + shells de senha ([plano](plans/escala-dry-pos-reset-senha-perfil.md)) · higiene PascalCase
|
||||
|
||||
|
|
@ -229,7 +245,8 @@ Paralelizáveis agora: fill-ins (O0+, RS+, …). ~~**A9** / **A9+** / **B9** / *
|
|||
- Serviço Rust separado; self-host/Coolify enquanto Vercel atender; doações in-app
|
||||
- Geocodificação de seções eleitorais / Praça = seção (polígonos **aproximados** das 21 Praças-zona SSA/CMS = **B8**, sem seções)
|
||||
- PWA do site/`/admin`; PostGIS sem query espacial real
|
||||
- WhatsApp Business API / disparo em massa; previsão estatística de votos neste ciclo
|
||||
- WhatsApp Business API / disparo em massa / blast a apoiadores ou eleitores (Res. TSE 23.610 art. 33; Meta veda WABA político). **Exceto** o programa **D3–D5**: bridge não oficial **1:1 interno** staff↔lideranças já no CRM (ver [whatsapp-interno-campanha.md](plans/whatsapp-interno-campanha.md)) — risco ToS/operacional explícito nos planos; não é substituto de WABA
|
||||
- Previsão estatística de votos neste ciclo
|
||||
- Import automático de planilhas de projeção (decisão E4 mantida: dados estratégicos via UI)
|
||||
|
||||
## Fontes
|
||||
|
|
|
|||
Loading…
Reference in a new issue