Merge pull request #628 from fsolla/plans/plan-issue-30

plans(C111,C112,B197): telefone não único, múltiplos telefones, e-mail oculto por padrão
This commit is contained in:
Francisco Solla 2026-08-10 23:30:46 +00:00 committed by GitHub
commit f44254cea5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 364 additions and 0 deletions

View file

@ -0,0 +1,118 @@
# Múltiplos telefones por pessoa
Status: registrado (blocked até plano em main)
Atualizado em: 2026-08-10
Issue: #626
Priority: P2
Model: composer-2.5
Impeccable: B — encaixe: formulários de criação/edição de pessoa + ficha (detalhe) + listas existentes
Canvas UI: /home/fsolla/.cursor/projects/home-fsolla-cursor-worktrees-teqo-plans-plan-issue-30/canvases/plan-c112-ui-draft.canvas.tsx
Appetite: ~1–2 dias eng; encaixe no modelo de pessoa existente
Responsável: —
## Intenção
Uma pessoa pode ter mais de um celular — o pessoal e o do comitê, o número que
troca e o antigo que ainda recebe WhatsApp. Hoje a ficha aceita um único número
e a mesa tem de escolher qual guardar. Queremos cadastrar **N telefones por
pessoa**, com as listas continuando compactas (mostram o principal) e a ficha
mostrando todos. Pré-requisito: telefones deixam de ser únicos (item **C111**).
## Persona e fluxo
- **Persona / contexto:** staff da mesa (coordenação e assessores) editando a
ficha de uma pessoa — liderança, apoiador, dobradinha, assessor, pessoa.
- **Job principal:** registrar todos os números de contato de uma pessoa, sem
perder informação e sem poluir as listas.
- **Fluxo desejado:** abre a ficha da liderança → vê os dois telefones dela →
adiciona um terceiro → salva → a lista mostra o principal; a ficha mostra
todos; WhatsApp e convite usam o principal.
- **Anti-goals de produto:** não é CRUD de agenda telefônica (sem rótulos
"casa/trabalho", sem tipo fixo/móvel, sem reordenação por arrasto); o import
CSV continua com uma coluna de telefone (mapeia no principal).
### Esboço de fluxo (B — ver canvas)
```text
[ficha da pessoa] → vê telefones (principal primeiro) → "+ Adicionar telefone"
→ salva → lista: mostra o principal · ficha: mostra todos · WhatsApp/convite: principal
```
## Objetivo e aceite
- Uma pessoa pode ter **2+ telefones** cadastrados e editáveis nas cinco
superfícies de pessoa: ficha/detalhe, criação/edição de liderança, apoiador,
dobradinha e assessor.
- As listas de pessoas continuam mostrando **um** telefone por linha (o
principal); a ficha mostra todos.
- WhatsApp (`wa.me`), convite e o import CSV usam o **principal** (o import só
tem uma coluna).
- A busca por telefone encontra a pessoa por **qualquer** um dos números.
- Guardrail: os telefones continuam **não únicos** entre pessoas (C111) — duas
pessoas podem compartilhar um número, e cada uma mantém seus demais números.
## Dados (intenção)
Dados: N/A — nenhuma métrica; a mudança é de cadastro e exibição de contato.
## Direção no codebase (hipótese)
- **Áreas prováveis:** `src/collections/Contact.ts` (campo de telefones),
formulários de pessoa (`src/app/(campaign)/campanha/(app)/{liderancas,apoiadores,dobradinhas,assessores,pessoas}/`),
fichas/detalhes, view models das listas (`src/utilities/*ViewModels`),
`src/app/(campaign)/campanha/actions/…` (escrita), import CSV
(`supporterImport`), `src/lib/phone.ts` (normalização).
- **Precedente a olhar:** C99/C100 (ficha `Contact` como identidade), B163
(edição inline de contato em dobradinhas), C6 (import de apoiadores),
C111 (telefone não único — dependência).
- **Risco de acoplamento:** o telefone é chave em fluxos automáticos (import,
convite, login de liderança) — o "principal" precisa ser estável para não
mudar de número a cada edição; e a mudança toca as mesmas páginas de lista do
item **B197** (colunas) — não rodar em paralelo.
## Dependências
- **C111** (telefone não único) — dura: múltiplos telefones pressupõem que
números compartilhados entre pessoas são permitidos.
- Serializa com **B197** (mesmas páginas de lista).
## Fora de escopo
- Rótulos de telefone (casa/trabalho/whatsapp) e tipo (fixo/móvel) — sem label.
- Reordenação visual por arrasto; "definir principal" por controle explícito
(a ordem da lista é a prioridade).
- Múltiplos telefones no import CSV (a planilha continua com uma coluna).
- Trocar a chave de login da liderança (continua o principal).
## Rabbit holes de produto
- **"Telefone com rótulo + tipo + principal explícito".** Vira CRUD de agenda
telefônica com controle extra em todo form. **Corte neste item:** ordem =
prioridade, sem labels, um input repetível.
- **"Mostrar todos os telefones nas listas".** Polui a linha e fura o pedido
implícito de lista compacta. **Corte:** lista mostra o principal; a ficha é o
lugar de todos.
## Questões em aberto (produto)
- **O que define o telefone "principal"?** **Opções:** A) o primeiro da lista —
ordem = prioridade, sem controle extra na UI; B) flag explícita "principal"
com controle dedicado. **Recomendação: A** — menos UI, e trocar o principal é
editar a ordem; se a mesa pedir reordenação, vira item futuro. _(assumido —
validar com produto)_
- **Onde se adiciona telefone?** **Opções:** A) nos formulários de
criação/edição ("+ Adicionar telefone", repetível) e na ficha; B) só na ficha.
**Recomendação: A** — quem cria a pessoa já informa os números que tem; sem
volta à ficha para completar. _(assumido — validar com produto)_
- **Telefones com número repetido na MESMA pessoa (dedupe interno)?**
**Opções:** A) impede salvar o mesmo número duas vezes na mesma ficha; B)
permite (deixar para a mesa limpar). **Recomendação: A** — custo baixo,
evita lista suja; o bloqueio é só dentro da própria ficha, nunca entre
pessoas. _(assumido — validar com produto)_
## Referências
- GitHub Issue (após registro)
- Canvas UI (gate): `plan-c112-ui-draft.canvas.tsx`
- C111 `docs/plans/telefone-nao-unico.md` (dependência) · C99/C100 · B163
- `src/collections/Contact.ts` · `src/lib/phone.ts`

