OMNI Brasil  /  Dashboard — Spec Técnica

Dashboard OMNI Brasil

Especificação Técnica Completa — Fonte Única de Verdade
1. Arquitetura
Pipedrive API
Meta Ads API
Google Ads API
──►
API Express
VPS · Porta 3030
──►
Nginx
dash.omnihypnosis.com.br
──►
Browser
dashboard.html
Fluxo de dados
  1. Browser acessa http://dash.omnihypnosis.com.br/dashboard-omni/dashboard.html
  2. Após login, loadAll() dispara 16 requests simultâneos para a API
  3. A API Express (porta 3030) busca dados em Pipedrive, Meta Ads e Google Ads
  4. Cada endpoint tem cache em memória (TTL 5–15 min) para evitar rate limit
  5. As funções render*() recebem os dados e atualizam o DOM
2. Infraestrutura

VPS

Hostmichael@76.13.169.211
SOLinux (Ubuntu)
Processo APINode.js direto (sem PM2)

Diretórios críticos no VPS

DiretórioConteúdo
/home/michael/omni_brasil/apps/dashboard-omni-api/Código da API Express
/home/michael/omni_brasil/apps/dashboard-omni-api/public/HTML servido pela API
/tmp/omni-apresentacoes/dashboard-omni/HTML servido pelo Nginx (URL principal)
/home/michael/omni_brasil/apps/dashboard-omni-api/routes/Rotas: pipedrive.js, meta.js, google.js
/home/michael/omni_brasil/apps/dashboard-omni-api/services/Integrações com APIs externas

Processo de Deploy

# 1. Editar SEMPRE o arquivo local (fonte da verdade)
apps/dashboard-omni/dashboard.html

# 2. Commitar no repositório
git add apps/dashboard-omni/dashboard.html
git commit -m "fix/feat: descrição"

# 3. Copiar para o Nginx (arquivo que o browser carrega)
scp apps/dashboard-omni/dashboard.html \
  michael@76.13.169.211:/tmp/omni-apresentacoes/dashboard-omni/dashboard.html
Nunca editar diretamente no VPS. A fonte da verdade é sempre o arquivo local no repositório.

SSL (pendente)

Pendente HTTPS inativo — certificado sem permissão

Causa: Nginx não roda como root e não consegue ler o privkey1.pem

Solução (Michael, como sudo no VPS):

sudo chmod 644 /etc/letsencrypt/archive/go.omnihypnosis.com.br/privkey1.pem
sudo systemctl restart nginx
3. Arquivo Principal
Localização
apps/dashboard-omni/dashboard.html — único arquivo (~3.200 linhas)
Linhas (aprox.)Conteúdo
1–11Head — fontes Google, Chart.js CDN
12–980Style — todo o CSS (variáveis, componentes, telas)
981–1986Body — HTML das telas (sidebar, telas, modais)
1987–3240Script — todo o JavaScript
// Constante da API (linha ~2270)
const API = ''; // URL relativa — mesmo domínio em produção
// Para desenvolvimento local com Live Server:
// const API = 'http://76.13.169.211:3030';
4. Design System

Variáveis de Cor

VariávelValorUso
--black#050505Sidebar, fundos mais escuros
--dark#0D0D0DFundo principal
--gold#C8A84BCor primária OMNI — destaques, títulos
--cream#F5F0E8Texto principal
--teal#2A9D8FCor secundária — SDR, SDR-IA
--success#4CAF7DPositivo / bom
--error#E05C5CErros, alertas
--warn#E8A24BAtenção

Componentes CSS reutilizáveis

ClasseUsoGerado via JS?
.hfunnelFunil horizontal escalonado — Closer, SDR, SDR-IA, TráfegoSim — buildHfunnel()
.kpi-cardCards KPI com label, valor e deltaNão (HTML fixo)
.panelContainer padrão de seção com fundo dark-el e bordaNão
.aqrc-tableTabela AQRC — usada com .table-scroll-v para scrollParcialmente
.table-scroll-vWrapper com scroll vertical (max-height: 420px) — AQRC diárioNão
.metric-miniMétricas em linha — Tráfego Meta / GoogleNão
.funnel-rowFunil vertical com barras horizontais — Visão GeralParcialmente
.tagBadges: .tag-up .tag-down .tag-gold .tag-grayNão

