# ROTEIRO TÉCNICO — Plataforma de Mobilização e Métricas de Lideranças

**Status:** planejamento — aguardando aprovação  
**Escopo:** especificação completa · **sem implementação nesta etapa**  
**WhatsApp:** Evolution API 2.3.7 (capacidades validadas na documentação oficial)

---

## 0. Resumo executivo

Plataforma nova e independente para medir **capacidade de mobilização observada** de lideranças via grupos de WhatsApp.

**Mede:** indicações (cliques), entradas, retenção, permanência, interação com conteúdos, crescimento, ranking por Índice de Mobilização.  
**Não mede:** votos, intenção de voto, apoio eleitoral, previsão eleitoral.

**Regra de entrega após aprovação:** 1 fase por vez → explicar → implementar o mínimo → testar → listar arquivos → validar → aguardar ok.

---

## 1. Arquitetura geral

### 1.1 Stack proposta

| Camada | Escolha | Por quê |
|--------|---------|---------|
| Backend | **Laravel 11 (PHP 8.3)** | Preferência do projeto; auth, filas, jobs, policies, excelentes para CRUD + webhooks |
| Frontend | **Inertia.js + Vue 3 + Tailwind** | UI profissional de produto; mobile-first / alta responsividade; manutenção única com Laravel |
| Banco app | **PostgreSQL** | Histórico temporal, JSON, agregações e índices fortes; separado do DB da Evolution |
| Cache/fila | **Redis** | Cache de métricas + filas (Laravel Horizon) |
| WhatsApp | **Evolution API 2.3.7** | Já definida; integração própria via HTTP + webhooks |
| Python | **Só se necessário (fase avançada)** | Jobs pesados de agregação/batch; MVP usa Jobs Laravel |

**Não obrigatório no MVP:** microserviços, Kafka, Elasticsearch.

### 1.2 Componentes

```
[Browser]
   │
   ├─ /l/{slug}          → App Laravel (atribuição + redirect WA)
   ├─ /r/{code}          → App Laravel (clique conteúdo + redirect destino)
   ├─ /admin/*           → Dashboard Inertia (auth)
   │
[Laravel App]
   │
   ├─ Webhooks ←──── Evolution API 2.3.7
   ├─ Jobs/Filas (Redis)
   ├─ Agregador de métricas
   └─ PostgreSQL (app)
   
[Evolution API]
   ├─ Instância WhatsApp
   ├─ Postgres próprio (Evolution)
   └─ Redis próprio (Evolution)
```

### 1.3 Princípios

1. **Idempotência** em todo evento Evolution (`event_id` único).
2. **Minimização LGPD** — hash de identificadores WhatsApp; métricas agregadas no dashboard.
3. **Atribuição best-effort** — clique → entrada com janela temporal + matching de identidade quando disponível.
4. **Agregações assíncronas** — eventos gravam fatos; métricas/ranking recalculam em jobs.
5. **Configurável** — pesos do índice em tabela/config, não hardcoded.

---

## 2. Fluxo completo

```
1. Admin cadastra liderança (nome, slug, grupo alvo)
2. Sistema gera link /l/{slug}
3. Pessoa acessa link
4. App registra click_attribution (liderança, IP hash, UA hash, timestamp, token)
5. Redirect para invite WhatsApp do grupo
6. Pessoa entra no grupo
7. Evolution envia GROUP_PARTICIPANTS_UPDATE (action=add)
8. Webhook Laravel valida assinatura/secret → enfileira ProcessGroupParticipantJob
9. Job: upsert participante (hash), membership_event, tenta vincular liderança (janela + token/match)
10. Ao longo do tempo: remove → membership_event; jobs de snapshot calculam retenção 7/30/60
11. Admin cria conteúdo + opcional link /r/{code}
12. App envia mensagem via Evolution (sendText/sendMedia)
13. Clique em /r/{code} → content_click → (quando possível) amarra à liderança de origem do membro
14. Jobs agregam métricas diárias / índice
15. Dashboard + ranking + alertas + relatório
```

### 2.1 Atribuição clique → entrada (estratégia)

