Seu guia para uma publicidade de sucesso começa aqui.
Automação de Relatórios: Extraindo Métricas de Campanhas da BMS para o Google Sheets
Este guia prático ensina como configurar a integração entre a plataforma BMS e o Google Sheets utilizando o Google Apps Script. Aprenda a automatizar a coleta de dados e gere relatórios de métricas de forma eficiente e personalizada.

A análise de dados é a chave para o sucesso de qualquer estratégia digital. Para facilitar esse processo, criamos uma solução via Google Apps Script que conecta sua conta BMS ao Google Sheets. Nas próximas etapas, você aprenderá a configurar a ferramenta para importar, de forma exclusiva, as métricas das suas campanhas. Com isso, você ganha autonomia para criar dashboards e relatórios personalizados de maneira simples e rápida.

Passo 1: Preparando o Google Sheets

  1. Abra uma planilha em branco no Google Sheets (ou a planilha onde deseja gerar os dados).
  2. No menu superior, clique em Extensões > Apps Script.
  3. Uma nova aba do navegador será aberta. Este é o editor de código onde instalaremos o sistema.

Passo 2: Inserindo os Códigos

No editor de código, você precisará configurar exatamente dois arquivos na barra lateral esquerda. Siga as instruções e copie os códigos abaixo.

Arquivo 1: Code.gs (O Motor)

  1. Clique no arquivo Code.gs que já vem criado por padrão na barra lateral esquerda.
  2. Apague tudo o que estiver dentro dele.
  3. Copie o código abaixo e cole lá:


JavaScript

/**
 * INTEGRAÇÃO BMS METRICS - GOOGLE SHEETS
 * Versão Final (Layout em Colunas + Atualização Automática)
 */