5. Telas
01 Visão Geral Funcionando

ID HTML: #screen-visao-geral  |  Dados: Pipedrive + Meta Ads

Endpoints consumidos

EndpointO que alimenta
/api/pipedrive/kpis5 KPI cards (faturamento, ROAS, CAC, etc.)
/api/pipedrive/funnelFunis SDR e Closer
/api/meta/overviewMétricas de tráfego (clicks, CPM, CTR, leads)
/api/meta/dailyGráfico de linha (investimento + leads por dia)

Funções JS

renderKPIs(d) · renderMetaOverview(d) · renderVisaoGeralFunnels(funnel, meta) · renderMetaDaily(data)

02 Gerenciador Funcionando — 6 fontes

ID HTML: #screen-gerenciador  |  Dados: Meta Ads + Google Ads + Pipedrive (por fonte UTM)

Endpoints consumidos

EndpointO que alimenta
/api/meta/campaignsTabela hierárquica Meta (campanha → conjunto → anúncio)
/api/google/campaignsTabela hierárquica Google (campanha)
/api/pipedrive/funnelMétricas de funil cruzadas por origem
/api/pipedrive/gerenciadorFontes Pipedrive por utm_source (mql, tentativas, conectados, avaliados, agendadas, realizadas, oportunidades, negociacoes, vendas, faturamento)

Payload da API (por anúncio Meta)

{ "campaign": "Nome", "adset": "Nome", "ad": "Nome",
  "spend": 32.19, "leads": 0, "cpl": "—",
  "impressions": 428, "clicks": 7, "ctr": "1.64", "cpc": "4.60", "cpm": "75.21" }

Grupos e prioridade UTM

Grupoutm_sourcePrioridadeExpand
Meta Adssocial, facebookads, instagram_stories, instagram_feed, demand-genPrevalece sobre canal manualCampanhas → Adsets → Anúncios (spend real)
Google Adsgoogle, google_yt, pmaxPrevaleceCampanhas (spend real + PD proporcional)
Orgânicomídias sociais (social seller), mídias sociais (outros), instagram-omni, instagram, insta, ig, site, youtubePrevaleceSem spend, apenas dados PD reais por fonte
Indicaçãocontém "indica"Só entra se não tem UTM prioritárioSem spend, apenas dados PD reais por fonte
Diretodireto, acesso-direto, omnihypnosis.com.br, '' (vazio)FallbackSem spend, apenas dados PD reais por fonte
Outrosqualquer outro (inside sales, prospecção em eventos, testeSource, etc.)FallbackSem spend, apenas dados PD reais por fonte

Nota: MySQL connection pode cair após idle — recarregar a página resolve automaticamente.

Função JS: renderGerenciador(metaCampaigns, funnelData, pdSources, googleCampsData)

03 AQRC Funcionando

ID HTML: #screen-aqrc  |  Endpoint: /api/pipedrive/aqrc

Mapeamento AQRC

LetraCampo PipedriveSignificado
A[OBA] Data MQLLeads que entraram (data de entrada na etapa MQL)
Q[OBA] Data Reunião AgendadaMarcaram reunião
R[OBA] Data Reunião RealizadaRealizaram reunião
C[OBA] Data de GanhoCompraram

Lógica de % (implementada)

% QQ ÷ A × 100 (conversão MQL → Reunião Agendada)
% RR ÷ Q × 100 (conversão Agendada → Realizada)
% CC ÷ R × 100 (conversão Realizada → Compra)
Conv.C ÷ A × 100 (conversão total MQL → Compra)

Nota: A coluna % A foi removida em 2026-04-13 — A é a métrica base sem denominador anterior, exibia sempre .

Colunas atuais: Data/Semana/Mês · A · % Q · Q · % R · R · % C · C · Conv.

Ordem: cada percentual aparece entre os dois valores que conecta (A → % Q → Q → % R → R → % C → C).

Tabelas: #aqrc-diario (scroll vertical .table-scroll-v) · #aqrc-semanal · #aqrc-mensal

04 Painel Closer Funcionando

ID HTML: #screen-closer  |  Endpoint: /api/pipedrive/closers  |  Pipeline: 21

