Tutorial – Crie um Robô de Alertas de Ações com Google Planilhas e Telegram

Você já teve a sensação de perder uma ótima oportunidade de compra ou venda só por não estar acompanhando o mercado naquele momento? Ficar “escravo” do Home Broker atualizando o preço dos ativos é um processo cansativo e que prejudica sua produtividade. Neste tutorial, vamos construir o Sniper de Ações: um sistema automático que monitora o mercado para você nos bastidores.

Você aprenderá como configurar um robô dentro da sua Planilha Google que, ao detectar que um ativo atingiu o seu preço alvo, envia uma notificação instantânea para o seu celular via Telegram

O que você vai aprender:

  • Integração com Telegram API: O passo a passo para criar o seu próprio bot e conectar a planilha diretamente ao seu celular de forma gratuita e estável.

  • Automação com Gatilhos (Triggers): Como configurar o Apps Script para “acordar” o robô em intervalos de tempo, permitindo que o monitoramento funcione mesmo com a planilha fechada.

  • Lógica de Filtro Inteligente: Como programar o sistema para comparar preços atuais (via Google Finance) com seus alvos, incluindo uma trava anti-spam para não sobrecarregar suas notificações.

  • Interface Web Responsiva: O segredo para criar um painel de controle moderno (Web App) que permite gerenciar seus alertas e ligar/desligar o robô diretamente pelo smartphone.

Código.gs

// ==========================================================================
// ARQUIVO: Code.gs (Versão TELEGRAM ✈️)
// ==========================================================================

const SHEET_MONITOR = "Monitoramento";
const SHEET_CONFIG = "Configurações";

// --- 1. FUNÇÕES DO SISTEMA (MENUS E GATILHOS) ---

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('🚀 Sniper de Ações')
    .addItem('🟢 Ligar Robô', 'iniciarMonitoramento')
    .addItem('🔴 Desligar Robô', 'pararMonitoramento')
    .addSeparator()
    .addItem('📲 Testar Telegram', 'testarEnvio')
    .addToUi();
}

function iniciarMonitoramento(minutos) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_CONFIG);
  
  // 1. Verifica se veio do Menu da Planilha (minutos é undefined/null)
  const isMenuCall = (typeof minutos !== 'number');

  // 2. Verifica se JÁ ESTÁ RODANDO
  const triggers = ScriptApp.getProjectTriggers();
  const isRunning = triggers.some(t => t.getHandlerFunction() === 'verificarAlertas');

  // CENÁRIO A: Chamada pelo Menu e Robô já ligado
  if (isMenuCall && isRunning) {
    const freqAtual = sheet.getRange("B3").getValue();
    SpreadsheetApp.getUi().alert(`⚠️ O Robô JÁ ESTÁ LIGADO!\n\nEle está rodando a cada ${freqAtual} minutos.\nSe quiser mudar o tempo, desligue primeiro.`);
    return; // Para tudo por aqui. Não recria o gatilho.
  }

  // CENÁRIO B: Vai ligar (ou reiniciar com novo tempo vindo do Web App)
  
  // Se veio do Menu, pega o tempo salvo. Se veio do WebApp, usa o tempo novo.
  if (isMenuCall) {
    minutos = sheet.getRange("B3").getValue();
  }

  // Validação de segurança para tempo
  const temposValidos = [1, 5, 10, 15, 30];
  if (!temposValidos.includes(minutos)) minutos = 10;

  // 3. Para o anterior em MODO SILENCIOSO (true) para não dar o pop-up chato
  pararMonitoramento(true);

  // 4. Salva preferência e Cria Gatilho
  sheet.getRange("B3").setValue(minutos);
  
  ScriptApp.newTrigger('verificarAlertas')
    .timeBased()
    .everyMinutes(minutos)
    .create();

  // 5. Feedback final (Só se for pelo Menu)
  if (isMenuCall) {
    SpreadsheetApp.getUi().alert(`✅ Robô Ligado!\nVerificação agendada a cada ${minutos} minutos.`);
  }
}

