mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 17:43:04 +00:00
Merge pull request #496 from fsolla/plans/plan-issue-10
plan(C99/C100): identidade de pessoas via Contact + lista unificada
This commit is contained in:
commit
3f7f0cc552
2 changed files with 152 additions and 0 deletions
66
docs/plans/pessoas-identidade-contact.md
Normal file
66
docs/plans/pessoas-identidade-contact.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Identidade: toda pessoa da campanha tem uma ficha Contact
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-09
|
||||
Issue: #494
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: A — sem superfície nova; campo no admin + fluxo de criação de staff
|
||||
Canvas UI: N/A — sem UI
|
||||
Appetite: ~1–1,5 dia eng; uma migration aditiva
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O staff da campanha (coordenador, assessor, candidato) vive em `campaignUser` com nome, e-mail e celular **duplicados** e sem vínculo com `Contact` — a mesma pessoa física pode existir como assessor, liderança e dobradinha em fichas separadas e sem ligação entre si. A lista unificada de pessoas (C100) precisa do vínculo para mostrar a mesma pessoa uma vez só. Dobradinhas já apontam para `Contact` obrigatoriamente (B163); o gap é o usuário do app. E a liderança ainda não tem **assessores responsáveis** — o vínculo entre pessoas que a coluna Assessorado da C100 mostra (dobradinhas já têm, em `stateDeputy.advisors`).
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** coordenação e equipe que administram pessoas e contas de acesso; a C100 é a consumidora direta do vínculo.
|
||||
- **Job principal:** toda pessoa com papel na campanha aponta para UMA ficha `Contact`; papéis múltiplos = a mesma ficha.
|
||||
- **Fluxo desejado:** ao criar/editar um usuário do app (admin ou criação inline de assessor), a ficha `Contact` é criada ou vinculada — sem pedir nome/e-mail/celular duas vezes; o vínculo é visível e editável no admin. A C100 passa a ler a pessoa pela ficha.
|
||||
- **Anti-goals de produto:** não vira segundo cadastro de pessoa; não muda login, senha, sessão, passkeys, invites nem notificações; não mexe no consentimento de lideranças/apoiadores.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- `campaignUser` ganha vínculo com `Contact` (1:1, opcional no dado — obrigatório no fluxo de criação de staff: cria ou vincula ficha).
|
||||
- Liderança ganha campo de **assessores responsáveis** (hasMany staff, mesmo padrão de `stateDeputy.advisors`) — habilita a coluna Assessorado da C100.
|
||||
- Sem mudança de auth: login por e-mail/celular, token, sessão, WebAuthn, convite e notificação funcionam como hoje.
|
||||
- Dedupe: staff vinculado à mesma ficha de uma liderança/dobradinha aparece UMA vez na C100.
|
||||
- Guardrails de produto: leader lockdown intacto; consentimentos (liderança/apoiador) intactos; `leadership.user` ("Acesso ao app") permanece.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
Dados: N/A — sem métrica; habilita o dedupe da C100 (um vínculo, não um número).
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `src/collections/CampaignUser.ts` (campo de vínculo), `src/collections/Leadership.ts` (assessores responsáveis, mesmo padrão de `stateDeputy.advisors`), `src/utilities/campaignAccess.ts` (acesso aos campos), fluxos de criação de usuário do app (admin e popover de assessores B154), precedente de dedupe em `src/utilities/contactPhoneInvariant.ts`.
|
||||
- **Precedente a olhar:** B163 (dobradinha com `contact` obrigatório + backfill) e B154 (criação inline de assessor).
|
||||
- **Risco de acoplamento:** `campaignUser` é collection de auth — qualquer hook novo roda sobre o documento de sessão; o executor deve respeitar o padrão `afterRead`/`beforeChange` existente e nunca expor campos privados de auth.
|
||||
|
||||
## Dependências
|
||||
|
||||
Nenhuma. Pré-requisito da C100.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Dedupe/reconciliação de `Contact` duplicados no dado histórico (mesma pessoa física em duas fichas) — aceito e visível na C100; reconciliação segue pós-eleição junto da multi-tenancy.
|
||||
- Remover `leadership.user`; renomear `contact` → person; entidade `campaign` (multi-tenancy) — pós-eleição.
|
||||
- Tocar `stateDeputy` — já vinculado (B163).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Vincular staff existente por heurística nome+telefone no backfill.** Erros de dedupe silenciosos e difíceis de desfazer. **Corte:** vínculo por decisão explícita (criação/edição); reconciliação em massa fica para o pós-eleição.
|
||||
- **"Já que está mexendo, deduplica tudo".** Explosão de escopo e risco em janela eleitoral. **Corte:** só o vínculo novo.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Reconciliação do staff já existente:** link manual por demanda vs varredura nome+telefone agora? **Recomendação:** manual/por demanda; varredura junto da reconciliação geral de Contacts pós-eleição. _(assumido — validar no gate)_
|
||||
- **Assessores responsáveis da liderança:** hasMany (padrão de `stateDeputy.advisors`) vs um único responsável? **Recomendação:** hasMany — consistente com a dobradinha e com `activity.advisors`. _(assumido — validar no gate)_
|
||||
|
||||
## Referências
|
||||
|
||||
- [pessoas-lista-unificada.md](pessoas-lista-unificada.md) (C100, consumidora)
|
||||
- B154 (criar assessor inline), B163 (dobradinhas com contato), `docs/plans/simplificar-modelo-lideranca.md` (histórico: unificação rejeitada em fatia — esta entrega é o caminho por vínculos)
|
||||
- `AGENTS.md` — convenção Contact (pessoa normalizada + joins), auth `campaignUser` isolada
|
||||
86
docs/plans/pessoas-lista-unificada.md
Normal file
86
docs/plans/pessoas-lista-unificada.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Pessoas: lista unificada de pessoas da campanha
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-09
|
||||
Issue: #495
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: C — fluxo novo: nova rota + tabela + omnibox + filtros salvos
|
||||
Canvas UI: /home/fsolla/.cursor/projects/home-fsolla-Code-teqo/canvases/plan-c100-ui-draft.canvas.tsx
|
||||
Appetite: ~3–4 dias eng
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
A mesa (coordenação e assessores) precisa ver todas as pessoas da campanha num lugar só — lideranças, dobradinhas e staff — sem saltar entre `/liderancas`, `/dobradinhas` e `/assessores`. A mesma pessoa com vários papéis aparece **uma vez**, com as capacidades expressas por território — não por uma coluna de "papel".
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** coordenador/assessor na mesa (staff).
|
||||
- **Job principal:** encontrar a pessoa e ver, num relance, onde ela atua (assessora / liderança / dobradinha) e em quais municípios — e refinar o recorte.
|
||||
- **Fluxo desejado:** abre `/campanha/pessoas` → vê as pessoas com as colunas territoriais → filtra na omnibox (capacidade, município) → salva o recorte como atalho (mesmo padrão de Municípios, B18).
|
||||
- **Anti-goals de produto:** não vira spreadsheet mode; não é segundo cadastro de pessoa (a ficha continua sendo `Contact`); não substitui as listas especializadas.
|
||||
|
||||
## Decisões de produto (travadas com o humano em 2026-08-09)
|
||||
|
||||
- **Colunas:** **Nome** (com partido entre parênteses quando a pessoa é dobradinha com partido — ex. "Maria (PT)"), **Contato** (telefone em destaque + e-mail como segunda linha discreta; sem telefone, o e-mail vira a linha principal), **Assessora** (municípios da carteira), **Lidera** (municípios da liderança), **Aliada em** (municípios com dobradinha), **Assessorado** (assessores responsáveis — dobradinha e liderança), **Base** (cidade do `Contact` quando houver) e **Ações** (sem título: WhatsApp `wa.me` só para quem tem telefone + convite de cadastro na plataforma + apagar pessoa). Sem coluna de papel/kind; sem coluna de partido.
|
||||
- **Sem apoiadores na lista (v1).** `/apoiadores` permanece e continua sendo a superfície deles.
|
||||
- **Merge por `Contact`:** uma pessoa física = uma linha; capacidades = colunas preenchidas. Coordenador/candidato entram pela carteira real (`municipality.advisors`); coluna vazia = não assessora.
|
||||
- **Facets da omnibox derivam das colunas:** "É assessora / É liderança / É dobradinha" + filtros de município (e os filtros já existentes por domínio, ex. status de apoio em lideranças).
|
||||
- **Outras listas permanecem.** A lista de Municípios é a referência estrutural (omnibox, filtros salvos, colunas).
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- `/campanha/pessoas` lista e filtra pessoas com as 8 colunas; a mesma pessoa em 2+ papéis = 1 linha.
|
||||
- Ações: WhatsApp (`wa.me`) apenas quando há telefone registrado no `Contact`; quem não tem telefone não tem ação.
|
||||
- Ações: convite de cadastro na plataforma reusa o fluxo `autopreenchimento` das lideranças (`createCampaignInvite` + `LeadershipInviteRowAction`, consent fail-closed), disponível para quem tem engajamento de liderança; sempre 1:1 por linha (Res. TSE 23.610 art. 33 — sem disparo em massa).
|
||||
- Ações: **Apagar pessoa** — cascata destrutiva transacional (ficha `Contact` + liderança + votos declarados + dobradinha + apoiadores + convites + conta de acesso vinculada), precedida de **confirmação explícita** que lista tudo o que será removido; contas de coordenação/candidato nunca apagáveis.
|
||||
- Filtros salvos funcionam no padrão B18 (localStorage + submenu no sidebar), especializados para Pessoas — 2º call site do padrão, sem genericizar (FD2 vetou o sistema genérico).
|
||||
- Escopo de acesso preservado: assessor vê só a carteira dele; leader não acessa (rota staff, lockdown).
|
||||
- Nenhuma rota existente muda; `/apoiadores`, `/liderancas`, `/dobradinhas`, `/assessores` intactos.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — as colunas expressam **vínculos** (território/partido), não métricas. Nenhum agregado de pessoa entra na superfície.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** rota nova `src/app/(campaign)/campanha/(app)/pessoas/`; shells do Pass 2 W1 (URL state, omnibox, tabela, paginação); utilitários por domínio existentes (leadership/stateDeputy/advisor); padrão B18 (`municipalitySavedFilters.ts`, `listQueryMatch.ts`); chips de relação (B159); entrada no sidebar.
|
||||
- **Precedente a olhar:** lista de Municípios (referência), B91 (busca global multi-entidade como agregador), B18 (filtros salvos), B159 (colunas de relação), B17 (seletor de colunas para conter a largura), share kit `wa.me` de apoiadores (precedente do botão Ações).
|
||||
- **Risco de acoplamento:** o merge por `Contact` no servidor deve aplicar o escopo de acesso **por capacidade** (cada query com o where existente do domínio) — nunca um escopo alargado; leader lockdown é o limite da rota.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **C99** ([pessoas-identidade-contact.md](pessoas-identidade-contact.md)) — sem o vínculo `campaignUser → Contact` e o campo de assessores responsáveis da liderança, as colunas Assessorado e Ações não completam.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Apoiadores na lista — revisitar pós-eleição, junto da multi-tenancy.
|
||||
- Remover as listas atuais; filtros salvos genéricos ou sincronizados no servidor; dedupe de `Contact` duplicados existentes (linhas duplicadas visíveis e aceitas no v1); rota para leader; entidade `campaign`.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Genericizar filtros salvos.** FD2 já vetou; o padrão B18 é o teto. **Corte:** especializar (2º call site do padrão).
|
||||
- **Incluir apoiadores "para completar".** A lista perde o foco e o merge fica mais caro. **Corte:** v1 sem; revisitar com evidência de uso.
|
||||
- **Coluna de partido própria.** O humano cortou — partido só como sufixo do nome.
|
||||
- **Células com muitos municípios.** Chips truncados com "+N" (padrão B159) e colapso mobile (B172).
|
||||
- **Cascata de apagar esquecer uma relação.** Cada relação com FK necessária (votos, convites, conta de acesso) precisa de passo explícito na transação — a coleção `campaignUser` já traz hooks de cascata (passkeys/notificações); o executor mapeia o resto com `migrate:status`/drizzle e testa o caminho completo em int.
|
||||
- **"Mesclar pessoas" vira escopo.** Transferir engajamentos entre fichas (ex. reancorar dobradinha) é campo admin hoje (`stateDeputy.contact` system-level); ferramenta de merge real é pós-eleição. **Corte:** v1 só copia campos da ficha + Apagar.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Pessoas sem nenhum papel (Contact puro, sem capacidade):** entram na lista? **Recomendação:** não no v1 — sem capacidade não há linha; continua sendo assunto do admin Payload. _(assumido — validar no gate)_
|
||||
- **Partido no nome:** única fonte `stateDeputy.party` (quem não é dobradinha não tem sufixo). **Recomendação:** sim. _(assumido — validar no gate)_
|
||||
- **Convite para dobradinhas/staff?** Hoje o convite de cadastro existe **só para lideranças** (`campaignInvite` ancorado em `leadership`); assessores entram por provisionamento direto, sem convite. **Recomendação:** na v1, convite só nas linhas com engajamento de liderança; generalizar o convite para pessoa (ancorar em `Contact`) é mudança de schema do `campaignInvite` — follow-up pós-eleição. _(assumido — validar no gate)_
|
||||
- **Quem pode apagar pessoa?** **Recomendação:** coordenação/candidato (ação destrutiva transversal, maior que o escopo de carteira de assessor). _(assumido — validar no gate)_
|
||||
- **Votos declarados na cascata:** apagar junto (lista explícita no diálogo) vs bloquear enquanto existirem? **Recomendação:** apagar junto, listado no diálogo de confirmação — o gesto de apagar é intenção explícita de remover a pessoa inteira. _(assumido — validar no gate)_
|
||||
- **Carteira e conta de acesso do apagado:** remover automaticamente dos `municipality.advisors` / `activity.advisors` e apagar a conta `campaignUser` vinculada (cascata existente de passkeys/notificações)? **Recomendação:** sim, na mesma transação, declarado no diálogo. _(assumido — validar no gate)_
|
||||
|
||||
## Referências
|
||||
|
||||
- [pessoas-identidade-contact.md](pessoas-identidade-contact.md) (C99, pré-requisito)
|
||||
- B18 (filtros salvos), B127/B128 (omnibox), B159 (colunas de relação), B91 (busca global), `docs/plans/sistema-listas-campanha.md` (shells Pass 2 W1)
|
||||
- `docs/plans/convite-whatsapp-lista-liderancas.md` + `LeadershipInviteRowAction` (convite de cadastro por linha — fluxo a reusar)
|
||||
- Precedente de cascata e LGPD: `removeSupporterData` (anônima Contact quando sem outros joins) e hooks `beforeDelete` de `campaignUser` (passkeys/notificações)
|
||||
- Canvas UI (gate): `plan-c100-ui-draft.canvas.tsx`
|
||||
- `AGENTS.md` — modelo Municípios, leader lockdown, naming pt-BR em labels
|
||||
Loading…
Reference in a new issue