Etapas do funil (Pipeline 21)

Stage IDEtapa
112Reunião Agendada
113Reunião Realizada
116Oportunidade
114Negociação
280Venda

Payload da API (por closer)

{ "name": "Nome Closer", "avatar": null,
  "r_agendadas": 31, "r_realizadas": 18,
  "oportunidades": 10, "negociacoes": 2, "vendas": 2,
  "faturamento": 27000, "noshow": 42 }

Funções: renderClosers(data) · buildHfunnel(stages, color) · agentHeader(...)

Pendência: Scroll vertical da tela completa — conteúdo inferior pode ficar cortado.

05 Painel SDR Funcionando

ID HTML: #screen-sdr  |  Endpoint: /api/pipedrive/sdrs  |  Pipeline: 65

Payload da API (por SDR)

{ "name": "Nome SDR", "avatar": null,
  "mql": 584, "tentativas": 212, "conectados": 16,
  "avaliados": 1, "r_agendadas": 0, "r_realizadas": 0,
  "vendas": 0, "faturamento": 0, "noshow": 0 }

Campos opcionais (aparecem no funil quando > 0): leads_gerais, conectado_ia — precisam ser adicionados no backend.

Pendência: Scroll da tela completa.

06 Painel SDR-IA Funcionando

ID HTML: #screen-sdr-ia  |  Endpoint: /api/pipedrive/sdr-ia  |  Pipeline: 67

Payload da API

{ "ok": true,
  "total_leads": 715, "contato_ia": 402, "mql": 6,
  "conectados": 0, "avaliados": 0,
  "r_agendadas": 0, "r_realizadas": 0, "noshow": 0 }

Campos opcionais (aparecem quando > 0): tentativas, oportunidades, negociacoes, vendas

Pendência: Scroll da tela completa.

07 Tráfego Meta Ads Funcionando

ID HTML: #screen-meta

Endpoints

EndpointO que alimenta
/api/meta/overviewKPIs — Investido, Leads, CPL, CPM, CPC, CTR
/api/meta/funnel-lpFunil Landing Page (Impressões → Cliques → View Página → Leads)
/api/meta/funnel-formFunil Form Nativo (Impressões → Cliques → Leads)
/api/meta/dailyGráfico de investimento diário

Pendência: Scroll da tela completa — parte inferior não visível.

08 Tráfego Google Ads A verificar

ID HTML: #screen-google  |  API: Google Ads direta

Payload real da API (/api/google/overview)

{ "ok": true, "spend": "5494.50", "clicks": 1216,
  "impressions": 23164, "leads": 142,
  "cpc": "4.52", "cpm": "237.20", "ctr": "5.25", "cpl": "38.69" }

Atenção: Todos os campos numéricos chegam como string. A função renderGoogleOverview faz parseFloat e parseInt corretamente.

Pendência: Verificar se a tela não renderiza por falta de scroll ou por erro JS. Checar console do browser.

6. Mapa de Funções JavaScript
FunçãoLinhaEntradaEfeito
loadAll()~2295Dispara 16 fetches, chama todas as render*
getDateRange()~2272Retorna {from, to} baseado no filtro
navigate(screen)~2016string IDAtiva a tela correspondente
renderKPIs(d)~2378/kpisPopula os 5 KPI cards
renderMetaOverview(d)~2388/meta/overviewPopula métricas Meta
renderGerenciador(...)~2420metaCampaigns, funnelData, pdSources, googleCampsDataTabela hierárquica com 6 grupos (Meta/Google/Orgânico/Indicação/Direto/Outros) por prioridade UTM
renderVisaoGeralFunnels(...)~2675funnel, metaPopula funis SDR/Closer na Visão Geral
buildHfunnel(stages, color)~2794[{lbl,val}], corRetorna HTML do funil escalonado
agentHeader(...)~2824nome, fat, ns, avatar, corRetorna HTML do cabeçalho do agente
renderClosers(data)~2840/closersGera funis por closer em #closer-vert-chart
renderSDRs(data)~2893/sdrsGera funis por SDR em #sdr-vert-chart
renderSDRIA(data)~2929/sdr-iaGera funil único em #sdr-ia-vert-chart
renderAQRC(data)~2975/aqrcPopula tabelas diário / semanal / mensal
renderMetaFunnels(...)~3054lpData, formDataPopula funis LP e Form Nativo
renderMetaDaily(data)~3086/meta/dailyAtualiza gráficos de linha
renderGoogleOverview(d)~3168/google/overviewPopula KPIs Google
renderGoogleDaily(data)~3200/google/dailyAtualiza gráfico Google
renderGoogleCampaigns(data)~3220/google/campaignsPopula tabela de campanhas Google
fmtBRL(n, dec)~2362numberFormata como 1.234,56
fmtInt(n)~2366numberFormata inteiro com . como separador
noshowColor(ns)~2785number %Retorna cor CSS por % de no-show
exportGerenciadorCSV()~2640Download CSV da tabela Gerenciador
7. Endpoints da API