function pararMonitoramento(silencioso = false) {
  const triggers = ScriptApp.getProjectTriggers();
  let encontrou = false;
  
  triggers.forEach(t => {
    if (t.getHandlerFunction() === 'verificarAlertas') {
      ScriptApp.deleteTrigger(t);
      encontrou = true;
    }
  });

  // Só mostra o alerta SE não for modo silencioso
  if (!silencioso) {
    try { 
      if (encontrou) {
        SpreadsheetApp.getUi().alert('🛑 Robô Desligado com sucesso.'); 
      } else {
        SpreadsheetApp.getUi().alert('⚠️ O Robô já estava desligado.'); 
      }
    } catch(e){}
  }
}

// --- 2. LÓGICA PRINCIPAL (O MONITORAMENTO) ---

function verificarAlertas() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName(SHEET_MONITOR);
  const configSheet = ss.getSheetByName(SHEET_CONFIG);
  
  // --- BLOCO DE SEGURANÇA NOVO ---
  if (!configSheet) {
    console.error(`ERRO CRÍTICO: Não encontrei a aba com nome "${SHEET_CONFIG}". Verifique o nome na planilha.`);
    return; // Para a execução aqui para não dar erro de script
  }
  
  if (!sheet) {
    console.error(`ERRO CRÍTICO: Não encontrei a aba com nome "${SHEET_MONITOR}".`);
    return;
  }
  
  // Pega configurações (Agora adaptadas para Telegram)
  const chatId = configSheet.getRange("B1").getValue(); // Seu ID
  const token = configSheet.getRange("B2").getValue();  // Token do Bot
  
  if (!chatId || !token) return;

  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return;
  
  const range = sheet.getRange(2, 1, lastRow - 1, 6);
  const data = range.getValues();
  const hoje = new Date().toDateString();
  
  let mensageiroAtivo = false;

  data.forEach((row, index) => {
    const [ativo, precoAtual, alvo, condicao, ultimoAviso, status] = row;
    const rowIndex = index + 2;

    if (status !== true || typeof precoAtual !== 'number' || !ativo) return;

    let disparar = false;

    // Lógica da Condição
    if (condicao === 'Acima de' && precoAtual >= alvo) disparar = true;
    if (condicao === 'Abaixo de' && precoAtual <= alvo) disparar = true;

    // Lógica Anti-Spam
    if (ultimoAviso instanceof Date && ultimoAviso.toDateString() === hoje) {
      disparar = false;
    }

    if (disparar) {
      // Montando a mensagem (Telegram suporta HTML ou Markdown)
      const msg = `🚀 *SNIPER ALERT* 🚀\n\n📊 Ativo: *${ativo}*\n💰 Valor Atual: *R$ ${precoAtual.toFixed(2)}*\n🎯 Seu Alvo: ${condicao} R$ ${alvo.toFixed(2)}\n\n_Hora de operar!_`;
      
      const enviou = enviarTelegram(chatId, token, msg);
      
      if (enviou) {
        sheet.getRange(rowIndex, 5).setValue(new Date());
        mensageiroAtivo = true;
      }
    }
  });
  
  if (mensageiroAtivo) console.log(`Alertas enviados via Telegram.`);
}

// --- 3. INTEGRAÇÃO TELEGRAM (NOVA FUNÇÃO) ---

function enviarTelegram(chatId, token, text) {
  try {
    // API Oficial do Telegram (Blindada)
    const url = `https://api.telegram.org/bot${token}/sendMessage?chat_id=${chatId}&text=${encodeURIComponent(text)}&parse_mode=Markdown`;
    UrlFetchApp.fetch(url);
    return true;
  } catch (e) {
    console.error("Erro ao enviar Telegram: " + e.message);
    return false;
  }
}

