Dashboard OMNI Brasil
- Browser acessa
http://dash.omnihypnosis.com.br/dashboard-omni/dashboard.html - Após login,
loadAll()dispara 16 requests simultâneos para a API - A API Express (porta 3030) busca dados em Pipedrive, Meta Ads e Google Ads
- Cada endpoint tem cache em memória (TTL 5–15 min) para evitar rate limit
- As funções
render*()recebem os dados e atualizam o DOM
VPS
| Host | michael@76.13.169.211 |
| SO | Linux (Ubuntu) |
| Processo API | Node.js direto (sem PM2) |
Diretórios críticos no VPS
| Diretório | Conteú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
SSL (pendente)
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
apps/dashboard-omni/dashboard.html — único arquivo (~3.200 linhas)
| Linhas (aprox.) | Conteúdo |
|---|---|
| 1–11 | Head — fontes Google, Chart.js CDN |
| 12–980 | Style — todo o CSS (variáveis, componentes, telas) |
| 981–1986 | Body — HTML das telas (sidebar, telas, modais) |
| 1987–3240 | Script — 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';
Variáveis de Cor
| Variável | Valor | Uso |
|---|---|---|
| --black | #050505 | Sidebar, fundos mais escuros |
| --dark | #0D0D0D | Fundo principal |
| --gold | #C8A84B | Cor primária OMNI — destaques, títulos |
| --cream | #F5F0E8 | Texto principal |
| --teal | #2A9D8F | Cor secundária — SDR, SDR-IA |
| --success | #4CAF7D | Positivo / bom |
| --error | #E05C5C | Erros, alertas |
| --warn | #E8A24B | Atenção |
Componentes CSS reutilizáveis
| Classe | Uso | Gerado via JS? |
|---|---|---|
| .hfunnel | Funil horizontal escalonado — Closer, SDR, SDR-IA, Tráfego | Sim — buildHfunnel() |
| .kpi-card | Cards KPI com label, valor e delta | Não (HTML fixo) |
| .panel | Container padrão de seção com fundo dark-el e borda | Não |
| .aqrc-table | Tabela AQRC — usada com .table-scroll-v para scroll | Parcialmente |
| .table-scroll-v | Wrapper com scroll vertical (max-height: 420px) — AQRC diário | Não |
| .metric-mini | Métricas em linha — Tráfego Meta / Google | Não |
| .funnel-row | Funil vertical com barras horizontais — Visão Geral | Parcialmente |
| .tag | Badges: .tag-up .tag-down .tag-gold .tag-gray | Não |
ID HTML: #screen-visao-geral | Dados: Pipedrive + Meta Ads
Endpoints consumidos
| Endpoint | O que alimenta |
|---|---|
| /api/pipedrive/kpis | 5 KPI cards (faturamento, ROAS, CAC, etc.) |
| /api/pipedrive/funnel | Funis SDR e Closer |
| /api/meta/overview | Métricas de tráfego (clicks, CPM, CTR, leads) |
| /api/meta/daily | Gráfico de linha (investimento + leads por dia) |
Funções JS
renderKPIs(d) · renderMetaOverview(d) · renderVisaoGeralFunnels(funnel, meta) · renderMetaDaily(data)
ID HTML: #screen-gerenciador | Dados: Meta Ads + Google Ads + Pipedrive (por fonte UTM)
Endpoints consumidos
| Endpoint | O que alimenta |
|---|---|
| /api/meta/campaigns | Tabela hierárquica Meta (campanha → conjunto → anúncio) |
| /api/google/campaigns | Tabela hierárquica Google (campanha) |
| /api/pipedrive/funnel | Métricas de funil cruzadas por origem |
| /api/pipedrive/gerenciador | Fontes 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
| Grupo | utm_source | Prioridade | Expand |
|---|---|---|---|
| Meta Ads | social, facebookads, instagram_stories, instagram_feed, demand-gen | Prevalece sobre canal manual | Campanhas → Adsets → Anúncios (spend real) |
| Google Ads | google, google_yt, pmax | Prevalece | Campanhas (spend real + PD proporcional) |
| Orgânico | mídias sociais (social seller), mídias sociais (outros), instagram-omni, instagram, insta, ig, site, youtube | Prevalece | Sem spend, apenas dados PD reais por fonte |
| Indicação | contém "indica" | Só entra se não tem UTM prioritário | Sem spend, apenas dados PD reais por fonte |
| Direto | direto, acesso-direto, omnihypnosis.com.br, '' (vazio) | Fallback | Sem spend, apenas dados PD reais por fonte |
| Outros | qualquer outro (inside sales, prospecção em eventos, testeSource, etc.) | Fallback | Sem 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)
ID HTML: #screen-aqrc | Endpoint: /api/pipedrive/aqrc
Mapeamento AQRC
| Letra | Campo Pipedrive | Significado |
|---|---|---|
| A | [OBA] Data MQL | Leads que entraram (data de entrada na etapa MQL) |
| Q | [OBA] Data Reunião Agendada | Marcaram reunião |
| R | [OBA] Data Reunião Realizada | Realizaram reunião |
| C | [OBA] Data de Ganho | Compraram |
Lógica de % (implementada)
| % Q | Q ÷ A × 100 (conversão MQL → Reunião Agendada) |
| % R | R ÷ Q × 100 (conversão Agendada → Realizada) |
| % C | C ÷ 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
ID HTML: #screen-closer | Endpoint: /api/pipedrive/closers | Pipeline: 21
Etapas do funil (Pipeline 21)
| Stage ID | Etapa |
|---|---|
| 112 | Reunião Agendada |
| 113 | Reunião Realizada |
| 116 | Oportunidade |
| 114 | Negociação |
| 280 | Venda |
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.
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.
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.
ID HTML: #screen-meta
Endpoints
| Endpoint | O que alimenta |
|---|---|
| /api/meta/overview | KPIs — Investido, Leads, CPL, CPM, CPC, CTR |
| /api/meta/funnel-lp | Funil Landing Page (Impressões → Cliques → View Página → Leads) |
| /api/meta/funnel-form | Funil Form Nativo (Impressões → Cliques → Leads) |
| /api/meta/daily | Gráfico de investimento diário |
Pendência: Scroll da tela completa — parte inferior não visível.
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.
| Função | Linha | Entrada | Efeito |
|---|---|---|---|
| loadAll() | ~2295 | — | Dispara 16 fetches, chama todas as render* |
| getDateRange() | ~2272 | — | Retorna {from, to} baseado no filtro |
| navigate(screen) | ~2016 | string ID | Ativa a tela correspondente |
| renderKPIs(d) | ~2378 | /kpis | Popula os 5 KPI cards |
| renderMetaOverview(d) | ~2388 | /meta/overview | Popula métricas Meta |
| renderGerenciador(...) | ~2420 | metaCampaigns, funnelData, pdSources, googleCampsData | Tabela hierárquica com 6 grupos (Meta/Google/Orgânico/Indicação/Direto/Outros) por prioridade UTM |
| renderVisaoGeralFunnels(...) | ~2675 | funnel, meta | Popula funis SDR/Closer na Visão Geral |
| buildHfunnel(stages, color) | ~2794 | [{lbl,val}], cor | Retorna HTML do funil escalonado |
| agentHeader(...) | ~2824 | nome, fat, ns, avatar, cor | Retorna HTML do cabeçalho do agente |
| renderClosers(data) | ~2840 | /closers | Gera funis por closer em #closer-vert-chart |
| renderSDRs(data) | ~2893 | /sdrs | Gera funis por SDR em #sdr-vert-chart |
| renderSDRIA(data) | ~2929 | /sdr-ia | Gera funil único em #sdr-ia-vert-chart |
| renderAQRC(data) | ~2975 | /aqrc | Popula tabelas diário / semanal / mensal |
| renderMetaFunnels(...) | ~3054 | lpData, formData | Popula funis LP e Form Nativo |
| renderMetaDaily(data) | ~3086 | /meta/daily | Atualiza gráficos de linha |
| renderGoogleOverview(d) | ~3168 | /google/overview | Popula KPIs Google |
| renderGoogleDaily(data) | ~3200 | /google/daily | Atualiza gráfico Google |
| renderGoogleCampaigns(data) | ~3220 | /google/campaigns | Popula tabela de campanhas Google |
| fmtBRL(n, dec) | ~2362 | number | Formata como 1.234,56 |
| fmtInt(n) | ~2366 | number | Formata inteiro com . como separador |
| noshowColor(ns) | ~2785 | number % | Retorna cor CSS por % de no-show |
| exportGerenciadorCSV() | ~2640 | — | Download CSV da tabela Gerenciador |
Base: http://dash.omnihypnosis.com.br/api/ | Params: ?from=YYYY-MM-DD&to=YYYY-MM-DD
Limpar cache: POST /cache/clear
| Endpoint | Cache | O que retorna |
|---|---|---|
| /api/pipedrive/funnel | 10 min | Funis SDR e Closer agregados |
| /api/pipedrive/kpis | 5 min | Faturamento, vendas, ticket médio |
| /api/pipedrive/closers | 10 min | Array de closers com métricas |
| /api/pipedrive/sdrs | 10 min | Array de SDRs com métricas |
| /api/pipedrive/sdr-ia | 10 min | Objeto único SDR-IA agregado |
| /api/pipedrive/aqrc | 10 min | Arrays daily, weekly, monthly |
| /api/pipedrive/gerenciador | 10 min | Fontes Pipedrive por utm_source: mql, tentativas, conectados, avaliados, agendadas, realizadas, oportunidades, negociacoes, vendas, faturamento |
| /api/pipedrive/pages | 10 min | Array de páginas com métricas |
| /api/meta/overview | 10 min | KPIs Meta Ads agregados |
| /api/meta/campaigns | 10 min | Array de anúncios com métricas |
| /api/meta/funnel-lp | 10 min | Funil Landing Page |
| /api/meta/funnel-form | 10 min | Funil Form Nativo |
| /api/meta/daily | 10 min | Array diário {date, spend, leads} |
| /api/google/overview | 10 min | KPIs Google (campos como string — usar parseFloat) |
| /api/google/campaigns | 15 min | Array de campanhas Google |
| /api/google/daily | 10 min | Array diário 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).
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)
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
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.
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.
Resolvido em 2026-04-13. Removido onchange="applyCustomDate()" dos inputs de data. O dashboard agora só recalcula ao clicar em Aplicar.
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.
Como fazer uma mudança segura
scp para o Nginx.O que NUNCA fazer
.panel, .kpi-card, .hfunnel) sem verificar o impacto em todas as 8 telas.buildHfunnel, agentHeader, fmtBRL) sem rastrear todos os usos com Grep.apps/dashboard-omni/backups/.apps/dashboard-omni/dashboard.html.Quando adicionar campo novo da API
- Verificar o payload real:
curl -s 'http://76.13.169.211:3030/api/ROTA?from=...&to=...' - Adicionar o campo na função
render*correspondente com fallback:data.campo_novo || 0 - Não alterar o
loadAll()nem os endpoints — mudança só no render
- Google Ads restaurado (P7): Causa raiz identificada —
GOOGLE_ADS_MCC_IDno.envcausavapermission deniedcom 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).
- 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:
.mainmudou demin-height: 100vhparaheight: 100vh, ativandooverflow-y: autoem 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
onchangedos 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.mde backups de 10/04 que causavam confusão de versão.
- Layout hfunnel Closer/SDR/SDR-IA
- AQRC % e scrollbar diário
- Deploy infraestrutura inicial