| Nível | Método | Confiança |
|-------|--------|-----------|
| A | Match por identificador WA (quando o fluxo futuro permitir captura) | Alta |
| B | Janela temporal pós-clique (ex.: 0–72h) + 1 entrada sem liderança no grupo | Média |
| C | Sem match → entrada “orgânica/não atribuída” | — |

**Limitação Evolution:** webhook de participante informa JID/telefone (quando resolvido), **não** o link clicado. Atribuição é construída no app, não nativa da Evolution.

**Alternativa se match fraco:** página intermediária “Confirme entrada” (opcional, fase posterior) pedindo opt-in mínimo — só se aprovado por LGPD/produto.

---

## 3. Modelo de banco (PostgreSQL)

Convenções: `snake_case`, PK `bigint`/`uuid` onde fizer sentido, `created_at`/`updated_at`, soft deletes só onde necessário.

### 3.1 `users`
Admin do sistema.  
Campos: `id`, `name`, `email` unique, `password`, `role` (admin|operator), `remember_token`, timestamps.  
Índices: `email`.

### 3.2 `leaderships`
Lideranças.  
Campos: `id`, `name`, `slug` unique, `status` (active|inactive), `external_code` nullable, `notes` text nullable, timestamps, `deleted_at` nullable.  
Índices: unique `slug`, `status`.

### 3.3 `whatsapp_instances`
Instâncias Evolution usadas pelo app.  
Campos: `id`, `name`, `evolution_instance_name` unique, `api_base_url`, `api_key_encrypted`, `status`, `connected_at` nullable, timestamps.  
Índices: `evolution_instance_name`.

### 3.4 `whatsapp_groups`
Grupos monitorados.  
Campos: `id`, `whatsapp_instance_id` FK, `group_jid` unique, `name`, `invite_code` nullable, `invite_url` nullable, `status`, timestamps.  
Índices: unique `group_jid`, FK instance.

### 3.5 `leadership_group`
N:N liderança ↔ grupo (uma liderança pode ter 1+ grupos no futuro).  
Campos: `id`, `leadership_id` FK, `whatsapp_group_id` FK, `is_primary` bool, timestamps.  
Índices: unique (`leadership_id`,`whatsapp_group_id`).

### 3.6 `attribution_clicks`
Acessos aos links `/l/{slug}`.  
Campos: `id`, `leadership_id` FK, `whatsapp_group_id` FK nullable, `token` unique (uuid), `clicked_at`, `ip_hash`, `ua_hash`, `referer` nullable, `redirected_to`, `matched_membership_id` nullable, `match_confidence` (high|medium|none), timestamps.  
Índices: `leadership_id+clicked_at`, `token`, `clicked_at`.

### 3.7 `participants`
Pessoas no ecossistema (minimizadas).  
Campos: `id`, `wa_id_hash` unique (SHA-256 do JID/telefone normalizado + pepper), `first_seen_at`, `last_seen_at`, timestamps.  
Índices: unique `wa_id_hash`.  
**Decisão:** sem telefone/JID em claro nem criptografado. Não armazenar nome/foto no MVP.

### 3.8 `memberships`
Participação atual/histórica em grupo.  
Campos: `id`, `participant_id` FK, `whatsapp_group_id` FK, `leadership_id` FK nullable (origem atribuída), `attribution_click_id` FK nullable, `joined_at`, `left_at` nullable, `is_active` bool, `attribution_method` (token_window|manual|none), timestamps.  
Índices: unique ativo parcial (`participant_id`,`whatsapp_group_id`) onde `is_active`, `leadership_id+joined_at`, `joined_at`, `left_at`.

### 3.9 `membership_events`
Fatos imutáveis de entrada/saída.  
Campos: `id`, `membership_id` FK, `participant_id` FK, `whatsapp_group_id` FK, `event_type` (join|leave|promote|demote), `occurred_at`, `evolution_event_id` unique, `raw_payload_hash`, `processed_at`, timestamps.  
Índices: unique `evolution_event_id`, `occurred_at`, `whatsapp_group_id+occurred_at`.