function testarEnvio() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const config = ss.getSheetByName(SHEET_CONFIG);
  const chatId = config.getRange("B1").getValue();
  const token = config.getRange("B2").getValue();
  
  const resultado = enviarTelegram(chatId, token, "✅ *Teste de Conexão:*\nSeu Sniper de Ações está conectado ao Telegram!");
  
  if(resultado) {
    SpreadsheetApp.getUi().alert("Sucesso! Verifique seu Telegram.");
  } else {
    SpreadsheetApp.getUi().alert("Erro! Verifique o Token e o Chat ID.");
  }
}

// --- 4. FUNÇÕES PARA O WEB APP ---

function doGet(e) {
  return HtmlService.createTemplateFromFile('Index')
    .evaluate()
    .setTitle("Sniper de Ações 🎯")
    .addMetaTag('viewport', 'width=device-width, initial-scale=1.0')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

function getWebData() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_MONITOR);
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return [];
  const rawData = sheet.getRange(2, 1, lastRow - 1, 6).getDisplayValues(); 
  return rawData.map((r, i) => ({
    id: i + 2,
    ativo: r[0],
    preco: r[1],
    alvo: r[2],
    condicao: r[3],
    ultimoAviso: r[4],
    status: sheet.getRange(i + 2, 6).getValue()
  }));
}

function getBotStatus() {
  const triggers = ScriptApp.getProjectTriggers();
  const isRunning = triggers.some(t => t.getHandlerFunction() === 'verificarAlertas');
  
  // Lê a frequência salva na planilha (Padrão 10 se estiver vazio)
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_CONFIG);
  let freq = sheet.getRange("B3").getValue();
  if (!freq) freq = 10; 

  return { active: isRunning, frequency: freq };
}

function toggleBotMain(ligar, minutos) {
  if (ligar) {
    iniciarMonitoramento(minutos); // Passa o tempo escolhido
    return true;
  } else {
    pararMonitoramento();
    return false;
  }
}

function changeBotFrequency(minutos) {
  // Só reinicia se já estiver ligado. Se estiver desligado, apenas salva na planilha.
  const status = getBotStatus();
  if (status.active) {
    iniciarMonitoramento(minutos); // Reinicia com novo tempo
  } else {
    // Só salva na planilha
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_CONFIG);
    sheet.getRange("B3").setValue(minutos);
  }
}

function addWebItem(ativo, alvo, condicao) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_MONITOR);
  const alvoNumerico = parseFloat(alvo);
  const tickerOficial = ativo.trim().toUpperCase();
  // Garante prefixo BVMF para evitar erros de ativo
  const formula = `=GOOGLEFINANCE("BVMF:${tickerOficial}")`;
  
  sheet.appendRow([tickerOficial, "Carregando...", alvoNumerico, condicao, "", true]);
  const lastRow = sheet.getLastRow();
  sheet.getRange(lastRow, 2).setFormula(formula);
  sheet.getRange(lastRow, 2, 1, 2).setNumberFormat("R$ #,##0.00");
  sheet.getRange(lastRow, 6).insertCheckboxes().check();
  return true;
}

function toggleWebStatus(rowIndex, novoStatus) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_MONITOR);
  sheet.getRange(rowIndex, 6).setValue(novoStatus);
}

function deleteWebItem(rowIndex) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_MONITOR);
  sheet.deleteRow(rowIndex);
}

// --- 5. FUNÇÃO EXTRA: RESET AUTOMÁTICO ---
// Essa função roda sozinha sempre que você edita algo na planilha
function onEdit(e) {
  const sheet = e.source.getActiveSheet();
  const range = e.range;
  
  // Verifica se estamos na aba Monitoramento e não é o cabeçalho
  if (sheet.getName() !== SHEET_MONITOR || range.getRow() < 2) return;
  
  const col = range.getColumn();
  
  // Coluna 3 é "Preço Alvo" e Coluna 4 é "Condição"
  // Se você mudou a Meta ou a Condição...
  if (col === 3 || col === 4) {
    // ...o script limpa a Coluna 5 (Último Aviso) dessa linha
    sheet.getRange(range.getRow(), 5).clearContent();
    
    // (Opcional) Mostra um "toast" no canto avisando
    SpreadsheetApp.getActiveSpreadsheet().toast('Alvo alterado: Aviso resetado!', 'Sniper Bot');
  }
}