Base: http://dash.omnihypnosis.com.br/api/  |  Params: ?from=YYYY-MM-DD&to=YYYY-MM-DD

Limpar cache: POST /cache/clear

EndpointCacheO que retorna
/api/pipedrive/funnel10 minFunis SDR e Closer agregados
/api/pipedrive/kpis5 minFaturamento, vendas, ticket médio
/api/pipedrive/closers10 minArray de closers com métricas
/api/pipedrive/sdrs10 minArray de SDRs com métricas
/api/pipedrive/sdr-ia10 minObjeto único SDR-IA agregado
/api/pipedrive/aqrc10 minArrays daily, weekly, monthly
/api/pipedrive/gerenciador10 minFontes Pipedrive por utm_source: mql, tentativas, conectados, avaliados, agendadas, realizadas, oportunidades, negociacoes, vendas, faturamento
/api/pipedrive/pages10 minArray de páginas com métricas
/api/meta/overview10 minKPIs Meta Ads agregados
/api/meta/campaigns10 minArray de anúncios com métricas
/api/meta/funnel-lp10 minFunil Landing Page
/api/meta/funnel-form10 minFunil Form Nativo
/api/meta/daily10 minArray diário {date, spend, leads}
/api/google/overview10 minKPIs Google (campos como string — usar parseFloat)
/api/google/campaigns15 minArray de campanhas Google
/api/google/daily10 minArray diário Google
8. Pendências em Aberto
✓ P1 Scroll nas telas Closer, SDR, SDR-IA, Meta, Google

Resolvido em 2026-04-13.

Causa real: .main tinha min-height: 100vh em vez de height: 100vh, então overflow-y: auto não ativava.

Solução aplicada: Mudança de min-height: 100vh para height: 100vh no CSS da .main (~linha 168 do dashboard.html).

✓ P2 Gerenciador: multi-fonte com prioridade UTM

Resolvido em 2026-04-13.

Implementação: 6 grupos com regra de prioridade UTM (Meta Ads, Google Ads, Orgânico, Indicação, Direto, Outros). Novo endpoint /api/pipedrive/gerenciador retorna fontes Pipedrive por utm_source. Meta Ads e Google Ads expandem em campanhas com spend real. Orgânico/Indicação/Direto/Outros mostram apenas dados PD reais por fonte.

Assinatura: renderGerenciador(metaCampaigns, funnelData, pdSources, googleCampsData)

P3 SSL/HTTPS

Causa: Nginx não roda como root, não consegue ler privkey1.pem.

Solução (Michael, com sudo):

sudo chmod 644 /etc/letsencrypt/archive/go.omnihypnosis.com.br/privkey1.pem
sudo systemctl restart nginx
✓ P4 Tela Google Ads — verificar renderização

Resolvido em 2026-04-13. Era o P1 (scroll) que impedia ver o conteúdo. Com o fix de height: 100vh, a tela Google Ads renderiza normalmente.

✓ P5 AQRC — coluna % A sempre exibia "—"

Resolvido em 2026-04-13. Coluna % A removida das três visões (diário, semanal, mensal). A é a métrica base sem etapa anterior — não há conversão a calcular.

✓ P6 Date picker disparava reload ao digitar a data

Resolvido em 2026-04-13. Removido onchange="applyCustomDate()" dos inputs de data. O dashboard agora só recalcula ao clicar em Aplicar.

