docs(plan): B202 — totais do recorte na lista de municípios (votos 2022 + expectativas 2026) (Related #1005)

This commit is contained in:
Francisco Solla 2026-09-14 14:35:34 -03:00
parent 119b8980fb
commit e8536e646e
2 changed files with 305 additions and 0 deletions

View file

@ -0,0 +1,209 @@
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<title>UI draft — Totais do recorte na lista de municípios (B202)</title>
</head>
<body class="bg-zinc-950 text-zinc-100 antialiased">
<main class="mx-auto max-w-6xl p-8">
<p class="text-sm text-zinc-400">
Coordenador na mesa, com um recorte ativo, precisa saber o total de votos 2022 e das
expectativas 2026 do conjunto filtrado — sem sair da lista. Decisão do gate: uma
<span class="text-zinc-200">linha compacta no rodapé compartilhado</span> da lista (o mesmo
nos viewports desktop e mobile, junto de “N municípios encontrados”), sem linha dentro da
tabela e sem bloco nos cards. Valores genéricos, sem dados reais.
</p>
<!-- ================= DESKTOP ================= -->
<section class="mt-8">
<h1 class="text-lg font-semibold">Desktop (~1280px) — linha compacta no rodapé da lista</h1>
<div class="mt-4 rounded-xl border border-zinc-800 bg-zinc-900/40 p-4">
<div class="flex items-center justify-between gap-4">
<h2 class="text-base font-semibold">Municípios</h2>
<span class="text-xs text-zinc-400">Cenário: <span class="text-zinc-100">Média</span></span>
</div>
<div class="mt-3 flex flex-wrap items-center gap-2 text-xs">
<span class="rounded-md border border-zinc-700 px-2 py-1 text-zinc-300">Buscar município</span>
<span class="rounded-full border border-zinc-700 px-2 py-0.5 text-zinc-300">Território: Metropolitano de Salvador</span>
<span class="rounded-full border border-zinc-700 px-2 py-0.5 text-zinc-300">Prioridade: Alta</span>
<span class="rounded-full border border-sky-500/60 px-2 py-0.5 text-sky-300">Cenário: Média</span>
<span class="ml-auto text-zinc-400">Limpar</span>
</div>
<div class="mt-4 overflow-hidden rounded-lg border border-zinc-800">
<table class="w-full text-sm">
<thead class="bg-zinc-900/70 text-xs text-zinc-400">
<tr>
<th class="w-52 px-3 py-2 text-left font-medium">Município</th>
<th class="px-3 py-2 text-right font-medium">Votos 2022</th>
<th class="px-3 py-2 text-center font-medium">Expectativa 2026</th>
<th class="px-3 py-2 text-left font-medium">Nível</th>
<th class="px-3 py-2 text-left font-medium">Tendência</th>
</tr>
</thead>
<tbody class="divide-y divide-zinc-800">
<tr>
<td class="px-3 py-2">
<span class="font-medium">Município Exemplo A</span>
<span class="block text-xs text-zinc-500">Território Exemplo</span>
</td>
<td class="px-3 py-2 text-right tabular-nums">
<span class="font-medium">1.234</span>
<span class="block text-xs text-zinc-500">12º de 663 · 0,4% da votação</span>
</td>
<td class="px-3 py-2 text-center tabular-nums">
<span class="font-medium">2.400</span>
<span class="block text-xs text-zinc-500">Nas lideranças: 900</span>
</td>
<td class="px-3 py-2"><span class="rounded border border-zinc-700 px-1.5 py-0.5 text-xs">N2</span></td>
<td class="px-3 py-2 text-xs text-zinc-300">Favorável</td>
</tr>
<tr>
<td class="px-3 py-2">
<span class="font-medium">Salvador</span>
<span class="ml-1 rounded bg-zinc-800 px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-zinc-300">Cidade</span>
<span class="block text-xs text-zinc-500">Cidade agregada · Metropolitano de Salvador</span>
</td>
<td class="px-3 py-2 text-right tabular-nums">
<span class="font-medium">48.312</span>
<span class="block text-xs text-zinc-500">12º de 663 · 3,1% da votação</span>
</td>
<td class="px-3 py-2 text-center text-zinc-500">—</td>
<td class="px-3 py-2 text-zinc-500">—</td>
<td class="px-3 py-2 text-xs text-zinc-300">Não registrada</td>
</tr>
<tr>
<td class="px-3 py-2">
<span class="font-medium">Município Exemplo B</span>
<span class="block text-xs text-zinc-500">Território Exemplo</span>
</td>
<td class="px-3 py-2 text-right tabular-nums">
<span class="font-medium">876</span>
<span class="block text-xs text-zinc-500">23º de 663 · 0,1% da votação</span>
</td>
<td class="px-3 py-2 text-center tabular-nums">
<span class="font-medium">1.100</span>
</td>
<td class="px-3 py-2 text-zinc-500">—</td>
<td class="px-3 py-2 text-xs text-zinc-300">Neutra</td>
</tr>
</tbody>
</table>
</div>
<!-- Rodapé compartilhado: contagem + linha compacta de totais + paginação -->
<div class="mt-3 flex flex-col items-center gap-2">
<p class="text-sm text-zinc-400">312 municípios encontrados</p>
<p class="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 rounded-lg border border-zinc-800 bg-zinc-900/70 px-3 py-1.5 text-xs text-zinc-400 tabular-nums">
<span>Total do recorte:</span>
<span>2022 <span class="text-sm font-semibold text-zinc-100">1.234.567</span></span>
<span class="text-zinc-600">·</span>
<span>Pess. <span class="text-zinc-100">812.400</span></span>
<span class="text-zinc-600">·</span>
<span>Média <span class="font-semibold text-sky-300">1.034.500</span></span>
<span class="text-zinc-600">·</span>
<span>Otim. <span class="text-zinc-100">1.290.100</span></span>
<span class="text-zinc-600">·</span>
<span class="text-zinc-500">189 de 312 com expectativa</span>
</p>
<div class="flex items-center gap-2 text-xs text-zinc-400">
<span class="rounded border border-zinc-700 px-2 py-1">Anterior</span>
<span>1 de 13</span>
<span class="rounded border border-zinc-700 px-2 py-1">Próxima</span>
</div>
</div>
</div>
<p class="mt-3 text-xs text-zinc-500">
Notas do rascunho: o total cobre <span class="text-zinc-300">todas as páginas do filtro</span>,
não só as linhas visíveis. Salvador entra uma vez — a linha “Cidade” substitui as 19 ZE quando
está no recorte; em recortes sem a cidade, as ZE somam. O cenário ativo (Média, default) fica
em destaque; os outros dois ficam sempre legíveis ao lado. O rodapé é o mesmo componente
compartilhado das listas — só municípios passa esses números; as demais listas ficam idênticas.
</p>
</section>
<!-- ================= MOBILE ================= -->
<section class="mt-10">
<h1 class="text-lg font-semibold">Mobile (~390px) — mesmo rodapé, quebrando em linhas</h1>
<div class="mt-4 w-[390px] rounded-xl border border-zinc-800 bg-zinc-900/40 p-4">
<div class="flex items-center justify-between">
<h2 class="text-base font-semibold">Municípios</h2>
<span class="text-xs text-zinc-400">Média</span>
</div>
<div class="mt-3 flex gap-2 text-xs">
<span class="rounded-md border border-zinc-700 px-2 py-1 text-zinc-300">Buscar</span>
<span class="rounded-full border border-zinc-700 px-2 py-0.5 text-zinc-300">TI: Metropolitano</span>
</div>
<div class="mt-3 space-y-2">
<article class="rounded-lg border border-zinc-800 p-3">
<p class="text-sm font-medium">Município Exemplo A</p>
<p class="text-xs text-zinc-500">Território Exemplo</p>
<div class="mt-2 grid grid-cols-2 gap-2 text-xs">
<span class="text-zinc-400">2022: <span class="tabular-nums text-zinc-100">1.234</span></span>
<span class="text-zinc-400">Expect.: <span class="tabular-nums text-zinc-100">2.400</span></span>
</div>
</article>
<article class="rounded-lg border border-zinc-800 p-3">
<p class="text-sm font-medium">Município Exemplo B</p>
<p class="text-xs text-zinc-500">Território Exemplo</p>
<div class="mt-2 grid grid-cols-2 gap-2 text-xs">
<span class="text-zinc-400">2022: <span class="tabular-nums text-zinc-100">876</span></span>
<span class="text-zinc-400">Expect.: <span class="tabular-nums text-zinc-100">1.100</span></span>
</div>
</article>
</div>
<div class="mt-3 flex flex-col items-center gap-2">
<p class="text-sm text-zinc-400">312 municípios encontrados</p>
<p class="w-full rounded-lg border border-zinc-800 bg-zinc-900/70 px-3 py-2 text-center text-xs text-zinc-400 tabular-nums">
<span class="block">Total do recorte</span>
<span class="mt-1 block">
2022 <span class="font-semibold text-zinc-100">1.234.567</span> · Pess.
<span class="text-zinc-100">812.400</span>
</span>
<span class="block">
Média <span class="font-semibold text-sky-300">1.034.500</span> · Otim.
<span class="text-zinc-100">1.290.100</span>
</span>
<span class="mt-1 block text-zinc-500">189 de 312 com expectativa</span>
</p>
<div class="flex items-center gap-2 text-xs text-zinc-400">
<span class="rounded border border-zinc-700 px-2 py-1">Anterior</span>
<span>1 de 13</span>
<span class="rounded border border-zinc-700 px-2 py-1">Próxima</span>
</div>
</div>
</div>
</section>
<!-- ================= VAZIO ================= -->
<section class="mt-10">
<h1 class="text-lg font-semibold">Recorte vazio — sem totais inventados</h1>
<div class="mt-4 max-w-xl rounded-xl border border-zinc-800 bg-zinc-900/40 p-4">
<div class="flex flex-wrap items-center gap-2 text-xs">
<span class="rounded-md border border-zinc-700 px-2 py-1 text-zinc-300">Buscar município</span>
<span class="rounded-full border border-zinc-700 px-2 py-0.5 text-zinc-300">Território: Exemplo</span>
</div>
<div class="mt-3 rounded-lg border border-dashed border-zinc-700 p-6 text-center text-sm text-zinc-400">
Nenhum município encontrado com os filtros atuais.
</div>
<div class="mt-3 flex flex-col items-center gap-2">
<p class="text-sm text-zinc-400">0 municípios encontrados</p>
</div>
<p class="mt-3 text-xs text-zinc-500">
Sem linhas, a linha de totais não aparece — nada de zeros enganosos. No recorte com linhas
mas sem nenhuma expectativa preenchida, o trecho de expectativa mostra “—”, não “0”.
</p>
</div>
</section>
</main>
</body>
</html>

View file

@ -0,0 +1,96 @@
# Totais do recorte na lista de municípios (votos 2022 + expectativas 2026)
Status: rascunho
Atualizado em: 2026-09-14
Issue: #1005
Priority: P2
Impeccable: B — encaixe no rodapé compartilhado da lista de /campanha/municipios (linha compacta, sem mexer na tabela)
Rascunho UI: docs/plans/totais-recorte-lista-municipios-ui-draft.html
Appetite: ~0,5–1 dia eng; um outcome verificável
Responsável: —
## Intenção
Na lista de municípios, quem filtra já vê os números linha a linha, mas ninguém soma o conjunto. O Coordenador precisa saber a magnitude do recorte que está olhando — os votos de Solla em 2022 e as expectativas de 2026 nos três cenários — sem exportar nem somar de cabeça. O total é do **recorte inteiro** (todas as páginas do filtro), não da página visível, e precisa respeitar o mesmo escopo de acesso das linhas (assessor soma só a carteira dele). Este item é sucessor legítimo do B129: ele **não** ressuscita o painel acima da lista que aquele item removeu — o total mora no rodapé da lista, numa linha compacta junto da contagem, sem empurrar as linhas para baixo.
## Persona e fluxo
- **Persona / contexto:** Coordenador Geral (também candidato e assessor, cada um no seu escopo) na mesa, com um recorte ativo — por território, prioridade, assessoria ou busca — querendo calibrar o tamanho do conjunto antes de agir linha a linha.
- **Job principal:** Saber, em um olhar, o total de votos 2022 e das expectativas 2026 do recorte filtrado, por cenário, sem perder de vista que é um agregado e não uma linha.
- **Fluxo desejado:** Entra em Municípios → filtra/busca → lê as linhas na tabela (ou cards, no mobile) → vê uma linha compacta de totais do recorte no rodapé da lista, junto de "N municípios encontrados".
- **Anti-goals de produto:** Não recriar o bloco de overview acima da lista (anti-goal do B129); não criar linha de totais dentro da tabela nem bloco dedicado nos cards (decisão do gate: rodapé compartilhado); não empurrar a lista para baixo; não introduzir médias, percentuais, cobertura ou seletor novo; não misturar pledges nesta v1; não expor nada a Liderança (lockdown intacto); o assessor nunca vê total maior que a carteira dele.
### Esboço de fluxo (B)
```text
[Municípios] → filtros/busca → lista mostra linhas → rodapé compartilhado mostra "N municípios encontrados" + a linha compacta de totais do recorte (2022 + 3 cenários)
```
### Rascunho UI (B)
- Rascunho UI (gate): `docs/plans/totais-recorte-lista-municipios-ui-draft.html`
## Objetivo e aceite
- Com qualquer recorte ativo, o rodapé da lista mostra **quatro somas**: votos Solla 2022 e expectativa 2026 nos três cenários (Pessimista / Média / Otimista), junto da contagem de municípios do recorte (ex.: "312 municípios encontrados · 2022 1.234.567 · Pess. 812.400 · Média 1.034.500 · Otim. 1.290.100").
- As somas cobrem **todas as páginas do recorte**, nunca apenas a página visível; o rótulo diz "recorte" (nunca "página").
- Os números batem com a soma das linhas do mesmo recorte (mesma fonte por linha, sem fórmula paralela).
- **Salvador conta uma vez:** quando a linha virtual da cidade aparece, ela representa as 19 ZE na soma de 2022 — nunca cidade + zonas; em recortes só de zonas, as zonas somam normalmente. As expectativas incluem todas as ZE de Salvador (a cidade não tem expectativa).
- Linha secundária honesta: quantos municípios do recorte têm expectativa preenchida (ex.: "189 dos 312 com expectativa").
- **Escopo de acesso:** assessor vê os totais apenas da carteira; coordenador/candidato, do recorte completo. Liderança não acessa a superfície.
- Guardrail B129: nenhum bloco agregado acima da lista; o total ocupa **uma linha compacta no rodapé compartilhado** (o mesmo nos dois viewports, abaixo da tabela/cards), sem empurrar as linhas e sem linha dentro da tabela.
- Recorte vazio não inventa totais; recorte sem nenhuma expectativa não mostra "0" enganoso.
## Dados (intenção)
- **Vou apresentar dados?** Sim — somas do recorte, na própria lista, junto das colunas que já existem.
- **Decisões desbloqueadas:** Coordenador/assessor/candidato dimensiona o recorte filtrado e compara cenários de relance para priorizar onde agir; leitura relativa/local preservada (somas do conjunto que se está olhando, nunca % estadual).
- **Forma:** *adiada ao plano de implementação* — restrições: recorte inteiro (não página), rótulo honesto, sem médias/percentuais/cobertura, sem pledges na v1, sem seletor novo, sem consulta nova se os dados do recorte já estiverem carregados.
## Dados da decisão (literais)
- Votos Solla 2022 (baseline eleitoral imutável, por município/ZE).
- Cenários de expectativa: `pessimistic | central | optimistic`; rótulos UI "Pessimista / Média / Otimista".
- `salvador` / linha `isCity`: conta uma vez em 2022 (cidade **ou** zonas, nunca ambas); sem expectativa própria — as 19 ZE entram na soma dos cenários.
- Nada de pledges ("Nas lideranças") nesta v1; nada de média, percentual ou cobertura.
## Direção no codebase (hipótese)
- **Áreas prováveis:** lista de municípios (`/campanha/municipios`) e o rodapé compartilhado onde já vivem a contagem "N municípios encontrados" e a paginação (o mesmo nos viewports desktop e mobile); a linha compacta de totais entra ali, e só a lista de municípios passa esses números — as outras listas que usam o mesmo rodapé ficam idênticas. O cenário ativo de expectativa hoje é estado local do cliente.
- **Precedente a olhar:** B129 (`docs/plans/remover-overview-lista-municipios.md`, #266) — a tensão a respeitar; B178/Salvador (`docs/plans/salvador-pagina-detalhe-cidade.md`) — a regra de contar uma vez; KPIs do recorte em `/campanha/apoiadores` (`supporterListOverviewAggregate`); rollup staff já existente para as colunas de voto/expectativa; células e ordenação da trilha B15/E8/A11.
- **Risco de acoplamento:** anti-goal do B129 ("não substituir o bloco por outro resumo agregado"); linha virtual de Salvador e não dupla contagem; a linha compacta mostra os três cenários de uma vez, então não depende do estado local do cliente; paginação de 25/página; cards mobile e tabela desktop compartilham o mesmo rodapé.
## Dependências
- Nenhuma (dura).
- Suave: B129 já entregue (respeitar o anti-goal); B178/Salvador como invariante a preservar.
## Fora de escopo
- Qualquer painel/overview acima da lista (o B129 fez a remoção; não reabrir).
- Médias, percentuais, cobertura de assessoria, "conta da cadeira" e projeções.
- Pledges ("Nas lideranças") na soma — pode virar item sucessor se a mesa pedir.
- Novos filtros, seletor de cenário na URL, exportação ou alteração do que cada coluna mostra.
- Linha de totais dentro da tabela (tfoot) e bloco específico dos cards mobile — decisão do gate: rodapé compartilhado.
- Soma em outras listas (lideranças, apoiadores, demandas).
## Rabbit holes de produto
- **"Já que soma, mostra também média/%/cobertura."** Vira dashboard e reabre o B129. **Corte neste item:** só as 4 somas + contagem + a linha secundária de quantos têm expectativa.
- **"Somar pledges junto para ver o total real."** Muda o significado da coluna e cria expectativa sobre número que a coluna não mostra. **Corte:** v1 espelha a coluna; defer registrado.
- **Salvador: "somar cidade + zonas para o total bater com o estado."** É a dupla contagem proibida pela trilha da capital. **Corte:** uma vez só — cidade **ou** zonas.
## Questões em aberto (produto)
- **Rótulos:** A) UI (Pessimista/Média/Otimista) — **Decidido no gate (2026-09-14): A**; B) planilha (Mínimo/Regular/Otimista). Manter o vocabulário que a coluna já usa evita tradução mental.
- **Onde:** A) linha de totais no rodapé da tabela desktop, alinhada sob as colunas de votos/expectativa + bloco compacto equivalente no rodapé dos cards mobile; B) só uma linha compacta no rodapé compartilhado. **Decidido no gate (2026-09-14): B** — a linha compacta fica no rodapé compartilhado (o mesmo nos dois viewports, junto da contagem), sem linha dentro da tabela e sem bloco dedicado nos cards.
- **Incluir pledges ("Nas lideranças") na soma?** **Decidido no gate (2026-09-14): A — não incluir** (espelha a coluna; defer registrado se a mesa pedir). **Rejeitado:** B) sim, usando `expectativa ?? pledges` como valor efetivo.
## Referências
- GitHub Issue [#1005](https://github.com/fsolla/teqo/issues/1005)
- Rascunho UI (gate): `docs/plans/totais-recorte-lista-municipios-ui-draft.html`
- `docs/plans/remover-overview-lista-municipios.md` (B129, #266) — anti-goal a respeitar
- `docs/plans/salvador-pagina-detalhe-cidade.md` — Salvador conta uma vez (cidade ou 19 ZE)
- Rota `/campanha/municipios`, tabela `MunicipalityList`, cards `MunicipalityMobileCard` e rodapé `CampaignListFooter`
- Precedente de agregado do recorte: `/campanha/apoiadores` (`supporterListOverviewAggregate`)