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.
// ==========================================================================
// 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;
} <!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()">×</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>
Precisa de uma solução sob medida?
Nós usamos cookies para analisar o tráfego do site e melhorar sua experiência. Ao clicar em 'Aceitar', você concorda com o uso de ferramentas de estatística, conforme detalhado em nossa Política de Privacidade.