P6.1 · Ingestão de custo e receita
Depende de P4.1 e P4.2. App `roi`.
O prompt
Construa a ingestão que alimenta o painel de ROI. Uma única tabela de fatos une gasto de anúncio e receita da Stripe — é isso que permite responder "quanto investi e quanto faturei" sem planilha.
1. Modelos
GastoDiario — granularidade de anúncio, não de campanha:
data, canal (meta/google), conta_id, campanha_id, campanha_nome, conjunto_id, conjunto_nome, anuncio_id, anuncio_nome, palavra_chave (só Google), impressoes, cliques, custo_centavos, conversoes_plataforma, valor_conversoes_plataforma_centavos.
Chave única (data, canal, anuncio_id, palavra_chave).
ReceitaEvento — cada cobrança real:
data, stripe_charge_id, stripe_customer_id, stripe_subscription_id, loja, plano, valor_centavos, tipo (primeira/renovacao/upgrade/estorno), mais os campos de atribuição copiados do Toque.
Atribuicao — a ponte: receita_evento, gasto_diario, modelo, confianca.
2. A ponte
utm_content do lead casa com anuncio_nome do gasto. Foi por isso que P2.3 e P4.1 exigiram nomenclatura rígida.
| Confiança | Quando |
|---|---|
exata | utm_content casou com um anúncio |
heuristica | só utm_campaign casou |
sem_atribuicao | não havia utm |
Mostre o percentual sem atribuição no painel. Saber que 18% da receita não tem origem conhecida é informação. Esconder é mentir para si mesmo.
3. Ingestões
Meta — diária às 5h, reprocessando os últimos 7 dias. A Meta ajusta números retroativamente; não confie no dado de ontem como final. Insights no nível ad, time_increment=1, campos spend, impressions, clicks, actions, action_values.
Google — diária às 5h, também reprocessando 7 dias:
SELECT segments.date, campaign.id, campaign.name, ad_group.id, ad_group.name,
ad_group_ad.ad.id, metrics.impressions, metrics.clicks,
metrics.cost_micros, metrics.conversions, metrics.conversions_value
FROM ad_group_ad WHERE segments.date DURING LAST_7_DAYS
cost_micros é micros. Divida por 10.000 para centavos. Escreva o teste — errar isso por um fator de 100 no painel já aconteceu em muito projeto.
Stripe — dupla garantia:
- Webhooks em tempo real (
invoice.paid,charge.refunded) criamReceitaEventona hora - Reconciliação diária listando as charges das últimas 72h e preenchendo o que faltou. Webhook perdido acontece; sem a reconciliação o painel mente
- Estorno gera
ReceitaEventocom valor negativo, não apaga o original
4. Métricas derivadas
Materialize em MetricaDiaria, por data, canal e campanha:
investido = soma do custo
receita_atribuida = soma da receita atribuida a campanha
leads = leads criados com essa utm_campaign
leads_qualificados = leads com faturamento >= R$ 30.000
vendas_radar = ReceitaEvento de planos radar
vendas_hub = ReceitaEvento do plano hub
CAC = investido / (vendas_radar + vendas_hub)
CPL = investido / leads
CPLQ = investido / leads_qualificados
ROAS_imediato = receita_atribuida / investido
ROAS_90d = receita de 90 dias da coorte / investido
MRR_novo = soma do recorrente das assinaturas novas
payback_dias = dias ate a receita acumulada cobrir o investido
ROAS_90d e payback_dias decidem escala. O ROAS_imediato sempre parece ruim num modelo com vendedor no meio — deixe isso explícito no painel, senão alguém corta uma campanha boa.
5. Coortes
Coorte por mês de entrada: quantos entraram, quanto já pagaram acumulado por mês desde a entrada, quantos viraram Hub, quantos cancelaram. É o que mostra se a máquina melhora ou piora com o tempo.
Critérios de aceite
- Teste de
cost_microspara centavos com valores de borda - Reprocessar 7 dias não duplica nem infla
- Estorno gera valor negativo e ajusta o ROAS
- Atribuição:
utm_contentcasando dáexata; só campanha dáheuristica; sem utm dásem_atribuicao - Ingestão real rodando com dados das contas, mostrando 7 dias de gasto e a receita da Stripe no mesmo período
- Reconciliação encontrando e preenchendo um webhook deliberadamente perdido — simule