✓ P7 Google Ads — integração restaurada

Resolvido em 2026-04-14. 121 campanhas retornando corretamente.

Causa raiz: GOOGLE_ADS_MCC_ID no .env causava permission denied — as credenciais OAuth reconectadas não tinham acesso via conta manager (MCC). A API precisa acessar o customer diretamente.

Fix aplicado: Linha GOOGLE_ADS_MCC_ID comentada no /home/rafa/omni_brasil/apps/dashboard-omni-api/.env. O código em google-direct.js já trata o campo como opcional (if (mccId) opts.login_customer_id = mccId) — não requer alteração de código.

Vault sincronizado: /opt/omni-vault/.env.master atualizado. PM2 dump salvo — processo sobrevive reinicialização da VPS.

Se voltar a falhar: Verificar GOOGLE_ADS_REFRESH_TOKEN no .env. Reautenticar via script OAuth e garantir que GOOGLE_ADS_MCC_ID permanece comentado.

9. Regras de Manutenção

Como fazer uma mudança segura

01
Identificar: usar Grep no arquivo para encontrar a função JS ou classe CSS afetada antes de qualquer edição. Ver Seção 6 (Mapa de Funções).
02
Ler o bloco completo antes de editar — nunca editar código que não foi lido.
03
Editar apenas o bloco identificado — não tocar no que está ao redor.
04
Testar no browser antes do commit. Verificar todas as telas que poderiam ser afetadas.
05
Commitar com mensagem descritiva, depois fazer deploy via scp para o Nginx.

O que NUNCA fazer

Editar diretamente no VPS — a fonte da verdade é sempre o repositório local.
Editar classes CSS globais (.panel, .kpi-card, .hfunnel) sem verificar o impacto em todas as 8 telas.
Editar funções helper (buildHfunnel, agentHeader, fmtBRL) sem rastrear todos os usos com Grep.
Criar backups inline no arquivo — backups ficam em apps/dashboard-omni/backups/.
Editar em cima de um backup — confirmar sempre que o arquivo aberto é apps/dashboard-omni/dashboard.html.

Quando adicionar campo novo da API

  1. Verificar o payload real: curl -s 'http://76.13.169.211:3030/api/ROTA?from=...&to=...'
  2. Adicionar o campo na função render* correspondente com fallback: data.campo_novo || 0
  3. Não alterar o loadAll() nem os endpoints — mudança só no render
10. Changelog
2026-04-14
  • Google Ads restaurado (P7): Causa raiz identificada — GOOGLE_ADS_MCC_ID no .env causava permission denied com as novas credenciais OAuth. Linha comentada, acesso direto ao customer, 121 campanhas retornando. Vault e PM2 dump atualizados.
  • Funil SDR — % Avaliado → R. Agendada: Adicionada seta de conversão entre Avaliado (SDR) e R. Agendada (Closer) na Visão Geral.
  • AQRC — colunas reordenadas + % C adicionada: Percentuais agora aparecem entre os valores que conectam: A · % Q · Q · % R · R · % C · C · Conv. Adicionado % C (C ÷ R × 100 — conversão Realizada → Compra).
2026-04-13
  • Gerenciador multi-fonte: 6 grupos com prioridade UTM (Meta Ads + Google Ads + Orgânico + Indicação + Direto + Outros). Novo endpoint /api/pipedrive/gerenciador. Assinatura: renderGerenciador(metaCampaigns, funnelData, pdSources, googleCampsData).
  • Fix scroll geral: .main mudou de min-height: 100vh para height: 100vh, ativando overflow-y: auto em todas as telas.
  • AQRC — coluna % A removida: A é métrica base sem denominador anterior — coluna sempre exibia .
  • Date picker — só aplica no botão: Removido onchange dos inputs de data. Reload só ocorre ao clicar em Aplicar.
  • Documentação técnica: Spec publicada online em dash.omnihypnosis.com.br/dashboard-spec.html.
  • Organização: Removidos dashboard-spec.md e backups de 10/04 que causavam confusão de versão.
Período anterior (até 2026-04-12)
  • Layout hfunnel Closer/SDR/SDR-IA
  • AQRC % e scrollbar diário
  • Deploy infraestrutura inicial