### 3.10 `evolution_webhook_inbox`
Inbox idempotente.  
Campos: `id`, `event_name`, `evolution_event_id` unique, `payload` jsonb, `status` (received|processed|failed|ignored), `attempts`, `error` text nullable, `received_at`, `processed_at` nullable.  
Índices: unique `evolution_event_id`, `status+received_at`.

### 3.11 `contents`
Conteúdos enviados.  
Campos: `id`, `created_by` FK users, `title`, `body` nullable, `media_type` (text|image|video|document|link), `media_url` nullable, `destination_url` nullable, `status` (draft|scheduled|sent|failed), `scheduled_at` nullable, `sent_at` nullable, timestamps.  
Índices: `status`, `sent_at`.

### 3.12 `content_deliveries`
Envio por grupo.  
Campos: `id`, `content_id` FK, `whatsapp_group_id` FK, `evolution_message_id` nullable, `status`, `sent_at` nullable, `error` nullable, timestamps.  
Índices: `content_id`, `whatsapp_group_id+sent_at`.

### 3.13 `tracked_links`
Links `/r/{code}`.  
Campos: `id`, `content_id` FK, `code` unique, `target_url`, `is_active` bool, timestamps.  
Índices: unique `code`.

### 3.14 `content_clicks`
Cliques em conteúdos.  
Campos: `id`, `tracked_link_id` FK, `content_id` FK, `whatsapp_group_id` FK nullable, `leadership_id` FK nullable, `participant_id` FK nullable, `clicked_at`, `ip_hash`, `ua_hash`, timestamps.  
Índices: `content_id+clicked_at`, `leadership_id+clicked_at`.

### 3.15 `metric_daily_leadership`
Agregado diário por liderança.  
Campos: `id`, `leadership_id` FK, `date`, `clicks`, `joins`, `leaves`, `active_members`, `content_clicks`, `retention_7`, `retention_30`, `retention_60`, `growth`, timestamps.  
Índices: unique (`leadership_id`,`date`).

### 3.16 `metric_daily_global`
Agregado diário global.  
Campos: `id`, `date` unique, contadores espelhando dashboard geral, timestamps.

### 3.17 `mobilization_scores`
Índice calculado.  
Campos: `id`, `leadership_id` FK, `period_start`, `period_end`, `score`, `components` jsonb, `rank`, `calculated_at`, timestamps.  
Índices: unique (`leadership_id`,`period_start`,`period_end`), `score` desc.

### 3.18 `mobilization_index_config`
Pesos configuráveis.  
Campos: `id`, `version`, `weights` jsonb, `is_active` bool, `created_by`, timestamps.

### 3.19 `alerts`
Campos: `id`, `type`, `leadership_id` nullable, `content_id` nullable, `severity`, `body`, `severity` jsonb, `is_read` bool, `created_at`.  
Índices: `is_read+created_at`.

### 3.20 `audit_logs`
Campos: `id`, `user_id` nullable, `action`, `entity_type`, `entity_id`, `meta` jsonb, `ip_hash`, `created_at`.

---

## 4. API (Laravel — rotas internas/admin + públicas)

### 4.1 Públicas
| Método | Rota | Função |
|--------|------|--------|
| GET | `/l/{slug}` | Registra atribuição + redirect invite |
| GET | `/r/{code}` | Registra clique conteúdo + redirect |

### 4.2 Auth
| Método | Rota | Função |
|--------|------|--------|
| POST | `/login` | Login |
| POST | `/logout` | Logout |

### 4.3 Admin (auth + policy)
| Método | Rota | Função |
|--------|------|--------|
| GET/POST | `/admin/leaderships` | CRUD lideranças |
| PUT/PATCH/DELETE | `/admin/leaderships/{id}` | Atualizar/desativar |
| GET/POST | `/admin/groups` | Grupos + vínculo |
| POST | `/admin/groups/{id}/sync-participants` | Sync manual Evolution |
| GET/POST | `/admin/contents` | Conteúdos |
| POST | `/admin/contents/{id}/send` | Enfileira envio |
| GET | `/admin/dashboard` | Métricas gerais |
| GET | `/admin/leaderships/{id}/profile` | Perfil |
| GET | `/admin/ranking` | Ranking |
| GET | `/admin/reports/weekly` | Relatório |
| GET | `/admin/reports/export` | CSV/PDF |
| GET | `/admin/alerts` | Alertas |
| PUT | `/admin/settings/mobilization-index` | Pesos do índice |