View file

@ -0,0 +1,126 @@
# Ocultar a coluna de e-mail por padrão nas listas de pessoas
Status: registrado (blocked até plano em main)
Atualizado em: 2026-08-10
Issue: #627
Priority: P2
Model: composer-2.5
Impeccable: B — encaixe: seletor de colunas existente (B17) + defaults por lista; sem rota nova
Canvas UI: /home/fsolla/.cursor/projects/home-fsolla-cursor-worktrees-teqo-plans-plan-issue-30/canvases/plan-b197-ui-draft.canvas.tsx
Appetite: ~0,5–1 dia eng; defaults + um split de coluna (pessoas) + um encaixe (assessores)
Responsável: —
## Intenção
As listas de pessoas — pessoas, lideranças, dobradinhas, assessores e apoiadores
— mostram o e-mail como coluna sempre visível. No dia a dia da mesa o canal real
é telefone/WhatsApp; o e-mail é o dado menos consultado e ocupa largura em cinco
telas. Queremos que a coluna de e-mail **comece oculta por padrão** nessas
listas, com o usuário podendo **ligá-la manualmente** pelo seletor de colunas —
mecânica que já existe em `/campanha` (usada em Municípios, Territórios etc.).
## Persona e fluxo
- **Persona / contexto:** staff da mesa (coordenação e assessores) navegando as
listas de pessoas para gestão diária.
- **Job principal:** ler a lista sem ruído (nome + telefone), e ter e-mail à mão
quando precisar, sem pedir para ninguém.
- **Fluxo desejado:** abre `/campanha/liderancas` → sem coluna de e-mail → quer
ver → abre o seletor de colunas → liga "E-mail" → a escolha fica salva naquele
dispositivo, como já acontece hoje.
- **Anti-goals de produto:** não reordenar colunas, não redesenhar as tabelas,
não mexer nas fichas/detalhes, não mudar o e-mail no card mobile (não há
colunas lá).
### Esboço de fluxo (B — ver canvas)
```text
[lista de pessoas] → e-mail oculto por padrão → [seletor de colunas] → liga E-mail
→ coluna aparece e fica lembrada no dispositivo → (desliga de novo quando quiser)
```
## Objetivo e aceite
- Nas **cinco listas** (pessoas, lideranças, dobradinhas, assessores, apoiadores)
a coluna de e-mail começa oculta para quem nunca mexeu no seletor de colunas.
- Em todas, o usuário pode **religar** o e-mail manualmente (seletor de colunas
existente), com a escolha lembrada por dispositivo — mesma mecânica atual.
- Nas listas que já têm o seletor (lideranças, dobradinhas, pessoas, apoiadores),
só muda o **default**; nenhuma outra coluna muda de ordem ou visibilidade.
- Na lista de assessores, o e-mail passa a ser ocultável (hoje não há seletor);
default oculto, religável.
- Na lista de pessoas, o e-mail deixa de ficar embutido na coluna "Contato" e
vira coluna própria, oculta por padrão — a coluna "Contato" fica só com o
telefone. (Na ficha/mobile nada muda.)
## Dados (intenção)
Dados: N/A — nenhuma métrica; a mudança é de apresentação de colunas.
## Direção no codebase (hipótese)
- **Áreas prováveis:** `src/lib/campaignColumnVisibility.ts` (defaults por
lista — mecanismo B17 já existente), páginas
`src/app/(campaign)/campanha/(app)/{liderancas,dobradinhas,pessoas,apoiadores,assessores}/`
e os componentes de lista (`src/components/campaign/{leadership,stateDeputy,people,supporter,advisor}/`).
- **Precedente a olhar:** B17 (`docs/plans/seletor-colunas-lista-municipios.md` —
mecanismo de ocultar colunas), B28 (`email-celular-lista-liderancas.md` —
coluna de e-mail em lideranças), B163 (dobradinhas), C100
(`pessoas-lista-unificada.md` — coluna "Contato" com telefone+e-mail na mesma
célula, decisão que este item reabre).
- **Risco de acoplamento:** a lista de assessores usa tabela própria (sem o
seletor); o item deve trazer a mesma mecânica de colunas para lá — sem criar
um segundo padrão. Mesmas páginas que o item **C112** (telefones) toca — não
rodar em paralelo.
## Dependências
- Nenhuma dura. Serializa com **C112** (múltiplos telefones — mesmas páginas de
lista).
## Fora de escopo
- Reordenação de colunas (por arrasto ou não).
- Redesenho das tabelas (cards mobile, larguras, densidade).
- Ocultar e-mail nas **fichas/detalhes** ou nos cards mobile — o pedido é de
colunas de lista.
- Ocultar qualquer outra coluna (telefone, municípios etc.).
## Rabbit holes de produto
- **"Já que vamos mexer, esconder também telefone/municípios".** Escopo
explode e mexe em leitura de gestão. **Corte neste item:** só e-mail.
- **"Redesenhar a tabela de assessores para caber no padrão".** O pedido é
visibilidade de coluna, não refactor da superfície. **Corte:** usar a
mecânica de ocultar/religar sem reescrever a tabela.
## Questões em aberto (produto)
- **Pessoas (C100) hoje mostra o e-mail dentro da coluna "Contato" (telefone em
destaque, e-mail embaixo). Ocultar "a coluna de e-mail" lá = separar o e-mail
numa coluna própria?** **Opções:** A) sim — coluna "E-mail" própria (como
lideranças/dobradinhas), oculta por padrão; B) esconder só a linha de e-mail
dentro da célula (fora da mecânica de colunas). **Recomendação: A** —
consistência entre as listas; quem religa ganha coluna igual às outras.
_(assumido — validar com produto)_
- **Apoiadores hoje não tem coluna de e-mail na lista (só na ficha).** **Opções:**
A) adicionar a coluna "E-mail" (oculta por padrão) — completa o pedido;
B) nada a esconder — não mexer. **Recomendação: A** — o pedido nomeia
apoiadores; a coluna nasce oculta e quem quiser liga. _(assumido — validar
com produto)_
- **Assessores não tem o seletor de colunas.** **Opções:** A) trazer a mesma
mecânica B17 para a tabela de assessores (ocultar por padrão + religar);
B) ocultar o e-mail fixo, sem como religar. **Recomendação: A** — sem religar,
o "ativar manualmente se preferir" não existe nessa lista. _(assumido —
validar com produto)_
- **Quem já tem escolha salva no dispositivo (cookie do seletor) continua com o
e-mail visível?** **Recomendação: sim** — o default vale para quem nunca
tocou no seletor; a escolha existente é respeitada (contrato atual do
mecanismo). _(assumido — validar com produto)_
## Referências
- GitHub Issue (após registro)
- Canvas UI (gate): `plan-b197-ui-draft.canvas.tsx`
- B17 `docs/plans/seletor-colunas-lista-municipios.md` · B28 `docs/plans/email-celular-lista-liderancas.md` · C100 `docs/plans/pessoas-lista-unificada.md`
- `src/lib/campaignColumnVisibility.ts` · `src/components/campaign/advisor/AdvisorsTable.tsx`