// --- NOVO: Função para Editar um item existente ---
function editWebItem(rowIndex, ativo, alvo, condicao) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_MONITOR);
  const alvoNumerico = parseFloat(alvo);
  const tickerOficial = ativo.trim().toUpperCase();
  
  // 1. Atualiza Ativo (Col 1)
  sheet.getRange(rowIndex, 1).setValue(tickerOficial);
  
  // 2. Atualiza a Fórmula (Col 2) - Caso tenha mudado o ativo
  sheet.getRange(rowIndex, 2).setFormula(`=GOOGLEFINANCE("BVMF:${tickerOficial}")`);
  
  // 3. Atualiza Alvo (Col 3) e Condição (Col 4)
  sheet.getRange(rowIndex, 3).setValue(alvoNumerico);
  sheet.getRange(rowIndex, 4).setValue(condicao);
  
  // 4. RESET AUTOMÁTICO (Apaga Col 5 - Último Aviso)
  // Isso garante que se o usuário mudou a meta, o robô vai avisar de novo.
  sheet.getRange(rowIndex, 5).clearContent();
  
  // 5. Garante a formatação R$
  sheet.getRange(rowIndex, 2, 1, 2).setNumberFormat("R$ #,##0.00");
  
  return true;
}

Index.html

<!DOCTYPE html>
<html>