### 4.4 Webhook
| Método | Rota | Função |
|--------|------|--------|
| POST | `/webhooks/evolution` | Inbox + enqueue |

> Inertia usa controllers que retornam props; endpoints JSON opcionais para export/gráficos.

---

## 5. Webhooks Evolution API 2.3.7 (reais)

Fonte: documentação oficial de webhooks v2.

### 5.1 Essenciais (MVP)
| Evento | Uso no sistema |
|--------|----------------|
| `GROUP_PARTICIPANTS_UPDATE` | Entrada/saída (`add`/`remove`; também `promote`/`demote` — registrar, pouco uso em métricas) |
| `GROUPS_UPSERT` | Grupo criado/conhecido |
| `GROUP_UPDATE` | Nome/metadados do grupo |
| `CONNECTION_UPDATE` | Saúde da instância |
| `QRCODE_UPDATED` | Onboarding conexão (admin) |

### 5.2 Úteis (fase conteúdos / ops)
| Evento | Uso |
|--------|------|
| `SEND_MESSAGE` / `MESSAGES_UPDATE` | Confirmar entrega de conteúdo |
| `MESSAGES_UPSERT` | Opcional — interação in-group (fase futura; não base do MVP de cliques) |

### 5.3 Endpoints Evolution a usar (integração outbound)
Documentados / padrão Evolution v2 (confirmar nomes exatos na fase 5 contra a instância 2.3.7):
- Criar/conectar instância
- Obter QR / status conexão
- Buscar grupos / participantes (`group` + `participants`)
- Enviar texto/mídia
- Configurar webhook da instância apontando para `/webhooks/evolution`

### 5.4 Limitações documentar na implementação
1. **Sem evento nativo “veio do link X”** → atribuição no app.  
2. **LID vs telefone** — 2.3.7 enriquece participantes; normalizar sempre para hash estável.  
3. **Saída voluntária vs remoção** — ambos chegam como `remove`; tratar igual para retenção.  
4. **Invite link** — obter/atualizar via API de grupo; se indisponível, cadastro manual da URL.  
5. **Interação com conteúdo no WhatsApp (views)** — não confiável via Baileys; usar **cliques em links rastreáveis** como proxy principal.

---

## 6. Jobs e filas

| Job | Trigger | Função |
|-----|---------|--------|
| `IngestEvolutionWebhookJob` | Webhook HTTP | Persistir inbox idempotente |
| `ProcessGroupParticipantJob` | Após ingest | join/leave + atribuição |
| `SyncGroupParticipantsJob` | Manual/cron | Reconciliar lista Evolution × DB |
| `SendContentJob` | Admin send | Enviar via Evolution + delivery |
| `ComputeDailyMetricsJob` | Cron diário | `metric_daily_*` |
| `ComputeMobilizationIndexJob` | Cron / on-demand | scores + ranking |
| `GenerateAlertsJob` | Pós-métricas | Queda retenção, spikes, top conteúdo |
| `GenerateWeeklyReportJob` | Cron semanal | Snapshot relatório |
| `ExportReportJob` | Request | CSV/PDF assíncrono |

**Fila:** Redis + `queue:work` / Horizon.  
**Idempotência:** unique `evolution_event_id`; locks por `participant+group` no processamento.

---

## 7. Segurança

1. Auth sessão (Laravel Breeze/Fortify) + CSRF no admin.  
2. Policies: só `admin` altera pesos/índice e instâncias.  
3. Webhook: secret em header/`apikey` compartilhado + allowlist IP opcional + rejeitar payload sem `event`.  
4. Links `/l` e `/r`: rate limit por IP; token opaco; sem expor IDs internos.  
5. Secrets Evolution em vault/env criptografado (`encrypted` cast).  
6. HTTPS obrigatório em produção.  
7. Headers de segurança (CSP básica, HSTS).  
8. Audit log em ações sensíveis (envio em massa, sync, alteração de pesos).

