# Plano - Módulo de Relatórios do Signal

> Status: **proposta para validação**. Nenhum código foi escrito. Baseado em investigação do repositório + verificação direta no banco de homologação.

## 1. Objetivo

Criar um módulo de Relatórios que substitua os relatórios fixos gerados hoje fora do Signal (Reporting Ninja) para os dois modelos de referência:

- **B2B** (mensal): tráfego GA4, aquisição Google/Meta Ads, SEO/orgânico.
- **Incorp** (semanal): Meta Lead Ads, Google Ads, hotsite GA4, leads, e vendas/VGV/ROI via CVCRM (quando ativo).

A entrega por e-mail e o resumo em imagem para WhatsApp ficam **fora deste escopo** (fase posterior).

## 2. Princípios de design (definidos com o time)

1. **Dirigido por integração.** O relatório não é 100% customizável como o dashboard. Um bloco aparece automaticamente quando o projeto tem a integração ativa e há dado. Regra: `bloco.sources ⊆ integrações ativas do projeto`.
2. **Liga/desliga apenas.** Sem edição de posição, tamanho ou ordem. Ordem fixa por convenção de integração. A única customização por cliente é habilitar/desabilitar blocos.
3. **Modelos padrão fixos por vertical** (B2B e Incorp), com override por cliente no nível liga/desliga.
4. **Ciclos fixos:**
   - Semanal: **d-7** comparado com os **7 dias anteriores**.
   - Mensal: **d-30** comparado com os **30 dias anteriores**.
   - A lógica de comparação período-a-período já existe no código (`computeTrend` / `buildTrends` em `DashboardDataController`) e já produz exatamente essa janela anterior.
   - **Cada ciclo é ativável/desativável por projeto** (ex.: um projeto pode ter só o semanal ligado, outro os dois). Faz parte da config do projeto.
5. **Snapshot imutável, persistido para consulta e envio.** Todo relatório gerado **congela seus dados e é salvo no banco** (`report_runs` + `report_snapshots`), para: (a) consulta posterior do histórico de relatórios gerados, e (b) envio por e-mail (fase posterior). Fatos são mutáveis (upsert por `grain_id`) e sofrem clamp D-1; sem snapshot salvo, um relatório reaberto diverge do enviado.
6. **Definição dinâmica de "Lead".** Varia conforme a integração ativa. Regra inicial: se há CRM ativo (Exent Hub ou CVCRM), **Lead = CRM**; o restante é tratado como **conversões**. Resolvido em runtime.

## 3. Cobertura real hoje (homologação: 20 projetos, 10 B2B / 10 Incorp)

| Integração | Projetos ativos | Volume de dado | Papel |
|---|---|---|---|
| Google Ads | 14/20 | campanhas 12k, anúncios 20k, keywords 110k | Base de aquisição |
| GA4 | 12/20 | páginas 356k, eventos 191k, campanhas 48k | Base de tráfego |
| Exent Hub | 11 ativo (+1 pausado) | 10,5k leads | Base de leads/CRM |
| CVCRM | 6/20 | leads 31k, reservas 2k, unidades 2,2k | Só Incorp (não é base) |
| Meta Ads | 5/20 | anúncios 14k | Aquisição secundária |
| Search Console | 1/20 | queries 1,1M | Bloco opcional (raro) |
| Instagram Social | 1/20 | posts 1,4k | Bloco opcional (raro) |

**Conclusão:** a espinha (GA4 + Google Ads + Exent Hub) já está populada. Cerca de 80% dos dois relatórios de referência é construível com o dado que já existe.

## 4. O que genuinamente falta (baixo volume, baixa prioridade)

Confirmado no banco: as colunas existem mas estão **100% vazias** (o provider nunca as escreve).

| Dado | Situação | Dependência |
|---|---|---|
| Device (GA4) | coluna `device_type` existe, 0 linhas populadas | Novo sync GA4 (dimensão `deviceCategory`) |
| Cidade (GA4) | coluna `country` existe, 0 linhas populadas | Novo sync GA4 (dimensão `city`) |
| Landing pages (GA4) | coluna `landing_page` vazia | Aproximável via `fact_ga4_page_daily.page_path` (já populado) - sem sync novo |
| Criativos Meta | thumbnail buscado ao vivo, nunca persistido (cache ~1h) | Baixar e persistir no snapshot |
| Criativos Google Ads | não existe nenhum criativo/thumbnail | Fora do MVP |
| New vs returning (GA4) | não coletado | Novo sync GA4 (baixa prioridade) |