const CONFIG = {
  apiMonUrl: "https://api.mon.bluems.com/v1",
  apiDspUrl: "https://api.dsp.bluems.com/v1",
  sheetData: "Dados",
  limitRows: 200000, 
  chunkDays: 31,
  idsPerRequest: 50,
  periodSeconds: 86400,
  pageSizeMetrics: 200,
  cacheTime: 1800
};
function onOpen() {
  SpreadsheetApp.getUi().createMenu('BMS Metrics 🚀')
    .addItem('📋 Abrir Painel', 'showSidebar')
    .addToUi();
}
function showSidebar() {
  const html = HtmlService.createHtmlOutputFromFile('Sidebar').setTitle('BMS Metrics').setWidth(350);
  SpreadsheetApp.getUi().showSidebar(html);
}
function checkConfig() {
  const p = PropertiesService.getScriptProperties();
  return (p.getProperty('BMS_API_KEY') && p.getProperty('BMS_ACCOUNT_ID')) ? true : false;
}
function saveSettings(key, id) {
  PropertiesService.getScriptProperties().setProperties({'BMS_API_KEY': key, 'BMS_ACCOUNT_ID': id});
  return true;
}
function getSidebarData() {
  const p = PropertiesService.getScriptProperties();
  const key = p.getProperty('BMS_API_KEY'), accId = p.getProperty('BMS_ACCOUNT_ID');
  if (!key || !accId) throw new Error("Credenciais ausentes.");
  const cache = CacheService.getUserCache().get("BMS_DATA_FINAL");
  if (cache) return JSON.parse(cache);
  const camps = [];
  try {
    const resp = UrlFetchApp.fetch(`${CONFIG.apiDspUrl}/accounts/${accId}/campaigns`, {headers:{"X-Api-Key":key}, muteHttpExceptions:true});
    if(resp.getResponseCode()===200) {
      JSON.parse(resp.getContentText()).values.forEach(c => camps.push({id: c.campaignId||c.id, name: c.name||c.id, tz: c.timezone||"UTC", created: c.createdAt}));
    }
  } catch(e) {}
  let metrics = [];
  try {
    const resp = UrlFetchApp.fetch(`${CONFIG.apiMonUrl}/metrics?pageSize=${CONFIG.pageSizeMetrics}`, {headers:{"X-Api-Key":key}, muteHttpExceptions:true});
    metrics = JSON.parse(resp.getContentText()).values || [];
  } catch(e) {}
  const uniqueMets = Array.from(new Map(metrics.map(m => [m.metricId, m])).values()).sort((a,b) => a.name.localeCompare(b.name));
  const data = { campaigns: camps.sort((a,b) => a.name.localeCompare(b.name)), metrics: uniqueMets };
  try { CacheService.getUserCache().put("BMS_DATA_FINAL", JSON.stringify(data), CONFIG.cacheTime); } catch(e){}
  
  return data;
}
function processMultiReport(selCamps, selMets, startDateStr) {
  // Salva a seleção para a execução automática de madrugada
  PropertiesService.getScriptProperties().setProperty('BMS_LAST_CAMPS', JSON.stringify(selCamps));
  PropertiesService.getScriptProperties().setProperty('BMS_LAST_METS', JSON.stringify(selMets));
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName(CONFIG.sheetData);
  
  if (!sheet) {
    sheet = ss.insertSheet(CONFIG.sheetData);
    sheet.appendRow(["Data", "Campanha"]);
    sheet.setFrozenRows(1);
    sheet.getRange("A:A").setNumberFormat("dd/MM/yyyy");
  }
  const lastCol = sheet.getLastColumn() || 2;
  let headers = sheet.getRange(1, 1, 1, lastCol).getValues()[0];
  
  selMets.forEach(m => {
    let colName = `${m.name} (${m.stat})`;
    if (!headers.includes(colName)) headers.push(colName);
  });
  let dataMap = new Map();
  const lastRow = sheet.getLastRow();
  
  if (lastRow > 1) {
    const existingData = sheet.getRange(2, 1, lastRow - 1, lastCol).getValues();
    existingData.forEach(row => {
      if (!row[0]) return; 
      let dStr = row[0] instanceof Date ? Utilities.formatDate(row[0], "UTC", "yyyy-MM-dd") : String(row[0]);
      let key = dStr + "||" + row[1];
      while (row.length < headers.length) row.push("");
      dataMap.set(key, row);
    });
  }
  const p = PropertiesService.getScriptProperties();
  const key = p.getProperty('BMS_API_KEY'), accId = p.getProperty('BMS_ACCOUNT_ID');
  let reqDate = new Date(startDateStr);
  reqDate.setHours(0,0,0,0);
  reqDate.setDate(reqDate.getDate() + 1); 
  const now = new Date(); now.setHours(0,0,0,0);
  const campsByTz = {};
  selCamps.forEach(c => { if(!campsByTz[c.tz]) campsByTz[c.tz] = []; campsByTz[c.tz].push(c); });
  for (const [tz, campaigns] of Object.entries(campsByTz)) {
    for (let i = 0; i < campaigns.length; i += CONFIG.idsPerRequest) {
      const chunk = campaigns.slice(i, i + CONFIG.idsPerRequest);
      const ids = chunk.map(c => c.id), map = new Map(chunk.map(c => [c.id, c.name]));
      let current = new Date(reqDate); current.setDate(current.getDate() - 1);
      
      while (current < now) {
        let end = new Date(current); end.setDate(end.getDate() + CONFIG.chunkDays);
        if (end > now) end = now; if (end <= current) break;
        
        const results = fetchInParallel(key, accId, selMets, ids, calculateIsoDates(current, end, tz));
        
        results.forEach((res, idx) => {
          if (!res || !res[0] || !res[0].results) return;
          const met = selMets[idx];
          let colName = `${met.name} (${met.stat})`;
          let colIdx = headers.indexOf(colName);
          
          res[0].results.forEach(r => {
            let id = r.group && r.group[0] ? (r.group[0].values ? r.group[0].values[0] : r.group[0].value) : null;
            let cName = map.get(id) || id || "-";
            
            (r.timestamps || []).forEach((t, k) => {
              let dVal, dStr; 
              try { 
                dVal = new Date(Utilities.formatDate(new Date(t), tz, "yyyy-MM-dd") + "T00:00:00"); 
                dStr = Utilities.formatDate(dVal, "UTC", "yyyy-MM-dd");
              } catch(e){ 
                dVal = new Date(t); dStr = String(t);
              }
              
              let rowKey = dStr + "||" + cName;
              let rowObj = dataMap.get(rowKey);
              
              if (!rowObj) {
                rowObj = new Array(headers.length).fill("");
                rowObj[0] = dVal; rowObj[1] = cName;
                dataMap.set(rowKey, rowObj);
              }
              
              while(rowObj.length < headers.length) rowObj.push("");
              rowObj[colIdx] = r.values[k];
            });
          });
        });
        current = new Date(end);
        Utilities.sleep(50);
      }
    }
  }
  let finalData = Array.from(dataMap.values());
  
  if (finalData.length > 0) {
    finalData.sort((a, b) => new Date(a[0]) - new Date(b[0]));
    finalData.forEach(r => { while(r.length < headers.length) r.push(""); });
    
    sheet.clearContents(); 
    sheet.getRange(1, 1, 1, headers.length).setValues([headers]); 
    sheet.getRange(2, 1, finalData.length, headers.length).setValues(finalData); 
    sheet.getRange(1, 1, 1, headers.length).setFontWeight("bold").setBackground("#f3f3f3");
  }
  if (sheet.getLastRow() > CONFIG.limitRows) sheet.deleteRows(2, (sheet.getLastRow() - CONFIG.limitRows) + 1000);
  return finalData.length;
}
function fetchInParallel(key, accId, metrics, ids, dates) {
  const reqs = metrics.map(m => ({
    url: `${CONFIG.apiMonUrl}/accounts/${accId}/metrics:query-statistics`,
    method: "POST", headers: { "X-Api-Key": key, "Content-Type": "application/json" },
    payload: JSON.stringify({ "start": dates.start, "end": dates.end, "limit": 10000, "queries": [{ "id": "q1", "metric": { "metricId": m.id, "statistic": m.stat, "period": CONFIG.periodSeconds, "groupBy": ["campaignId"], "where": [{ "name": "campaignId", "values": ids }] } }] }),
    muteHttpExceptions: true
  }));
  return UrlFetchApp.fetchAll(reqs).map(r => r.getResponseCode() === 200 ? JSON.parse(r.getContentText()) : null);
}
function calculateIsoDates(start, end, tz) {
  const f = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
  try { return { start: Utilities.formatDate(start, tz, f), end: Utilities.formatDate(end, tz, f) }; } 
  catch (e) { return { start: Utilities.formatDate(start, "UTC", f), end: Utilities.formatDate(end, "UTC", f) }; }
}
// --- FUNÇÃO DE ATUALIZAÇÃO AUTOMÁTICA (ACIONADOR DIÁRIO) ---
function autoUpdateDiario() {
  const p = PropertiesService.getScriptProperties();
  const campsStr = p.getProperty('BMS_LAST_CAMPS');
  const metsStr = p.getProperty('BMS_LAST_METS');
  
  if (!campsStr || !metsStr) {
    console.warn("Rode o relatório manualmente pelo menos uma vez para configurar a automação.");
    return;
  }
  const selCamps = JSON.parse(campsStr);
  const selMets = JSON.parse(metsStr);
  // Define a data inicial como 5 dias atrás para garantir que atualizações retroativas sejam capturadas
  let d = new Date();
  d.setDate(d.getDate() - 5);
  let startDateStr = Utilities.formatDate(d, Session.getScriptTimeZone(), "yyyy-MM-dd");
  processMultiReport(selCamps, selMets, startDateStr);
}