View file

@ -0,0 +1,120 @@
# Telefone não único: duas pessoas podem compartilhar o mesmo celular
Status: registrado (blocked até plano em main)
Atualizado em: 2026-08-10
Issue: #625
Priority: P1
Model: cursor-grok-4.5-medium
Impeccable: A — N/A (nenhuma superfície nova; comportamento de escrita, sem UI)
Canvas UI: N/A — sem UI
Appetite: ~0,5–1 dia eng; um outcome verificável
Responsável: —
## Intenção
Hoje o sistema recusa salvar uma pessoa cujo celular já pertence a outro contato
("Já existe outro contato com este celular"). Na prática, números compartilhados
são comuns — aparelho de família, número do comitê, número que passou de mão — e
a recusa trava o fluxo de gestão da mesa: a ficha não salva, o assessor fica sem
saída e inventa um número errado ou deixa a pessoa sem telefone. O telefone é um
**canal de contato**, não a identidade da pessoa — a identidade é a ficha `Contact`
(entrega C99). Queremos que duas pessoas possam ter o mesmo número, sem bloquear
nenhum fluxo de cadastro ou edição.
## Persona e fluxo
- **Persona / contexto:** staff da mesa (coordenação e assessores) cadastrando e
editando pessoas — lideranças, apoiadores, dobradinhas, assessores — além do
fluxo de importação de apoiadores por CSV.
- **Job principal:** salvar a ficha de uma pessoa com o número real dela, mesmo
que outra pessoa já use o mesmo número.
- **Fluxo desejado:** a assessora salva a liderança Ana com o celular que já é do
comitê → **salva sem erro** → a ficha fica completa com o número real. A busca
por telefone continua encontrando as duas pessoas. Fluxos automáticos (import,
convite) tratam números compartilhados **sem adivinhar** de quem são.
- **Anti-goals de produto:** esta entrega não reconcilia fichas duplicadas do
histórico (fora de escopo, pós-eleição); não muda o que as listas exibem; não
cria um "identificador" novo para a pessoa.
### Esboço de fluxo (A — omitido; sem UI nova)
## Objetivo e aceite
- Cadastrar ou editar uma pessoa com telefone igual ao de outra pessoa **salva
sem erro**, em qualquer um dos cinco fluxos de escrita de pessoa (liderança,
apoiador, dobradinha, assessor, pessoa).
- As mensagens de erro de conflito de celular ("já existe outro contato…")
deixam de aparecer nos fluxos de gestão.
- Fluxos automáticos que usam o telefone como chave (import CSV, convite por
telefone, login de liderança) continuam funcionando; quando o número é
compartilhado, eles **falham fechado com mensagem clara** ou usam a chave que
não é ambígua — nunca "chutam" qual pessoa.
- Nenhum outro campo de `Contact` muda; a busca por telefone continua achando
todas as pessoas com aquele número.
## Dados (intenção)
Dados: N/A — nenhuma métrica nova; a mudança é de escrita e matching de contato.
## Direção no codebase (hipótese)
- **Áreas prováveis:** `src/utilities/contactPhoneInvariant.ts` (regra de
unicidade e locks), `src/collections/Contact.ts` (hook de validação),
`src/collections/CampaignUser.ts` (sincronização conta↔ficha),
`src/utilities/campaignInviteRedemption.ts` (login/convite por telefone),
`src/app/(campaign)/campanha/actions/supporterImport.ts` (dedupe do CSV),
forms de liderança/apoiador/dobradinha/assessor/pessoa.
- **Precedente a olhar:** C99 (`docs/plans/pessoas-identidade-contact.md`) —
declarou o telefone como chave de dedupe da ficha; esta entrega reabre essa
política por pedido do produto; C100 (lista de pessoas).
- **Risco de acoplamento:** o telefone é hoje a chave "pessoa ↔ ficha" em vários
fluxos (import, convite, resolução de conta). Cada fluxo precisa de um
comportamento definido para números compartilhados — sem criar identidade
ambígua nem bloquear a gestão.
## Dependências
- Nenhuma dura. Não deve rodar em paralelo com trabalho que edite os mesmos
arquivos de escrita de pessoa (ver `serializes` na Issue).
## Fora de escopo
- Múltiplos telefones por pessoa → item **C112** (dependente deste).
- Reconciliação de fichas duplicadas existentes (mesma pessoa em duas fichas) —
aceita e visível; reconciliação segue pós-eleição.
- Mudar o que as listas mostram (telefone principal, colunas) → fora.
## Rabbit holes de produto
- **"Já que vamos desbloquear, deduplicar as fichas existentes".** Explosão de
escopo e risco em janela eleitoral. **Corte neste item:** só o comportamento de
escrita; reconciliação segue pós-eleição.
- **"Telefone vira chave solta: import passa a criar ficha nova sempre".**
Perde o casamento pessoa↔ficha do CSV. **Corte:** matching por telefone
continua onde existe, com falha fechada na ambiguidade.
## Questões em aberto (produto)
- **Import CSV casa linhas por telefone; com números compartilhados, o que o
import faz?** **Opções:** A) mantém o casamento por telefone e falha fechado
quando o número pertence a mais de um contato — a linha fica sinalizada para
resolução manual; B) para de casar por telefone e cria fichas novas. **Recomendação:
A** — o casamento por telefone é o jeito mais confiável de reencontrar a
pessoa no re-import; ambiguidade pede decisão humana, nunca chute. _(assumido —
validar com produto)_
- **Login de liderança é pelo celular (nome de usuário da conta). Duas
lideranças com o mesmo número: a segunda entra como?** **Opções:** A) a conta
continua única — quem não tem número exclusivo usa e-mail para entrar;
B) bloqueia criar a segunda conta com o mesmo número. **Recomendação: A** —
o login é de conta, não de pessoa; o bloqueio de conta recriaria o problema
que esta entrega resolve. _(assumido — validar com produto)_
- **Convite por telefone para número compartilhado:** a quem o convite vincula?
**Opções:** A) falha fechado pedindo escolher a ficha; B) vincula à ficha de
quem gerou o convite (o convite nasce de uma ficha). **Recomendação: B** — o
convite já é gerado a partir de uma pessoa; o número é só o canal de envio.
## Referências
- C99 `docs/plans/pessoas-identidade-contact.md` · C100 `docs/plans/pessoas-lista-unificada.md`
- C6 `docs/plans/escala-dry-pos-c6.md` (import com dedupe por telefone)
- `src/utilities/contactPhoneInvariant.ts` (regra atual a reavaliar)