---

## 8. LGPD

### 8.1 Dados necessários
| Dado | Base | Tratamento |
|------|------|------------|
| Hash do WA JID/telefone | Legítimo interesse métricas agregadas / execução do serviço | SHA-256 + pepper; sem reverter no UI |
| Timestamp join/leave | Necessário retenção | Guardar |
| Clique atribuição (ip_hash, ua_hash) | Segurança + atribuição | Hash; retenção limitada (ex. 90 dias) |
| Conteúdos e cliques | Operação | Agregar no dashboard |

### 8.2 Não coletar no MVP
Nome completo do participante, foto, lista telefônica em claro no frontend, geolocalização precisa, mensagens privadas.

### 8.3 Controles
- Criptografia em repouso do identificador bruto (se precisar para sync).  
- Acesso ao identificador só via job interno.  
- Retenção: eventos brutos webhook X dias; fatos de membership permanentes agregáveis.  
- Direito de exclusão: apagar/anonimizar `participants` + desvincular memberships.  
- Dashboard só números agregados por liderança.

---

## 9. Dashboard — telas

1. **Login**  
2. **Dashboard geral** — cards + gráficos (base, retenção, interação) + alertas  
3. **Lideranças** — lista, criar/editar, status, slug/link  
4. **Perfil da liderança** — base, retenção 7/30/60, mobilização, evolução, top conteúdos, histórico  
5. **Ranking** — tabela Índice de Mobilização  
6. **Grupos** — vínculo Evolution, invite, sync, status conexão  
7. **Conteúdos** — criar, agendar, enviar, performance  
8. **Relatórios** — semanal + export CSV/PDF  
9. **Alertas** — lista e marcar lido  
10. **Configurações** — instância WA, pesos do índice, usuários  

Tom visual: produto de tecnologia (limpo, tipografia forte, gráficos claros) — detalhar UI na fase 12.

---

## 10. Índice de Mobilização (metodologia)

### 10.1 Indicadores (período configurável, default 30 dias)

| Código | Indicador | Ideia |
|--------|-----------|-------|
| A | **Aquisição líquida** | joins atribuídos − leaves da base atribuída |
| B | **Retenção 30d** | % da coorte que permanece após 30 dias |
| C | **Crescimento relativo** | variação % da base ativa da liderança no período |
| D | **Engajamento** | content_clicks da base / membros ativos médios |
| E | **Qualidade da atribuição** | % joins com match medium/high (penaliza volume “solto”) |

### 10.2 Normalização
Para cada indicador \(X\), normalizar entre lideranças ativas no período (min-max ou z-score truncado):

\[
N(X) = \frac{X - X_{min}}{X_{max} - X_{min} + \epsilon} \in [0,1]
\]

Evita crowning só por quem tem base maior: **A** usa taxa (joins/base elegível) além de volume absoluto em componente separado opcional com peso baixo.

### 10.3 Pesos default (configuráveis)

| Indicador | Peso |
|-----------|------|
| Retenção \(N(B)\) | 0.30 |
| Aquisição líquida normalizada \(N(A)\) | 0.25 |
| Engajamento \(N(D)\) | 0.20 |
| Crescimento relativo \(N(C)\) | 0.15 |
| Qualidade atribuição \(N(E)\) | 0.10 |

\[
IML = 100 \times \sum w_i N_i
\]

### 10.4 Salvaguardas
- Mínimo de amostra (ex.: ≥10 joins no período) senão flag “dados insuficientes” (não ranquear no topo cegamente).  
- Outliers: winsorizar top 5%.  
- Separar ranking “volume” (opcional) do ranking “Índice de Mobilização”.  
- UI sempre rotula: **Índice de Mobilização da Liderança** — nunca “votos”.

---

## 11. Roadmap de fases