<head>
  <base target="_top">
  <meta charset="UTF-8">
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <style>
    :root {
      --primary: #00e676;
      --danger: #ff5252;
      --info: #29b6f6;
      --dark: #121212;
      --card: #1e1e1e;
      --text: #ffffff;
    }

    body {
      background-color: var(--dark);
      color: var(--text);
      font-family: 'Inter', sans-serif;
      margin: 0;
      padding: 20px;
      padding-bottom: 80px;
    }

    .header {
      background-color: var(--card);
      padding: 20px;
      border-radius: 15px;
      margin-bottom: 20px;
      box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
    }

    .header-top {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 15px;
    }

    .header h1 {
      margin: 0;
      font-size: 1.3rem;
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .time-selector {
      background: #333;
      color: white;
      border: 1px solid #444;
      padding: 5px 10px;
      border-radius: 8px;
      margin-right: 10px;
      font-size: 0.9rem;
    }

    .bot-control {
      display: flex;
      align-items: center;
      justify-content: space-between;
      background: #333;
      padding: 10px 15px;
      border-radius: 10px;
    }

    .bot-status-text {
      font-weight: 600;
      font-size: 0.9rem;
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .status-dot {
      width: 10px;
      height: 10px;
      border-radius: 50%;
      background-color: #555;
    }

    .status-dot.on {
      background-color: var(--primary);
      box-shadow: 0 0 8px var(--primary);
    }

    .card-list {
      display: flex;
      flex-direction: column;
      gap: 15px;
    }

    .card {
      background-color: var(--card);
      padding: 20px;
      border-radius: 12px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
      border-left: 5px solid #444;
    }

    .card.active {
      border-left-color: var(--primary);
    }

    .card-info h3 {
      margin: 0 0 5px 0;
      font-size: 1.2rem;
    }

    .card-price {
      font-size: 1.4rem;
      font-weight: 600;
      color: var(--primary);
    }

    .card-meta {
      font-size: 0.85rem;
      color: #aaa;
      margin-top: 5px;
    }

    .card-actions {
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .switch {
      position: relative;
      display: inline-block;
      width: 44px;
      height: 24px;
    }

    .switch input {
      opacity: 0;
      width: 0;
      height: 0;
    }

    .slider {
      position: absolute;
      cursor: pointer;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: #555;
      transition: .4s;
      border-radius: 34px;
    }

    .slider:before {
      position: absolute;
      content: "";
      height: 18px;
      width: 18px;
      left: 3px;
      bottom: 3px;
      background-color: white;
      transition: .4s;
      border-radius: 50%;
    }

    input:checked+.slider {
      background-color: var(--primary);
    }

    input:checked+.slider:before {
      transform: translateX(20px);
    }

    .btn-icon {
      background: none;
      border: none;
      cursor: pointer;
      transition: transform 0.2s;
    }

    .btn-icon:hover {
      transform: scale(1.2);
    }

    .btn-del {
      color: var(--danger);
    }

    .btn-edit {
      color: var(--info);
    }

    /* Botão Azul */

    .fab {
      position: fixed;
      bottom: 30px;
      right: 30px;
      width: 60px;
      height: 60px;
      background-color: var(--primary);
      color: #000;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      box-shadow: 0 5px 15px rgba(0, 230, 118, 0.4);
      cursor: pointer;
      font-size: 30px;
      border: none;
      z-index: 100;
      transition: transform 0.2s;
    }

    .fab:hover {
      transform: scale(1.1);
    }

    .modal {
      display: none;
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.8);
      align-items: center;
      justify-content: center;
      z-index: 200;
    }

    .modal-content {
      background: var(--card);
      padding: 25px;
      border-radius: 15px;
      width: 90%;
      max-width: 400px;
    }

    .form-group {
      margin-bottom: 15px;
    }

    .form-group label {
      display: block;
      margin-bottom: 5px;
      color: #aaa;
    }

    .form-group input,
    .form-group select {
      width: 100%;
      padding: 12px;
      background: #333;
      border: 1px solid #444;
      color: white;
      border-radius: 8px;
      box-sizing: border-box;
      font-size: 1rem;
    }

    .btn-save {
      width: 100%;
      padding: 15px;
      background: var(--primary);
      border: none;
      border-radius: 8px;
      color: #000;
      font-weight: bold;
      cursor: pointer;
      font-size: 1rem;
    }

    .close-modal {
      float: right;
      cursor: pointer;
      color: #aaa;
    }

    #loader {
      text-align: center;
      margin-top: 50px;
    }
  </style>
</head>

<body>

  <div class="header">
    <div class="header-top">
      <h1><span class="material-icons">radar</span> Sniper de Ações</h1>
      <button onclick="refreshAll()" style="background:none; border:none; color:var(--primary); cursor:pointer;"><span class="material-icons">refresh</span></button>
    </div>

    <div class="bot-control">
      <div class="bot-status-text">
        <div id="statusDot" class="status-dot"></div>
        <span id="statusText">...</span>
      </div>

      <div style="display:flex; align-items:center;">
        <!-- NOVO SELETOR DE TEMPO -->
        <select id="botFrequency" class="time-selector" onchange="changeFrequency(this.value)">
          <option value="1">1 min (Turbo)</option>
          <option value="5">5 min</option>
          <option value="10">10 min</option>
          <option value="15">15 min</option>
          <option value="30">30 min</option>
        </select>

        <label class="switch">
          <input type="checkbox" id="masterSwitch" onchange="toggleMasterBot(this.checked)">
          <span class="slider"></span>
        </label>
      </div>
    </div>
  </div>

  <div id="loader">Carregando carteira...</div>
  <div id="container" class="card-list"></div>

  <!-- Botão flutuante para CRIAR NOVO -->
  <button class="fab" onclick="openNewModal()">+</button>

  <div id="modal" class="modal">
    <div class="modal-content">
      <span class="close-modal" onclick="closeModal()">&times;</span>
      <h2 id="modalTitle">Novo Alerta</h2>
      <div class="form-group">
        <label>Ativo (Ticker)</label>
        <input type="text" id="novoAtivo" placeholder="Ex: VALE3" style="text-transform: uppercase;">
      </div>
      <div class="form-group">
        <label>Preço Alvo</label>
        <input type="text" id="novoAlvo" placeholder="Ex: 65,50" inputmode="decimal">
      </div>
      <div class="form-group">
        <label>Condição</label>
        <select id="novaCondicao">
          <option value="Acima de">Avisar se SUBIR acima de</option>
          <option value="Abaixo de">Avisar se CAIR abaixo de</option>
        </select>
      </div>
      <button class="btn-save" id="btnSave" onclick="saveItem()">Salvar Alerta</button>
    </div>
  </div>

  <script>
    // Variável Global para saber se estamos editando
    let editingId = null;

    function refreshAll() {
      loadBotStatus();
      loadData();
    }

    // --- Status do Robô ---
    function loadBotStatus() {
      google.script.run.withSuccessHandler(status => {
        // status agora é um objeto { active: true, frequency: 10 }
        
        const check = document.getElementById('masterSwitch');
        const text = document.getElementById('statusText');
        const dot = document.getElementById('statusDot');
        const select = document.getElementById('botFrequency');
        
        check.checked = status.active;
        select.value = status.frequency; // Atualiza o select com o valor real
        
        if(status.active) {
          text.innerText = "Ligado"; text.style.color = "#00e676"; dot.classList.add('on');
        } else {
          text.innerText = "Parado"; text.style.color = "#aaa"; dot.classList.remove('on');
        }
      }).getBotStatus();
    }

    function toggleMasterBot(isChecked) {
      const minutos = document.getElementById('botFrequency').value;
      const text = document.getElementById('statusText');
      
      text.innerText = isChecked ? "Ligando..." : "Desligando...";
      
      // Envia também os minutos escolhidos
      google.script.run.withSuccessHandler(loadBotStatus).toggleBotMain(isChecked, parseInt(minutos));
    }

    function changeFrequency(val) {
      // Feedback visual rápido
      const text = document.getElementById('statusText');
      if (document.getElementById('masterSwitch').checked) {
         text.innerText = "Atualizando...";
      }
      
      google.script.run.withSuccessHandler(() => {
        loadBotStatus(); // Recarrega para confirmar
      }).changeBotFrequency(parseInt(val));
    }

    // --- Lista de Cards ---
    function loadData(isBackground = false) {
      // Só mostra o texto "Carregando..." se NÃO for atualização automática
      if (!isBackground) {
        document.getElementById('loader').style.display = 'block';
        document.getElementById('container').style.opacity = '0.5'; // Dá um efeito visual leve
      }
      
      google.script.run.withSuccessHandler((data) => {
        renderCards(data);
        // Restaura visual
        document.getElementById('loader').style.display = 'none';
        document.getElementById('container').style.opacity = '1';
      }).getWebData();
    }

    function renderCards(data) {
      const container = document.getElementById('container');
      
      // Limpa tudo e reconstrói (Isso atualiza os preços!)
      container.innerHTML = '';
      
      if (data.length === 0) {
        container.innerHTML = '<p style="text-align:center; color:#555;">Nenhum ativo monitorado.</p>';
        return;
      }

      data.forEach(item => {
        const div = document.createElement('div');
        div.className = `card ${item.status ? 'active' : ''}`;
        
        let alvoFormatado = item.alvo.toString().replace('R$', '').trim().replace('.', ',');
        
        div.innerHTML = `
          <div class="card-info">
            <h3>${item.ativo}</h3>
            <!-- AQUI O PREÇO NOVO ENTRA AUTOMATICAMENTE -->
            <div class="card-price">${item.preco}</div>
            <div class="card-meta">${item.condicao} R$ ${alvoFormatado}</div>
          </div>
          <div class="card-actions">
            <button class="btn-icon btn-edit" onclick="openEditModal(${item.id}, '${item.ativo}', '${alvoFormatado}', '${item.condicao}')">
              <span class="material-icons">edit</span>
            </button>
            <label class="switch">
              <input type="checkbox" ${item.status ? 'checked' : ''} onchange="toggleItemStatus(${item.id}, this.checked)">
              <span class="slider"></span>
            </label>
            <button class="btn-icon btn-del" onclick="deleteItem(${item.id})">
              <span class="material-icons">delete</span>
            </button>
          </div>
        `;
        container.appendChild(div);
      });
    }

    function startAutoRefresh() {
      // A cada 30 segundos (30000 ms), ele chama o loadData em modo silencioso
      setInterval(() => {
        // Só atualiza se o usuário NÃO estiver com o modal aberto (para não atrapalhar a edição)
        if(document.getElementById('modal').style.display !== 'flex') {
           loadData(true); 
           // Opcional: Atualiza o status do robô também
           loadBotStatus();
        }
      }, 30000); 
    }

    refreshAll();
    startAutoRefresh();

    function toggleItemStatus(id, newStatus) {
      google.script.run.toggleWebStatus(id, newStatus);
      const card = event.target.closest('.card');
      if(newStatus) card.classList.add('active'); else card.classList.remove('active');
    }

    function deleteItem(id) {
      if(confirm('Remover este alerta?')) {
        google.script.run.withSuccessHandler(loadData).deleteWebItem(id);
      }
    }

    // --- Lógica do Modal (Criar vs Editar) ---

    // Modo 1: Criar Novo
    function openNewModal() {
      editingId = null; // Limpa o ID
      document.getElementById('modalTitle').innerText = "Novo Alerta";
      document.getElementById('btnSave').innerText = "Adicionar";
      document.getElementById('novoAtivo').value = '';
      document.getElementById('novoAlvo').value = '';
      document.getElementById('modal').style.display = 'flex';
    }

    // Modo 2: Editar Existente
    function openEditModal(id, ativo, alvo, condicao) {
      editingId = id; // Define o ID que está sendo editado
      document.getElementById('modalTitle').innerText = "Editar Alerta";
      document.getElementById('btnSave').innerText = "Salvar Alterações";
      
      document.getElementById('novoAtivo').value = ativo;
      document.getElementById('novoAlvo').value = alvo;
      document.getElementById('novaCondicao').value = condicao;
      
      document.getElementById('modal').style.display = 'flex';
    }

    function saveItem() {
      const ativo = document.getElementById('novoAtivo').value.toUpperCase();
      let alvoRaw = document.getElementById('novoAlvo').value;
      const condicao = document.getElementById('novaCondicao').value;

      if(!ativo || !alvoRaw) return alert('Preencha tudo!');

      // Converte vírgula para ponto
      alvoRaw = alvoRaw.replace(',', '.');
      if (isNaN(alvoRaw)) return alert('Valor inválido');

      const btn = document.getElementById('btnSave');
      const originalText = btn.innerText;
      btn.innerText = 'Salvando...';
      btn.disabled = true;

      // O Callback de sucesso é o mesmo para ambos
      const onSuccess = () => {
        closeModal();
        loadData();
        btn.innerText = originalText;
        btn.disabled = false;
      };

      if (editingId) {
        // Se tem ID, é EDIÇÃO
        google.script.run.withSuccessHandler(onSuccess)
          .editWebItem(editingId, ativo, alvoRaw, condicao);
      } else {
        // Se não tem ID, é CRIAÇÃO
        google.script.run.withSuccessHandler(onSuccess)
          .addWebItem(ativo, alvoRaw, condicao);
      }
    }

    function closeModal() { document.getElementById('modal').style.display = 'none'; }
    
    refreshAll();
  </script>
</body>

</html>

Acompanhe nas redes sociais

Projetos Personalizados

Precisa de uma solução sob medida?

© 2025 Transformando Planilhas. Todos os direitos reservados.