Arquivo 2: Sidebar.html (A Interface Visual)

  1. Na barra lateral esquerda do editor, clique no botão "+" (Adicionar um arquivo) e escolha HTML.
  2. Nomeie exatamente como Sidebar (o Google adiciona o .html sozinho).
  3. Apague o conteúdo que vier escrito e cole este código:

HTML

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
  <style>
    body { padding: 10px; padding-bottom: 70px; font-family: 'Segoe UI', sans-serif; background: #f8f9fa; }
    .hidden { display: none !important; }
    .top-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
    .app-title { font-weight: 700; color: #1a73e8; font-size: 15px; }
    
    .icon-btn { background: none; border: none; cursor: pointer; color: #9aa0a6; font-size: 16px; padding: 5px; }
    .icon-btn:hover { color: #202124; }
    
    .card { background: #fff; padding: 10px; border-radius: 6px; box-shadow: 0 1px 2px rgba(0,0,0,0.1); margin-bottom: 10px; }
    h3 { margin: 0 0 8px 0; color: #202124; font-size: 13px; font-weight: 600; }
    
    .list-box { height: 160px; overflow-y: auto; border: 1px solid #dadce0; border-radius: 4px; background: #fff; }
    .row { display: flex; align-items: center; padding: 5px; border-bottom: 1px solid #f1f3f4; font-size: 12px; }
    .row label { flex: 1; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2; }
    .row input { margin-right: 8px; }
    .met-id { font-size: 10px; color: #5f6368; font-family: monospace; display: block; }
    .stat-sel { font-size: 10px; width: 70px; margin-left: 5px; }
    
    .input-std { width: 94%; padding: 6px; border: 1px solid #dadce0; border-radius: 4px; font-size: 12px; margin-bottom: 8px; }
    .btn-main { background: #1a73e8; color: #fff; width: 100%; height: 40px; border: none; border-radius: 4px; font-weight: 600; font-size: 13px; cursor: pointer; margin-top: 10px; display: flex; justify-content: center; align-items: center; gap: 8px; }
    .btn-main:hover { background: #1557b0; }
    .btn-main:disabled { background: #dadce0; color: #888; cursor: default; }
    
    .tools { font-size: 10px; text-align: right; margin-top: 2px; }
    .tools a { color: #1a73e8; cursor: pointer; margin-left: 6px; text-decoration: none; }
    #status { margin-top: 10px; font-size: 12px; text-align: center; color: #5f6368; }
  </style>
</head>
<body>
  <div id="scr-conf" class="hidden">
    <div class="top-bar"><span class="app-title">⚙️ Configuração</span><button id="btnCloseConf" class="icon-btn" onclick="nav('rep')">✕</button></div>
    <div class="card">
      <input type="text" id="accId" class="input-std" placeholder="Account ID">
      <input type="text" id="apiKey" class="input-std" placeholder="API Key">
    </div>
    <button class="btn-main" style="background:#1e8e3e" onclick="save()">✅ Salvar</button>
  </div>
  <div id="scr-rep" class="hidden">
    <div class="top-bar"><span class="app-title">🚀 Relatório</span><button class="icon-btn" onclick="nav('conf')">⚙️</button></div>
    <div class="card"><h3>📅 Data Inicial</h3><input type="date" id="startDt" class="input-std"></div>
    <div class="card"><h3>📢 Campanhas</h3><input type="text" class="input-std" placeholder="🔍 Filtrar..." onkeyup="filter('lstCamp', this.value)"><div id="lstCamp" class="list-box"></div><div class="tools"><a onclick="viewSel('lstCamp',this)">🌪️ Filtrar Sel.</a><a onclick="tog('lstCamp',1)">Todos</a><a onclick="tog('lstCamp',0)">Nenhum</a></div></div>
    <div class="card"><h3>📊 Métricas</h3><input type="text" class="input-std" placeholder="🔍 Filtrar..." onkeyup="filter('lstMet', this.value)"><div id="lstMet" class="list-box"></div><div class="tools"><a onclick="viewSel('lstMet',this)">🌪️ Filtrar Sel.</a><a onclick="tog('lstMet',1)">Todos</a><a onclick="tog('lstMet',0)">Nenhum</a></div></div>
    <button id="btnRun" class="btn-main" onclick="run()">🚀 Gerar Relatório</button><div id="status"></div>
  </div>
<script>
  window.onload = () => { document.getElementById('startDt').valueAsDate = new Date(); google.script.run.withSuccessHandler(init).checkConfig(); };
  function init(h) { if(h) { nav('rep'); google.script.run.withSuccessHandler(render).getSidebarData(); } else { nav('conf'); } }
  function nav(s) { document.getElementById('scr-conf').classList.toggle('hidden', s!=='conf'); document.getElementById('scr-rep').classList.toggle('hidden', s!=='rep'); }
  function save() { const k=document.getElementById('apiKey').value, i=document.getElementById('accId').value; google.script.run.withSuccessHandler(()=>{nav('rep');google.script.run.withSuccessHandler(render).getSidebarData();}).saveSettings(k,i); }
  function render(d) {
    let hC="", hM="";
    d.campaigns.forEach(c => hC += `<div class="row"><input type="checkbox" value="${c.id}" n="c" tz="${c.tz}" cr="${c.created}"><label>${c.name}</label></div>`);
    d.metrics.forEach(m => {
      let def = m.statistics.includes('count') ? 'count' : (m.statistics.includes('sum') ? 'sum' : 'avg');
      let ops = m.statistics.map(s => `<option value="${s}" ${s==def?'selected':''}>${s}</option>`).join('');
      hM += `<div class="row"><input type="checkbox" value="${m.metricId}" n="m"><label><span style="font-weight:600">${m.name}</span><span class="met-id">${m.metricId}</span></label><select class="stat-sel">${ops}</select></div>`;
    });
    document.getElementById('lstCamp').innerHTML = hC; document.getElementById('lstMet').innerHTML = hM;
  }
  function run() {
    const dt=document.getElementById('startDt').value;
    const cs=Array.from(document.querySelectorAll('input[n="c"]:checked')).map(e=>({id:e.value, name:e.nextSibling.innerText, tz:e.getAttribute('tz'), created:e.getAttribute('cr')}));
    const ms=Array.from(document.querySelectorAll('input[n="m"]:checked')).map(e=>({id:e.value, name:e.nextSibling.querySelector('span').innerText, stat:e.parentNode.querySelector('select').value}));
    if(!cs.length || !ms.length) return alert("Selecione campanhas e métricas!");
    const btn=document.getElementById('btnRun'), st=document.getElementById('status');
    btn.disabled=true; btn.innerText="⏳ Processando...";
    st.innerHTML = `Organizando métricas em colunas...`;
    google.script.run
      .withSuccessHandler(n=>{
        btn.disabled=false; 
        btn.innerText="🚀 Gerar Relatório"; 
        st.innerHTML=`<span style="color:green; font-weight:bold;">✅ Planilha Atualizada!</span>`;
      })
      .withFailureHandler(e=>{
        btn.disabled=false; 
        btn.innerText="🚀 Gerar Relatório"; 
        st.innerHTML=`<span style='color:red'>Erro: ${e.message}</span>`;
      })
      .processMultiReport(cs,ms,dt);
  }
  function filter(id,v) { Array.from(document.getElementById(id).children).forEach(r=>r.style.display=r.innerText.toLowerCase().includes(v.toLowerCase())?'flex':'none'); }
  function tog(id,s) { Array.from(document.getElementById(id).children).forEach(r=>{if(r.style.display!=='none') r.querySelector('input').checked=!!s;}); }
  function viewSel(id,el) {
    const active=el.getAttribute('on')==='1';
    Array.from(document.getElementById(id).children).forEach(r => r.style.display=active?'flex':(r.querySelector('input').checked?'flex':'none'));
    el.innerText=active?"🌪️ Filtrar Sel.":"🔙 Ver Todos"; el.setAttribute('on',active?'0':'1');
  }
</script>
</body>
</html>

Atenção: Clique no ícone de Disquete no topo da tela para salvar o projeto após colar os dois códigos.

Passo 3: Permissões do Google

  1. Volte para a aba da sua Planilha do Google Sheets e atualize a página (pressione F5).
  2. Aguarde alguns segundos. Você verá um novo menu chamado "BMS Metrics 🚀" aparecer ao lado do menu "Ajuda".
  3. Clique em BMS Metrics 🚀 > 📋 Abrir Painel.
  4. Como é a primeira vez, o Google pedirá uma Autorização de segurança.
    • Clique em Continuar.
    • Escolha sua conta Google.
    • Clique em Avançado (se aparecer um aviso) e depois em Acessar projeto.
    • Clique em Permitir.

Passo 4: Configurando suas Credenciais

  1. Com as permissões concedidas, clique novamente em BMS Metrics 🚀 > 📋 Abrir Painel.
  2. Uma janela lateral vai se abrir na direita pedindo suas credenciais.
  3. Preencha o seu Account ID e a sua API Key da BMS.
  4. Clique em ✅ Salvar. O painel principal será carregado em seguida.

Passo 5: Gerando o seu Primeiro Relatório

  1. No painel, escolha a Data Inicial (ex: início do mês).
  2. Na seção de Campanhas, marque as caixinhas das campanhas que deseja analisar.
  3. Na seção de Métricas, marque as caixinhas do que deseja extrair (ex: Clicks, Impressions, CTR) e verifique se a estatística ao lado (sum, avg) está correta para o que você precisa.
  4. Clique em 🚀 Gerar Relatório.
  5. Aguarde o processamento. O script criará (ou atualizará) a aba chamada "Dados", organizando cada métrica em uma coluna de forma inteligente e à prova de duplicatas.