| Fase | Nome | Entrega | Critério de pronto |
|------|------|---------|--------------------|
| **1** | Estrutura inicial | App Laravel + Inertia/Vue + env + Docker/compose app (Postgres/Redis app) | App sobe local/`/api/laravel` |
| **2** | Banco | Migrations das tabelas §3 | migrate ok |
| **3** | Cadastro lideranças | CRUD + slug único | Admin cria/edita |
| **4** | Links exclusivos | `/l/{slug}` + `attribution_clicks` + redirect | Clique registrado |
| **5** | Integração Evolution | Client HTTP, instância, webhook inbox idempotente | Evento teste processado 1x |
| **6** | Grupos e participantes | Mapear grupo, sync, join/leave | Memberships corretos |
| **7** | Histórico e retenção | Eventos + retenção 7/30/60 + jobs diários | Números batem com fixtures |
| **8** | Conteúdos | CRUD + envio Evolution | Msg chega no grupo |
| **9** | Links rastreáveis | `/r/{code}` + `content_clicks` | Clique medido |
| **10** | Métricas | Agregados diários + APIs dashboard | Cards preenchidos |
| **11** | Índice | Config pesos + job score + ranking | Ranking reproduzível |
| **12** | Dashboard UI | Telas §9 polidas | UX aprovada |
| **13** | Relatórios | Semanal + **PDF** (+ CSV opcional) | Export PDF ok |
| **14** | Testes | Feature/unit + webhook replay + carga leve | Suite verde |
| **15** | Produção | HTTPS, backups, monitoração, runbook | Go-live |

### Ordem ajustada vs sugestão original
Mantida quase igual; **métricas (10) antes do índice (11)** e **dashboard UI (12) depois dos dados** para não desenhar gráficos vazios.

### Método de trabalho (economia de tokens)
- 1 chat = 1 fase.  
- Plan curto → Agent só no escopo da fase.  
- Explore agent para docs Evolution na fase 5.  
- Sem `@` de árvore inteira.  
- Parar no 2º erro repetido e reformular.

---

## 12. Decisões aprovadas

| # | Tema | Decisão |
|---|------|--------|
| 1 | Domínio / SSL | **https://conecta.restor.app.br** — Let's Encrypt ativo (renovação automática). Evolution via proxy: `https://conecta.restor.app.br/evolution-api`. |
| 2 | Modelo de grupos | **Várias lideranças no mesmo grupo** (links distintos → mesmo `whatsapp_group`; ranking comparativo na mesma base). |
| 3 | Identificador WA | **Somente hash** (`wa_id_hash` + pepper). Sem armazenar telefone/JID criptografado. Sync usa Evolution como fonte. |
| 4 | Relatórios | **PDF** como entrega principal na fase 13 (CSV/Excel como complementar se útil). |
| 5 | Frontend | **Inertia.js + Vue 3 + Tailwind** — UI profissional de produto, **mobile-first / altamente responsiva** (não só “encolhe”: navegação, cards, gráficos e ranking usáveis no celular). |
| 6 | Identidade visual | **Azul e branco** no padrão MDB. Primário aproximado `#009FE3` / `#0077C8`, fundo branco, texto escuro; sem roxo/creme genéricos. |
| 7 | Qualidade de UI | **Produto profissional**, não “cara de IA”. Evitar layouts genéricos (cards idênticos, gradients óbvios, tipografia padrão de template). A Fase 1 é provisória; a partir da Fase 12 (e em cada tela nova) elevar composição, tipografia, hierarquia e mobile. |

### Implicações técnicas

- **Grupo compartilhado:** atribuição e ranking por `leadership_id` dentro do mesmo `whatsapp_group_id`; entradas orgânicas ficam sem liderança.
- **Sem PII criptografada:** jobs nunca persistem JID em claro; matching interno só via hash determinístico.
- **PDF:** stack prevista `barryvdh/laravel-dompdf` ou Browsershot (avaliar na fase 13); layout do PDF também legível em mobile ao abrir.
- **UI mobile:** breakpoints completos, tabelas → cards no mobile, gráficos com scroll/touch, menu off-canvas, touch targets ≥44px.

---

## 13. Próximo passo

Roteiro + decisões §12 aprovados pelo cliente.

→ Aguardando autorização explícita para iniciar **FASE 1 — Estrutura inicial**.

Quando autorizar a Fase 1: enviar a URL (mesmo que provisória) assim que existir, para já prever `APP_URL` / SSL / webhook.