Nenhum desses é dado de base. Todos alimentam blocos que ligam quando o dado chega.

## 5. Risco/pré-requisito bloqueante: drift de schema

O banco de homologação está **à frente das migrations**. Exemplo confirmado: `fact_marketing_daily` tem 32 colunas no banco, mas a migration cria ~15. Colunas que existem só no banco: `landing_page`, `device_type`, `country`, `campaign_id/name`, `adset/ad`, `keyword_text`, `reach`, `leads_platform`, `revenue`, `metadata`.

Isso já quebrou produção antes (padrão conhecido do projeto). **Qualquer deploy em produção construído pelas migrations não terá essas colunas e o `DashboardDataController` quebra.** O módulo de relatório herdaria o risco. Por isso a reconciliação de schema é pré-requisito, independente do relatório.

## 6. Plano faseado

### Fase 0 - Reconciliação de schema (pré-requisito, não é sobre report)

- Auditar o drift entre migrations e banco em todas as fact tables (feito só em `fact_marketing_daily` até agora).
- Criar migration(s) de reconciliação para produção bater com homologação.
- **Único item que precisa vir antes de tudo.** Sem isso, nada vai a produção com segurança.

### Fase 1 - Estrutura base do relatório sobre o dado existente (MVP)

- Modelo de dados (conceitual):
  - `report_templates` - modelo fixo por vertical (b2b/incorp), `is_default`.
  - `report_template_blocks` - blocos default-on do modelo (`block_code`, `default_enabled`; sem sort/size).
  - **Config do projeto** (liga/desliga por cliente) - `Project.report_config` (coluna JSONB, espelhando o padrão de `Project.tab_config`). Guarda:
    - `cycles`: `{ semanal: bool, mensal: bool }` - **ativação de cada ciclo por projeto**.
    - `sections`: `{ [integration]: bool }` - integração entra no relatório (com cabeçalho) ou não.
    - `blocks`: `{ [block_code]: bool }` - liga/desliga de cada gráfico.
  - `report_runs` - **cada relatório gerado**, por ciclo (`cadence` weekly/monthly, `period_start/end`, `compare_start/end`, `status`, `generated_at`). Persistido no banco para consulta posterior e base do envio por e-mail.
  - `report_snapshots` - retrato imutável do run (`payload` jsonb com os dados congelados, `provider_freshness` via `last_completed_at`, `readiness`). É o que garante que o relatório salvo/enviado não muda depois.
- Resolução de blocos no run: template da vertical → filtra por integrações ativas → aplica config do projeto (ciclo ativo + seções + blocos) → resolve fonte de cada métrica pela regra dinâmica de Lead.
- Reaproveita `TabBlockDefinitions.BLOCKS.sources`, `MetricDefinitions` e a agregação/comparação de `DashboardDataController`.
- Cobre a maioria dos dois modelos de referência sem depender de sync novo.
- A **tela de relatórios gerados** (item do sidebar) lista os `report_runs` salvos, por projeto/ciclo, para consulta do histórico.

### Fase 2 - Preencher lacunas (incremental, em paralelo)

- Sync GA4 de device + cidade (colunas já existem no banco).
- Persistência de criativo Meta no snapshot (download no momento do run).
- Cada correção "acende" o bloco correspondente quando fica pronta.

### Fora de escopo (fase posterior)

- Envio por e-mail e destinatários (`report_recipients`, `report_deliveries`).
- Resumo em imagem para WhatsApp.
- Comentários/insights consultivos por bloco.
- Criativos de Google Ads.

## 7. Pontos a validar antes de soltar

1. Aceite da **Fase 0 como pré-requisito** (reconciliação de schema antes de qualquer deploy).
2. Aceite de **manter device/cidade fora do caminho crítico** (Fase 2, não bloqueia o MVP).
3. Confirmação da **regra dinâmica de Lead** (CRM ativo → Lead = CRM; senão conversões).
4. Estratégia de **snapshot de criativo** (PDF fechado vs blob no banco) - a definir tecnicamente.
5. Lista final de blocos default-on por vertical (B2B e Incorp).

---

## Anexo - referência de investigação

Fontes de dado por provider, matrizes B2B/Incorp campo-a-campo e evidências em código estão na investigação que originou este plano (models, migrations, providers, controllers, `MetricDefinitions`, `TabBlockDefinitions`). Atribuição venda→anúncio via Exent Hub é experimental e só funciona com CVCRM ativo (cobertura pequena).
