Skip to content

Instantly share code, notes, and snippets.

@HenriqueRL26
Last active February 26, 2026 01:09
Show Gist options
  • Select an option

  • Save HenriqueRL26/ec192f32d35157117c8bd9331b548fc1 to your computer and use it in GitHub Desktop.

Select an option

Save HenriqueRL26/ec192f32d35157117c8bd9331b548fc1 to your computer and use it in GitHub Desktop.
Automação - manutenção , contabil e resumos - Moto
Automação - manutenção , contabil e resumos - Moto
@flaviohloureiro
Copy link

flaviohloureiro commented Feb 24, 2026

Código de implantação
AKfycbx5s8JJpN4LBLQ10g_qEesjBotLyU5WB0--Kse43U8oyzWAGcxkvsiTMxSBwGyJq3L2

api da web
https://script.google.com/macros/s/AKfycbx5s8JJpN4LBLQ10g_qEesjBotLyU5WB0--Kse43U8oyzWAGcxkvsiTMxSBwGyJq3L2/exec

@HenriqueRL26
Copy link
Author

8177006223:AAGo0LB18SJsSdcowoExzNXrqCFrObdI8So

@flaviohloureiro
Copy link

flaviohloureiro commented Feb 25, 2026

Mapa desenhado

MENU PRINCIPAL

├─ 1️⃣ Registrar KM do dia

├─ 2️⃣ Resumos
│ ├─ 2.1 Hoje
│ ├─ 2.2 Semana
│ ├─ 2.3 Mês
│ ├─ 2.4 Ano
│ └─ 2.5 Geral

├─ 3️⃣ Manutenção
│ ├─ 3.1 Próximas manutenções
│ ├─ 3.2 Registrar manutenção
│ │ ├─ Escolher item
│ │ ├─ Informar KM
│ │ ├─ Informar valor
│ │ └─ Confirmar
│ └─ 3.3 Histórico de manutenções

├─ 4️⃣ Contábil
│ ├─ 4.1 Registrar entrada
│ │ ├─ Categoria
│ │ ├─ Valor
│ │ └─ Confirmar
│ │
│ ├─ 4.2 Registrar saída
│ │ ├─ Categoria
│ │ ├─ Valor
│ │ └─ Confirmar
│ │
│ ├─ 4.3 Resumo financeiro
│ │ ├─ Hoje
│ │ ├─ Semana
│ │ ├─ Mês
│ │ └─ Ano
│ │
│ ├─ 4.4 Resumo por categoria (mês)
│ │
│ └─ 4.5 Acumulado geral

└─ 5️⃣ Configurações
├─ 5.1 Alertas
│ ├─ 5.1.1 Listar alertas
│ ├─ 5.1.2 Incluir novo alerta
│ └─ 5.1.3 Gerenciar alerta existente
│ ├─ Ativar / Desativar
│ ├─ Alterar data
│ └─ Alterar prazo

└─ 5.2 Resumo avançado

@HenriqueRL26
Copy link
Author

EXPLICAÇÃO RÁPIDA DE CADA PARTE

🔹 COMANDOS GLOBAIS (FUNCIONAM EM QUALQUER LUGAR)

00️⃣ Menu principal
• Limpa qualquer fluxo
• Volta direto para o menu principal

0️⃣ Voltar
• Volta um nível
• Cancela fluxo em andamento
• No menu principal → fecha o bot

1️⃣ Registrar KM do dia
• Registra KM total atual
• Só aceita número maior que o último
• Grava na aba HISTORICO_KM
• Usa data sem hora (00:00)
• Alimenta automaticamente:
• manutenção
• resumos
• alertas de manutenção

2️⃣ Resumos

Lê a aba RESUMOS (já calculada pelo Excel).
• Hoje
• Semana
• Mês
• Ano
• Geral (todos juntos)

📌 O bot não calcula, ele apenas lê.

3️⃣ Manutenção

3.1 Próximas manutenções
• Lê a aba CONTROLE_MANUTENCAO
• Mostra:
• Próxima troca
• KM atual
• KM restantes
• STATUS (OK / ATENÇÃO / ATRASADO)

📌 O STATUS vem do Excel, não do código.

3.2 Registrar manutenção

Fluxo guiado:
1. Escolhe o item
2. Informa KM
3. Informa valor
4. Confirma

Registra em:
• HISTORICO_MANUTENCAO
• CONTABIL_DIARIO (como SAÍDA / MANUTENÇÃO)

📌 Isso automaticamente faz o Excel:
• Atualizar controle
• Atualizar STATUS
• Ativar ou desativar alertas

3.3 Histórico de manutenções
• Mostra as últimas 5
• Ordenadas por data
• Formatação amigável

4️⃣ Contábil

4.1 Registrar entrada
• Categorias fixas + OUTROS
• Registra em CONTABIL_DIARIO
• DATA sem hora

4.2 Registrar saída
• Mesmo fluxo da entrada
• Registra como SAÍDA

4.3 Resumo financeiro

Calculado no código, lendo CONTABIL_DIARIO:
• Hoje
• Semana
• Mês
• Ano

Mostra:
• Total entradas
• Total saídas
• Saldo

4.4 Resumo por categoria (mês)
• Agrupa por categoria
• Separado em:
• Entradas
• Saídas
• Mostra totais
• Mostra saldo do mês

4.5 Acumulado geral
• Soma tudo desde o início
• Resultado final da operação

5️⃣ Configurações

5.1 Alertas

5.1.1 Listar alertas
Mostra apenas:
• ATIVO = TRUE
• STATUS = ⚠️ PENDENTE ou 🛑 VENCIDO

5.1.2 Incluir novo alerta
Fluxo:
1. Nome
2. Data
3. Dias antes

Insere na aba ALERTAS_DATA:
• Primeira linha vazia
• Mantém fórmulas
• Já nasce ativo

5.1.3 Gerenciar alerta existente
• Lista todos os alertas
• Seleciona por número
• Ações:
• Ativar / Desativar
• Alterar data
• Alterar prazo

📌 Ideal para:
• Reutilizar alerta
• Pausar cobrança
• Ajustar vencimento

5.2 Resumo avançado
• Total entradas
• Total saídas
• Resultado geral
• Visão macro rápida

3️⃣ ALERTAS AUTOMÁTICOS (RODAM SOZINHOS)

Todo dia, via gatilho:

🔧 Manutenção
• STATUS ⚠️ ATENÇÃO ou 🛑 ATRASADO
• Vem direto do Excel

📅 Datas
• STATUS ⚠️ PENDENTE ou 🛑 VENCIDO
• Apenas se ATIVO = TRUE

📌 Se não tiver nada → nenhuma mensagem

@HenriqueRL26
Copy link
Author

codex V 1.0

/******************************

  • CONFIGURAÇÃO TELEGRAM
    /
    const TELEGRAM_TOKEN = "8177006223:AAGo0LB18SJsSdcowoExzNXrqCFrObdI8So";
    const TELEGRAM_API = "https://api.telegram.org/bot" + TELEGRAM_TOKEN;
    /
  • SPREADSHEET (ID FIXO)
    ******************************/
    function setSpreadsheetId() {
    const ID = "1xxEjAWEtH6UgCovGbslAJtqmdqShWJZK4KoSx_p5XNU";
    PropertiesService.getScriptProperties().setProperty("SPREADSHEET_ID", ID);
    }

function getSS_() {
const id = PropertiesService.getScriptProperties().getProperty("SPREADSHEET_ID");
if (!id) throw new Error("SPREADSHEET_ID não configurado. Rode setSpreadsheetId() 1x.");
return SpreadsheetApp.openById(id);
}

/******************************

  • ESTADOS DO BOT
    ******************************/
    const STATE = {
    MENU: "MENU",
    REG_KM: "REG_KM",
    RESUMOS: "RESUMOS",

MANUT_MENU: "MANUT_MENU",
MANUT_ITEM: "MANUT_ITEM",
MANUT_OUTROS_NOME: "MANUT_OUTROS_NOME",
MANUT_KM: "MANUT_KM",
MANUT_VALOR: "MANUT_VALOR",
MANUT_CONFIRMA: "MANUT_CONFIRMA",
// CONTÁBIL
CONTABIL_MENU: "CONTABIL_MENU",
CONT_ENT_CATEG: "CONT_ENT_CATEG",
CONT_ENT_OUTROS: "CONT_ENT_OUTROS",
CONT_ENT_VALOR: "CONT_ENT_VALOR",
CONT_ENT_CONFIRMA: "CONT_ENT_CONFIRMA",
// SAÍDA
CONT_SAI_CATEG: "CONT_SAI_CATEG",
CONT_SAI_OUTROS: "CONT_SAI_OUTROS",
CONT_SAI_VALOR: "CONT_SAI_VALOR",
CONT_SAI_CONFIRMA: "CONT_SAI_CONFIRMA",
CONT_RES_MENU: "CONT_RES_MENU",
// CONFIGURAÇÕES
CONFIG_MENU: "CONFIG_MENU",
ALERTA_MENU: "ALERTA_MENU",
ALERTA_NOVO_ITEM: "ALERTA_NOVO_ITEM",
ALERTA_NOVO_DATA: "ALERTA_NOVO_DATA",
ALERTA_NOVO_DIAS: "ALERTA_NOVO_DIAS",
// ===== GERENCIAR ALERTA EXISTENTE (5.1.3) =====
ALERTA_GER_MENU: "ALERTA_GER_MENU",
ALERTA_GER_SELECIONA: "ALERTA_GER_SELECIONA",
ALERTA_GER_ACAO: "ALERTA_GER_ACAO",
ALERTA_GER_NOVA_DATA: "ALERTA_GER_NOVA_DATA",
ALERTA_GER_NOVOS_DIAS: "ALERTA_GER_NOVOS_DIAS",
ALERTA_GER_CONFIRMA: "ALERTA_GER_CONFIRMA"
};

/******************************

  • DATA SEM HORA (00:00)
    ******************************/
    function hojeSemHora_() {
    const d = new Date();
    return new Date(d.getFullYear(), d.getMonth(), d.getDate());
    }

/******************************

  • WEBHOOK TELEGRAM
    ******************************/
    function doPost(e) {
    try {
    const data = JSON.parse(e.postData.contents);
    const msg = data.message || data.channel_post;
    if (!msg || !msg.text) return respostaOK();

    const texto = msg.text.trim().toLowerCase();
    const chatId = msg.chat.id;
    // Salva o chatId dono (1x) para enviar alertas diários
    const props = PropertiesService.getScriptProperties();
    if (!props.getProperty("CHAT_ID_DONO")) {
    props.setProperty("CHAT_ID_DONO", String(chatId));
    }

    const estado = getUserState(chatId);

    // ===== COMANDOS GLOBAIS =====
    if (texto === "menu" || texto === "00") {
    clearFluxos(chatId);
    enviarMenuPrincipal(chatId);
    return respostaOK();
    }

    if (texto === "0") {
    // 0 = voltar (submenus) / fechar (somente menu principal)
    tratarVoltarOuFechar(chatId, estado);
    return respostaOK();
    }

    // ===== DISPATCH POR ESTADO =====
    if (estado === STATE.MENU) {
    tratarMenuPrincipal(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.REG_KM) {
    tratarRegistroKM(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.RESUMOS) {
    tratarMenuResumos(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.MANUT_MENU) {
    tratarMenuManutencao(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.MANUT_ITEM) {
    tratarItemManutencao(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.MANUT_OUTROS_NOME) {
    tratarOutrosNomeManutencao(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.MANUT_KM) {
    tratarKmManutencao(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.MANUT_VALOR) {
    tratarValorManutencao(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.MANUT_CONFIRMA) {
    confirmarManutencao(texto, chatId);
    return respostaOK();
    }

     // ===== CONTÁBIL (4.1) =====
    

    if (estado === STATE.CONTABIL_MENU) {
    tratarMenuContabil(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.CONT_ENT_CATEG) {
    tratarEntradaCategoria(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.CONT_ENT_OUTROS) {
    tratarEntradaOutros(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.CONT_ENT_VALOR) {
    tratarEntradaValor(texto, chatId);
    return respostaOK();
    }

    if (estado === STATE.CONT_ENT_CONFIRMA) {
    confirmarEntrada(texto, chatId);
    return respostaOK();
    }
    if (estado === STATE.CONT_SAI_CATEG) {
    tratarSaidaCategoria(texto, chatId);
    return respostaOK();
    }
    if (estado === STATE.CONT_SAI_OUTROS) {
    tratarSaidaOutros(texto, chatId);
    return respostaOK();
    }
    if (estado === STATE.CONT_SAI_VALOR) {
    tratarSaidaValor(texto, chatId);
    return respostaOK();
    }
    if (estado === STATE.CONT_SAI_CONFIRMA) {
    confirmarSaida(texto, chatId);
    return respostaOK();
    }
    if (estado === STATE.CONT_RES_MENU) {
    tratarMenuResumoFinanceiro(texto, chatId);
    return respostaOK();
    }
    if (estado === STATE.CONFIG_MENU) {
    tratarMenuConfiguracoes(texto, chatId);
    return respostaOK();
    }

if (estado === STATE.ALERTA_MENU) {
tratarMenuAlertas(texto, chatId);
return respostaOK();
}

if (estado === STATE.ALERTA_NOVO_ITEM) {
tratarAlertaNovoItem(texto, chatId);
return respostaOK();
}

if (estado === STATE.ALERTA_NOVO_DATA) {
tratarAlertaNovoData(texto, chatId);
return respostaOK();
}

if (estado === STATE.ALERTA_NOVO_DIAS) {
tratarAlertaNovoDias(texto, chatId);
return respostaOK();
}
// ===== GERENCIAR ALERTA EXISTENTE (5.1.3) =====
if (estado === STATE.ALERTA_GER_SELECIONA) {
tratarSelecionaAlertaGerenciar_(texto, chatId);
return respostaOK();
}

if (estado === STATE.ALERTA_GER_MENU) {
tratarMenuAcaoGerenciar_(texto, chatId);
return respostaOK();
}

if (estado === STATE.ALERTA_GER_NOVA_DATA) {
tratarGerenciarNovaData_(texto, chatId);
return respostaOK();
}

if (estado === STATE.ALERTA_GER_NOVOS_DIAS) {
tratarGerenciarNovosDias_(texto, chatId);
return respostaOK();
}
// fallback
enviarMenuPrincipal(chatId);
return respostaOK();

} catch (err) {
return respostaOK();
}
}

/******************************

  • VOLTAR / FECHAR (0)
    ******************************/
    function tratarVoltarOuFechar(chatId, estado) {
    // Menu principal: 0 = fechar
    if (estado === STATE.MENU) {
    clearFluxos(chatId);
    enviarMensagem(chatId, "👋 Menu encerrado.");
    return;
    }

// Submenus: 0 = voltar conforme contexto
if (estado === STATE.RESUMOS) {
enviarMenuPrincipal(chatId);
return;
}

if (estado === STATE.MANUT_MENU) {
enviarMenuPrincipal(chatId);
return;
}

// Fluxo de manutenção em andamento: cancelar e voltar ao menu manutenção
if (
estado === STATE.MANUT_ITEM ||
estado === STATE.MANUT_OUTROS_NOME ||
estado === STATE.MANUT_KM ||
estado === STATE.MANUT_VALOR ||
estado === STATE.MANUT_CONFIRMA
) {
clearDadosManutencao(chatId);
enviarMenuManutencao(chatId);
return;
}

// Registro de KM: voltar menu principal
if (estado === STATE.REG_KM) {
enviarMenuPrincipal(chatId);
return;
}
// ===== GERENCIAR ALERTA EXISTENTE (5.1.3) =====
if (
estado === STATE.ALERTA_GER_MENU ||
estado === STATE.ALERTA_GER_SELECIONA ||
estado === STATE.ALERTA_GER_NOVA_DATA ||
estado === STATE.ALERTA_GER_NOVOS_DIAS
) {
enviarMenuAlertas(chatId);
return;
}
if (estado === STATE.CONTABIL_MENU) {
enviarMenuPrincipal(chatId);
return;
}

if (
estado === STATE.CONT_ENT_CATEG ||
estado === STATE.CONT_ENT_OUTROS ||
estado === STATE.CONT_ENT_VALOR ||
estado === STATE.CONT_ENT_CONFIRMA
) {
clearDadosEntrada_(chatId);
enviarMenuContabil(chatId);
return;
}
// Submenu de Resumo Financeiro (4.3)
if (estado === STATE.CONT_RES_MENU) {
enviarMenuContabil(chatId);
return;
}
// ===== CONFIGURAÇÕES (MENU 5) =====
if (estado === STATE.CONFIG_MENU) {
enviarMenuPrincipal(chatId);
return;
}

// ===== ALERTAS (submenu do 5) =====
if (
estado === STATE.ALERTA_MENU ||
estado === STATE.ALERTA_NOVO_ITEM ||
estado === STATE.ALERTA_NOVO_DATA ||
estado === STATE.ALERTA_NOVO_DIAS
) {
enviarMenuConfiguracoes(chatId);
return;
}

enviarMenuPrincipal(chatId);
}

/******************************

  • MENU PRINCIPAL
    ******************************/
    function enviarMenuPrincipal(chatId) {
    setUserState(chatId, STATE.MENU);

const msg =
`🏍️ CONTROLE DA MOTO
Escolha uma opção:

1️⃣ Registrar KM do dia
2️⃣ Resumos
3️⃣ Manutenção
4️⃣ Contábil
5️⃣ Configurações

0️⃣ Fechar`;

enviarMensagem(chatId, msg);
}

/******************************

  • TRATAR MENU PRINCIPAL
    ******************************/
    function tratarMenuPrincipal(texto, chatId) {
    switch (texto) {
    case "1":
    setUserState(chatId, STATE.REG_KM);
    enviarMensagem(chatId, "Digite a KM total atual:");
    break;

    case "2":
    setUserState(chatId, STATE.RESUMOS);
    enviarMenuResumos(chatId);
    break;

    case "3":
    enviarMenuManutencao(chatId);
    break;

    case "4":
    enviarMenuContabil(chatId);
    break;

    case "5":
    enviarMenuConfiguracoes(chatId);
    break;

    default:
    enviarMensagem(chatId, "❌ Opção inválida. Digite 1 a 5 ou 0.");
    }
    }

/******************************

  • REGISTRO DE KM (SOMENTE VIA MENU)
    ******************************/
    function tratarRegistroKM(texto, chatId) {
    if (!/^\d+$/.test(texto)) {
    enviarMensagem(chatId, "❌ Digite apenas números para a KM.");
    return;
    }

const kmNova = Number(texto);
const resultado = registrarKM(kmNova);

if (!resultado.gravou) {
enviarMensagem(chatId, "⚠️ KM ignorada. Deve ser maior que a última registrada.");
enviarMenuPrincipal(chatId);
return;
}

enviarMensagem(chatId, ✅ KM ${kmNova.toLocaleString("pt-BR")} registrada com sucesso.);
enviarMenuPrincipal(chatId);
}

/******************************

  • REGISTRA KM NO HISTORICO_KM
  • robusto, sem getLastRow()
    ******************************/
    function registrarKM(kmNova) {
    const ss = getSS_();
    const aba = ss.getSheetByName("HISTORICO_KM");

const lastKmRow = getLastDataRowInColumn_(aba, 3); // coluna C
const kmAtual = lastKmRow >= 2 ? Number(aba.getRange(lastKmRow, 3).getValue()) : 0;

if (kmNova <= kmAtual) return { gravou: false };

// proteção contra retry do Telegram
const props = PropertiesService.getScriptProperties();
const ultimaKm = Number(props.getProperty("ULTIMA_KM_PROCESSADA") || 0);
if (kmNova === ultimaKm) return { gravou: false };
props.setProperty("ULTIMA_KM_PROCESSADA", kmNova);

const novaLinha = Math.max(lastKmRow + 1, 2);

// Coluna D (KM_RODADO) fica por fórmula no sheet
aba.getRange(novaLinha, 1).setValue(hojeSemHora_()); // DATA_ENTRADA (A)
aba.getRange(novaLinha, 3).setValue(kmNova); // KM_TOTAL (C)
aba.getRange(novaLinha, 5).setValue("BOT"); // ORIGEM (E)

return { gravou: true };
}

/******************************

  • RESUMOS (ABA RESUMOS POR LINHAS)
    ******************************/
    function enviarMenuResumos(chatId) {
    const msg =
    `📊 RESUMOS
    Escolha uma opção:

1️⃣ Hoje
2️⃣ Semana
3️⃣ Mês
4️⃣ Ano
5️⃣ Geral
0️⃣ Voltar
00️⃣ Menu principal`;

enviarMensagem(chatId, msg);
}

function tratarMenuResumos(texto, chatId) {
switch (texto) {
case "1":
enviarResumoPorPeriodo(chatId, "HOJE");
break;
case "2":
enviarResumoPorPeriodo(chatId, "SEMANA");
break;
case "3":
enviarResumoPorPeriodo(chatId, "MES");
break;
case "4":
enviarResumoPorPeriodo(chatId, "ANO");
break;
case "5":
enviarResumoGeralPorLinhas(chatId);
break;
default:
enviarMensagem(chatId, "❌ Opção inválida.");
}
}

/**

  • Aba RESUMOS:
  • A: PERIODO | B: KM | C: GASTOS | D: GANHOS | E: SALDO | F: KM_ATUAL
    */
    function enviarResumoPorPeriodo(chatId, periodo) {
    const ss = getSS_();
    const aba = ss.getSheetByName("RESUMOS");

const lastRow = aba.getLastRow();
if (lastRow < 2) {
enviarMensagem(chatId, "⚠️ Aba RESUMOS sem dados.");
return;
}

const valores = aba.getRange(2, 1, lastRow - 1, 6).getValues();
const row = valores.find(r => String(r[0]).trim().toUpperCase() === periodo);

if (!row) {
enviarMensagem(chatId, ⚠️ Período '${periodo}' não encontrado na aba RESUMOS.);
return;
}

const km = row[1];
const gastos = row[2];
const ganhos = row[3];
const saldo = row[4];
const kmAtual = row[5];

const msg =
`📊 Resumo ${periodo}

📏 KM: ${km}
💸 Gastos: R$ ${gastos}
💰 Ganhos: R$ ${ganhos}
🟢 Saldo: R$ ${saldo}

🏍️ KM atual: ${kmAtual}`;

enviarMensagem(chatId, msg);
}

function enviarResumoGeralPorLinhas(chatId) {
const ss = getSS_();
const aba = ss.getSheetByName("RESUMOS");

const lastRow = aba.getLastRow();
if (lastRow < 2) {
enviarMensagem(chatId, "⚠️ Aba RESUMOS sem dados.");
return;
}

const valores = aba.getRange(2, 1, lastRow - 1, 6).getValues();

const get = (p) =>
valores.find(r => String(r[0]).trim().toUpperCase() === p) || null;

const hoje = get("HOJE");
const semana = get("SEMANA");
const mes = get("MES");
const ano = get("ANO");

const linha = (nome, r) =>
r
? ${nome}: KM ${r[1]} | Gastos R$ ${r[2]} | Ganhos R$ ${r[3]} | Saldo R$ ${r[4]}
: ${nome}: (sem dados);

const kmAtual =
(ano && ano[5]) ||
(mes && mes[5]) ||
(semana && semana[5]) ||
(hoje && hoje[5]) ||
"";

const msg =
`📊 Resumo Geral

${linha("HOJE", hoje)}
${linha("SEMANA", semana)}
${linha("MÊS", mes)}
${linha("ANO", ano)}

🏍️ KM atual: ${kmAtual}`;

enviarMensagem(chatId, msg);
}

/******************************

  • MANUTENÇÃO
    ******************************/
    function enviarMenuManutencao(chatId) {
    setUserState(chatId, STATE.MANUT_MENU);

const msg =
`🔧 MANUTENÇÃO
Escolha uma opção:

1️⃣ Próximas manutenções
2️⃣ Registrar manutenção
3️⃣ Histórico de manutenções
0️⃣ Voltar
00️⃣ Menu principal`;

enviarMensagem(chatId, msg);
}

function tratarMenuManutencao(texto, chatId) {
switch (texto) {
case "1":
enviarProximasManutencoes(chatId);
break;

case "2":
  iniciarRegistroManutencao(chatId);
  break;

case "3":
  enviarHistoricoManutencoes(chatId);
  break;

default:
  enviarMensagem(chatId, "❌ Opção inválida.");

}
}

/**

  • Lê CONTROLE_MANUTENCAO:
  • A ITEM | B INTERVALO_KM | C KM_BASE | D PROXIMA_TROCA | E KM_ATUAL | F FALTAM_KM | G STATUS
    */
    function enviarProximasManutencoes(chatId) {
    const aba = getSS_().getSheetByName("CONTROLE_MANUTENCAO");
    const lastRow = aba.getLastRow();
    if (lastRow < 2) {
    enviarMensagem(chatId, "⚠️ Aba CONTROLE_MANUTENCAO sem dados.");
    enviarMenuManutencao(chatId);
    return;
    }

const dados = aba.getRange(2, 1, lastRow - 1, 7).getValues();

let msg = "🔧 Próximas Manutenções\n\n";
dados.forEach(l => {
msg +=
`🧩 ${l[0]}
Próxima: ${l[3]}
KM atual: ${l[4]}
Faltam: ${l[5]}
Status: ${l[6]}

`;
});

enviarMensagem(chatId, msg);
enviarMenuManutencao(chatId);
}

/*** 3.3 - Histórico de manutenções (últimas 5) ***/
function enviarHistoricoManutencoes(chatId) {
const ss = getSS_();
const aba = ss.getSheetByName("HISTORICO_MANUTENCAO");

const lastRow = aba.getLastRow();
if (lastRow < 2) {
enviarMensagem(chatId, "📜 Histórico de manutenções vazio.");
enviarMenuManutencao(chatId);
return;
}

// DATA | ITEM | KM | VALOR | OBS
const dados = aba.getRange(2, 1, lastRow - 1, 6).getValues();

// Ordena por data desc (mais recente primeiro)
dados.sort((a, b) => new Date(b[0]) - new Date(a[0]));

const ultimas = dados.slice(0, 5);

let msg = "📜 Histórico de Manutenções (últimas 5)\n\n";
ultimas.forEach(l => {
const data = Utilities.formatDate(new Date(l[0]), Session.getScriptTimeZone(), "dd/MM/yyyy");
const item = l[1];
const km = l[2];
const valor = (l[3] !== "" && l[3] != null) ? R$ ${Number(l[3]).toFixed(2).replace(".", ",")} : "-";
const obs = l[4] ? 📝 ${l[4]} : "";

msg +=

`🛠️ ${item}
📅 ${data}
📏 KM ${km}
💸 ${valor}
${obs}

`;
});

enviarMensagem(chatId, msg);
enviarMenuManutencao(chatId);
}

/*** Registrar Manutenção (Opção A) ***/
function iniciarRegistroManutencao(chatId) {
clearDadosManutencao(chatId);
setUserState(chatId, STATE.MANUT_ITEM);

const msg =
`🧰 Qual manutenção foi feita?

1️⃣ Óleo do motor
2️⃣ Filtro de óleo
3️⃣ Fluido de freio
4️⃣ Relação completa
5️⃣ Embreagem
6️⃣ Outros

0️⃣ Cancelar`;

enviarMensagem(chatId, msg);
}

function tratarItemManutencao(texto, chatId) {
if (texto === "0") {
clearDadosManutencao(chatId);
enviarMenuManutencao(chatId);
return;
}

const mapa = {
"1": "Óleo do motor",
"2": "Filtro de óleo",
"3": "Fluido de freio",
"4": "Relação completa",
"5": "Embreagem"
};

if (texto === "6") {
setUserState(chatId, STATE.MANUT_OUTROS_NOME);
enviarMensagem(chatId, "Digite o nome da manutenção (ex: pastilha dianteira):");
return;
}

const item = mapa[texto];
if (!item) {
enviarMensagem(chatId, "❌ Opção inválida.");
return;
}

setManutProp(chatId, "ITEM", item);
setUserState(chatId, STATE.MANUT_KM);
enviarMensagem(chatId, "📏 Informe a KM no momento da manutenção:");
}

function tratarOutrosNomeManutencao(texto, chatId) {
const nome = (texto || "").trim();
if (!nome) {
enviarMensagem(chatId, "❌ Nome inválido. Digite novamente:");
return;
}

setManutProp(chatId, "ITEM", Outros - ${nome});
setUserState(chatId, STATE.MANUT_KM);
enviarMensagem(chatId, "📏 Informe a KM no momento da manutenção:");
}

function tratarKmManutencao(texto, chatId) {
if (!/^\d+$/.test(texto)) {
enviarMensagem(chatId, "❌ Digite apenas números.");
return;
}

const km = Number(texto);

// KM da manutenção não pode ser menor que a última KM registrada no HISTORICO_KM
const kmAtual = obterKmAtual_();
if (km < kmAtual) {
enviarMensagem(chatId, ⚠️ KM inválida. A última KM registrada é ${kmAtual}. Digite uma KM igual ou maior:);
return;
}

setManutProp(chatId, "KM", String(km));
setUserState(chatId, STATE.MANUT_VALOR);
enviarMensagem(chatId, "💸 Informe o valor gasto (ex: 90,00):");
}

function tratarValorManutencao(texto, chatId) {
const valor = parseValorBR_(texto);
if (!isFinite(valor) || valor < 0) {
enviarMensagem(chatId, "❌ Valor inválido. Ex: 90,00");
return;
}

setManutProp(chatId, "VALOR", String(valor));

const item = getManutProp(chatId, "ITEM");
const km = getManutProp(chatId, "KM");

setUserState(chatId, STATE.MANUT_CONFIRMA);
enviarMensagem(
chatId,
`✅ Confirmar manutenção?

Item: ${item}
KM: ${km}
Valor: R$ ${valor.toFixed(2).replace(".", ",")}

1️⃣ Confirmar
0️⃣ Cancelar`
);
}

function confirmarManutencao(texto, chatId) {
if (texto !== "1") {
clearDadosManutencao(chatId);
enviarMenuManutencao(chatId);
return;
}

const ss = getSS_();
const hist = ss.getSheetByName("HISTORICO_MANUTENCAO");
const cont = ss.getSheetByName("CONTABIL_DIARIO");

const item = getManutProp(chatId, "ITEM");
const km = Number(getManutProp(chatId, "KM"));
const valor = Number(getManutProp(chatId, "VALOR"));

// HISTORICO_MANUTENCAO: DATA | ITEM | KM | VALOR | OBS
hist.appendRow([hojeSemHora_(), item, km, valor, ""]);
cont.appendRow([hojeSemHora_(), "SAIDA", "MANUTENCAO", valor, item]);

clearDadosManutencao(chatId);
enviarMensagem(chatId, "✅ Manutenção registrada com sucesso.");
enviarMenuManutencao(chatId);
}

/******************************

  • Helpers Manutenção
    ******************************/
    function setManutProp(chatId, chave, valor) {
    PropertiesService.getScriptProperties().setProperty(MANUT_${chave}_${chatId}, valor);
    }

function getManutProp(chatId, chave) {
return PropertiesService.getScriptProperties().getProperty(MANUT_${chave}_${chatId}) || "";
}

function clearDadosManutencao(chatId) {
const props = PropertiesService.getScriptProperties();
["ITEM", "KM", "VALOR"].forEach(k => props.deleteProperty(MANUT_${k}_${chatId}));
}

function obterKmAtual_() {
const ss = getSS_();
const aba = ss.getSheetByName("HISTORICO_KM");
const lastKmRow = getLastDataRowInColumn_(aba, 3); // coluna C
return lastKmRow >= 2 ? Number(aba.getRange(lastKmRow, 3).getValue()) : 0;
}

/**

  • Converte "R$ 1.234,56" / "1234,56" / "90" => number
    */
    function parseValorBR_(txt) {
    let s = String(txt || "").trim().toLowerCase();
    s = s.replace("r$", "").replace(/\s+/g, "");
    // remove separador de milhar
    s = s.replace(/./g, "");
    // vírgula decimal -> ponto
    s = s.replace(",", ".");
    const n = Number(s);
    return n;
    }

/******************************

  • CONTROLE DE ESTADO
    ******************************/
    function getUserState(chatId) {
    const props = PropertiesService.getScriptProperties();
    return props.getProperty("STATE_" + chatId) || STATE.MENU;
    }

function setUserState(chatId, state) {
const props = PropertiesService.getScriptProperties();
props.setProperty("STATE_" + chatId, state);
}

function clearFluxos(chatId) {
clearUserState(chatId);
clearDadosManutencao(chatId);
}

function clearUserState(chatId) {
const props = PropertiesService.getScriptProperties();
props.deleteProperty("STATE_" + chatId);
}
/******************************

  • 5.1.3 — GERENCIAR ALERTA (ALTERAR DATA)
    ******************************/
    function tratarGerenciarNovaData_(texto, chatId) {
    // valida formato dd/mm/aaaa
    if (!/^\d{2}/\d{2}/\d{4}$/.test(texto)) {
    enviarMensagem(chatId, "❌ Data inválida. Use dd/mm/aaaa:");
    return;
    }

const [d, m, a] = texto.split("/").map(Number);
const data = new Date(a, m - 1, d);

if (isNaN(data.getTime())) {
enviarMensagem(chatId, "❌ Data inválida. Tente novamente:");
return;
}

const ss = getSS_();
const aba = ss.getSheetByName("ALERTAS_DATA");

const props = PropertiesService.getScriptProperties();
const linha = Number(props.getProperty("ALERTA_GER_LINHA"));

if (!linha) {
enviarMensagem(chatId, "⚠️ Nenhum alerta selecionado.");
enviarMenuAlertas(chatId);
return;
}

// coluna B = DATA
aba.getRange(linha, 2).setValue(data);

enviarMensagem(chatId, "✅ Data do alerta atualizada com sucesso.");
enviarMenuAlertas(chatId);
}
/******************************

  • 5.1.3 — GERENCIAR ALERTA (ALTERAR PRAZO)
    ******************************/
    function tratarGerenciarNovosDias_(texto, chatId) {
    if (!/^\d+$/.test(texto)) {
    enviarMensagem(chatId, "❌ Digite apenas números (ex: 5):");
    return;
    }

const dias = Number(texto);

const ss = getSS_();
const aba = ss.getSheetByName("ALERTAS_DATA");

const props = PropertiesService.getScriptProperties();
const linha = Number(props.getProperty("ALERTA_GER_LINHA"));

if (!linha) {
enviarMensagem(chatId, "⚠️ Nenhum alerta selecionado.");
enviarMenuAlertas(chatId);
return;
}

// coluna C = ALERTAR (dias antes)
aba.getRange(linha, 3).setValue(dias);

enviarMensagem(chatId, "✅ Prazo do alerta atualizado com sucesso.");
enviarMenuAlertas(chatId);
}
/******************************

  • UTILITÁRIOS
    ******************************/
    function incluirNovoAlerta_(item, data, alertarDias) {
    const ss = getSS_();
    const aba = ss.getSheetByName("ALERTAS_DATA");

const lastRow = aba.getLastRow();
const colA = aba.getRange(2, 1, Math.max(lastRow - 1, 1), 1).getValues();

let linhaLivre = -1;
for (let i = 0; i < colA.length; i++) {
if (!colA[i][0]) {
linhaLivre = i + 2;
break;
}
}

if (linhaLivre === -1) {
linhaLivre = lastRow + 1;
}

aba.getRange(linhaLivre, 1).setValue(item); // ITEM
aba.getRange(linhaLivre, 2).setValue(data); // DATA
aba.getRange(linhaLivre, 3).setValue(alertarDias); // ALERTAR
aba.getRange(linhaLivre, 5).setValue(true); // ATIVO
aba.getRange(linhaLivre, 6).setValue(""); // OBS
}
function getLastDataRowInColumn_(sheet, col) {
const maxRows = sheet.getSheetValues(sheet.getMaxRows(), col, 1, 1)[0][0] === "" ? sheet.getMaxRows() : sheet.getMaxRows();
const cell = sheet.getRange(maxRows, col).getNextDataCell(SpreadsheetApp.Direction.UP);
return cell.getRow();
}

function enviarMensagem(chatId, texto) {
UrlFetchApp.fetch(TELEGRAM_API + "/sendMessage", {
method: "post",
contentType: "application/json",
payload: JSON.stringify({ chat_id: chatId, text: texto }),
muteHttpExceptions: true
});
}

/******************************

  • RESPOSTA HTTP 200 (SEM 302)
    ******************************/
    function respostaOK() {
    return HtmlService.createHtmlOutput("OK");
    }

/******************************

  • CONTÁBIL (MENU 4)
    ******************************/
    function enviarMenuContabil(chatId) {
    setUserState(chatId, STATE.CONTABIL_MENU);

enviarMensagem(
chatId,
`💰 CONTÁBIL
Escolha uma opção:

1️⃣ Registrar entrada
2️⃣ Registrar saída
3️⃣ Resumo financeiro
4️⃣ Resumo por categoria (mês)
5️⃣ Acumulado geral

0️⃣ Voltar
00️⃣ Menu principal`
);
}

function tratarMenuContabil(texto, chatId) {
switch (texto) {
case "1":
iniciarEntrada_(chatId);
break;
case "2":
iniciarSaida_(chatId);
break;
case "3":
enviarMenuResumoFinanceiro(chatId);
break;
case "4":
enviarResumoPorCategoriaMes_(chatId);
break;
case "5":
enviarAcumuladoGeral_(chatId);
break;
default:
enviarMensagem(chatId, "❌ Opção inválida.");
}
}

/******************************

  • 4.1 Registrar ENTRADA
    ******************************/
    function iniciarEntrada_(chatId) {
    clearDadosEntrada_(chatId);
    setUserState(chatId, STATE.CONT_ENT_CATEG);

enviarMensagem(
chatId,
`💵 ENTRADA - Escolha a categoria:

1️⃣ UBER
2️⃣ 99
3️⃣ IFOOD
4️⃣ SHOPEE
5️⃣ MERCADO LIVRE
6️⃣ OUTROS

0️⃣ Cancelar`
);
}

function tratarEntradaCategoria(texto, chatId) {
if (texto === "0") {
clearDadosEntrada_(chatId);
enviarMenuContabil(chatId);
return;
}

const mapa = {
"1": "UBER",
"2": "99",
"3": "IFOOD",
"4": "SHOPEE",
"5": "MERCADO LIVRE"
};

if (texto === "6") {
setUserState(chatId, STATE.CONT_ENT_OUTROS);
enviarMensagem(chatId, "Digite o nome da categoria (ex: LALAMOVE):");
return;
}

const cat = mapa[texto];
if (!cat) {
enviarMensagem(chatId, "❌ Opção inválida.");
return;
}

setEntradaProp_(chatId, "CAT", cat);
setUserState(chatId, STATE.CONT_ENT_VALOR);
enviarMensagem(chatId, "💰 Informe o valor (ex: 100,00):");
}

function tratarEntradaOutros(texto, chatId) {
const nome = (texto || "").trim();
if (!nome) {
enviarMensagem(chatId, "❌ Nome inválido. Digite novamente:");
return;
}

setEntradaProp_(chatId, "CAT", nome.toUpperCase());
setUserState(chatId, STATE.CONT_ENT_VALOR);
enviarMensagem(chatId, "💰 Informe o valor (ex: 100,00):");
}

function tratarEntradaValor(texto, chatId) {
const valor = parseValorBR_(texto);

if (!isFinite(valor) || valor <= 0) {
enviarMensagem(chatId, "❌ Valor inválido. Ex: 100,00");
return;
}

setEntradaProp_(chatId, "VALOR", String(valor));
setUserState(chatId, STATE.CONT_ENT_CONFIRMA);

const cat = getEntradaProp_(chatId, "CAT");

enviarMensagem(
chatId,
`✅ Confirmar ENTRADA?

Categoria: ${cat}
Valor: R$ ${valor.toFixed(2).replace(".", ",")}

1️⃣ Confirmar
0️⃣ Cancelar`
);
}

function confirmarEntrada(texto, chatId) {
if (texto !== "1") {
clearDadosEntrada_(chatId);
enviarMenuContabil(chatId);
return;
}

const ss = getSS_();
const aba = ss.getSheetByName("CONTABIL_DIARIO");

const categoria = getEntradaProp_(chatId, "CAT");
const valor = Number(getEntradaProp_(chatId, "VALOR"));

// DATA sem hora (CRÍTICO)
aba.appendRow([hojeSemHora_(), "ENTRADA", categoria, valor, ""]);

clearDadosEntrada_(chatId);
enviarMensagem(chatId, "✅ Entrada registrada com sucesso.");
enviarMenuContabil(chatId);
}

/******************************

  • 4.2 Registrar SAÍDA
    ******************************/
    function iniciarSaida_(chatId) {
    clearDadosSaida_(chatId);
    setUserState(chatId, STATE.CONT_SAI_CATEG);

enviarMensagem(
chatId,
`💸 SAÍDA - Escolha a categoria:

1️⃣ COMBUSTÍVEL
2️⃣ ALMOÇO
3️⃣ JANTAR
4️⃣ MERCADO
5️⃣ OUTROS

0️⃣ Cancelar`
);
}

function tratarSaidaCategoria(texto, chatId) {
if (texto === "0") {
clearDadosSaida_(chatId);
enviarMenuContabil(chatId);
return;
}

const mapa = {
"1": "COMBUSTIVEL",
"2": "ALMOCO",
"3": "JANTAR",
"4": "MERCADO"
};

if (texto === "5") {
setUserState(chatId, STATE.CONT_SAI_OUTROS);
enviarMensagem(chatId, "Digite o nome da categoria (ex: PEDÁGIO):");
return;
}

const cat = mapa[texto];
if (!cat) {
enviarMensagem(chatId, "❌ Opção inválida.");
return;
}

setSaidaProp_(chatId, "CAT", cat);
setUserState(chatId, STATE.CONT_SAI_VALOR);
enviarMensagem(chatId, "💰 Informe o valor (ex: 30,00):");
}

function tratarSaidaOutros(texto, chatId) {
const nome = (texto || "").trim();
if (!nome) {
enviarMensagem(chatId, "❌ Nome inválido. Digite novamente:");
return;
}

setSaidaProp_(chatId, "CAT", nome.toUpperCase());
setUserState(chatId, STATE.CONT_SAI_VALOR);
enviarMensagem(chatId, "💰 Informe o valor (ex: 30,00):");
}

function tratarSaidaValor(texto, chatId) {
const valor = parseValorBR_(texto);

if (!isFinite(valor) || valor <= 0) {
enviarMensagem(chatId, "❌ Valor inválido. Ex: 30,00");
return;
}

setSaidaProp_(chatId, "VALOR", String(valor));
setUserState(chatId, STATE.CONT_SAI_CONFIRMA);

const cat = getSaidaProp_(chatId, "CAT");

enviarMensagem(
chatId,
`✅ Confirmar SAÍDA?

Categoria: ${cat}
Valor: R$ ${valor.toFixed(2).replace(".", ",")}

1️⃣ Confirmar
0️⃣ Cancelar`
);
}

function confirmarSaida(texto, chatId) {
if (texto !== "1") {
clearDadosSaida_(chatId);
enviarMenuContabil(chatId);
return;
}

const ss = getSS_();
const aba = ss.getSheetByName("CONTABIL_DIARIO");

const categoria = getSaidaProp_(chatId, "CAT");
const valor = Number(getSaidaProp_(chatId, "VALOR"));

aba.appendRow([hojeSemHora_(), "SAIDA", categoria, valor, ""]);

clearDadosSaida_(chatId);
enviarMensagem(chatId, "✅ Saída registrada com sucesso.");
enviarMenuContabil(chatId);
}

/******************************

  • 4.3-A Resumo Financeiro - HOJE
    ******************************/
    function enviarResumoFinanceiroHoje_(chatId) {
    const ss = getSS_();
    const aba = ss.getSheetByName("CONTABIL_DIARIO");

const lastRow = aba.getLastRow();
if (lastRow < 2) {
enviarMensagem(chatId, "⚠️ Nenhum lançamento encontrado.");
enviarMenuContabil(chatId);
return;
}

const dados = aba.getRange(2, 1, lastRow - 1, 4).getValues();
const hoje = hojeSemHora_().getTime();

let entradas = 0;
let saidas = 0;

dados.forEach(l => {
const data = new Date(l[0]).getTime();
const tipo = String(l[1]).toUpperCase();
const valor = Number(l[3]) || 0;

if (data === hoje) {
  if (tipo === "ENTRADA") entradas += valor;
  if (tipo === "SAIDA") saidas += valor;
}

});

const saldo = entradas - saidas;

enviarMensagem(
chatId,
`💰 RESUMO FINANCEIRO — HOJE

📥 Entradas: R$ ${entradas.toFixed(2).replace(".", ",")}
📤 Saídas: R$ ${saidas.toFixed(2).replace(".", ",")}

🟢 Saldo: R$ ${saldo.toFixed(2).replace(".", ",")}`
);

enviarMenuContabil(chatId);
}

function tratarMenuConfiguracoes(texto, chatId) {
switch (texto) {
case "1":
enviarMenuAlertas(chatId);
break;

case "2":
  enviarResumoAvancado_(chatId);
  break;

default:
  enviarMensagem(chatId, "❌ Opção inválida.");

}
}

/******************************

  • 5.2 — ALERTAS
    ******************************/
    function enviarMenuAlertas(chatId) {
    setUserState(chatId, STATE.ALERTA_MENU);

enviarMensagem(
chatId,
`🔔 ALERTAS
Escolha uma opção:

1️⃣ Listar alertas
2️⃣ Incluir novo alerta
3️⃣ Gerenciar alerta existente

0️⃣ Voltar`
);
}

function tratarMenuAlertas(texto, chatId) {
switch (texto) {
case "1":
listarAlertasAtivos_(chatId);
break;

case "2":
  setUserState(chatId, STATE.ALERTA_NOVO_ITEM);
  enviarMensagem(chatId, "📝 Informe o nome do alerta (ex: IPVA):");
  break;

case "3":
  enviarMenuGerenciarAlertas_(chatId);
  break;

default:
  enviarMensagem(chatId, "❌ Opção inválida.");

}
}

/******************************

  • 5.2 — INCLUIR NOVO ALERTA
    ******************************/
    function tratarAlertaNovoItem(texto, chatId) {
    const item = String(texto || "").trim();
    if (!item) {
    enviarMensagem(chatId, "❌ Nome inválido. Digite novamente:");
    return;
    }

PropertiesService.getScriptProperties()
.setProperty(ALERTA_ITEM_${chatId}, item);

setUserState(chatId, STATE.ALERTA_NOVO_DATA);
enviarMensagem(chatId, "📅 Informe a data do alerta (dd/mm/aaaa):");
}

function tratarAlertaNovoData(texto, chatId) {
const partes = texto.split("/");
if (partes.length !== 3) {
enviarMensagem(chatId, "❌ Data inválida. Use dd/mm/aaaa:");
return;
}

const [d, m, a] = partes.map(Number);
const data = new Date(a, m - 1, d);

if (isNaN(data.getTime())) {
enviarMensagem(chatId, "❌ Data inválida. Use dd/mm/aaaa:");
return;
}

PropertiesService.getScriptProperties()
.setProperty(ALERTA_DATA_${chatId}, data.toISOString());

setUserState(chatId, STATE.ALERTA_NOVO_DIAS);
enviarMensagem(chatId, "⏰ Quantos dias antes deseja ser alertado?");
}

function tratarAlertaNovoDias(texto, chatId) {
if (!/^\d+$/.test(texto)) {
enviarMensagem(chatId, "❌ Digite apenas números:");
return;
}

const dias = Number(texto);
const props = PropertiesService.getScriptProperties();

const item = props.getProperty(ALERTA_ITEM_${chatId});
const dataISO = props.getProperty(ALERTA_DATA_${chatId});

incluirNovoAlerta_(
item,
new Date(dataISO),
dias
);

// limpa dados temporários
props.deleteProperty(ALERTA_ITEM_${chatId});
props.deleteProperty(ALERTA_DATA_${chatId});

enviarMensagem(chatId, "✅ Alerta cadastrado com sucesso.");
enviarMenuAlertas(chatId);
}

/******************************

  • Helpers - Entrada
    ******************************/
    function setEntradaProp_(chatId, chave, valor) {
    PropertiesService.getScriptProperties().setProperty(ENT_${chave}_${chatId}, valor);
    }

function getEntradaProp_(chatId, chave) {
return PropertiesService.getScriptProperties().getProperty(ENT_${chave}_${chatId}) || "";
}

function clearDadosEntrada_(chatId) {
const props = PropertiesService.getScriptProperties();
["CAT", "VALOR"].forEach(k => props.deleteProperty(ENT_${k}_${chatId}));
}

/******************************

  • Helpers - Saída
    ******************************/
    function setSaidaProp_(chatId, chave, valor) {
    PropertiesService.getScriptProperties().setProperty(SAI_${chave}_${chatId}, valor);
    }

function getSaidaProp_(chatId, chave) {
return PropertiesService.getScriptProperties().getProperty(SAI_${chave}_${chatId}) || "";
}

function clearDadosSaida_(chatId) {
const props = PropertiesService.getScriptProperties();
["CAT", "VALOR"].forEach(k => props.deleteProperty(SAI_${k}_${chatId}));
}

/******************************

  • 4.3-B Resumo Financeiro - SEMANA / MÊS / ANO
    ******************************/

/**

  • Calcula resumo financeiro por intervalo de datas
    */
    function calcularResumoFinanceiro_(dataInicio, dataFim) {
    const ss = getSS_();
    const aba = ss.getSheetByName("CONTABIL_DIARIO");

const lastRow = aba.getLastRow();
if (lastRow < 2) {
return { entradas: 0, saidas: 0, saldo: 0 };
}

const dados = aba.getRange(2, 1, lastRow - 1, 4).getValues();

let entradas = 0;
let saidas = 0;

dados.forEach(l => {
const data = new Date(l[0]);
const tipo = String(l[1]).toUpperCase();
const valor = Number(l[3]) || 0;

if (data >= dataInicio && data <= dataFim) {
  if (tipo === "ENTRADA") entradas += valor;
  if (tipo === "SAIDA") saidas += valor;
}

});

return {
entradas,
saidas,
saldo: entradas - saidas
};
}

/**

  • Resumo SEMANA atual
    */
    function enviarResumoFinanceiroSemana_(chatId) {
    const hoje = hojeSemHora_();
    const inicioSemana = new Date(hoje);
    inicioSemana.setDate(hoje.getDate() - hoje.getDay()); // domingo

const fimSemana = new Date(inicioSemana);
fimSemana.setDate(inicioSemana.getDate() + 6);

const r = calcularResumoFinanceiro_(inicioSemana, fimSemana);

enviarMensagem(
chatId,
`📊 Resumo Financeiro — SEMANA

💰 Entradas: R$ ${r.entradas.toFixed(2).replace(".", ",")}
💸 Saídas: R$ ${r.saidas.toFixed(2).replace(".", ",")}
🟢 Saldo: R$ ${r.saldo.toFixed(2).replace(".", ",")}`
);
}

/**

  • Resumo MÊS atual
    */
    function enviarResumoFinanceiroMes_(chatId) {
    const hoje = hojeSemHora_();
    const inicioMes = new Date(hoje.getFullYear(), hoje.getMonth(), 1);
    const fimMes = new Date(hoje.getFullYear(), hoje.getMonth() + 1, 0);

const r = calcularResumoFinanceiro_(inicioMes, fimMes);

enviarMensagem(
chatId,
`📊 Resumo Financeiro — MÊS

💰 Entradas: R$ ${r.entradas.toFixed(2).replace(".", ",")}
💸 Saídas: R$ ${r.saidas.toFixed(2).replace(".", ",")}
🟢 Saldo: R$ ${r.saldo.toFixed(2).replace(".", ",")}`
);
}

/**

  • Resumo ANO atual
    */
    function enviarResumoFinanceiroAno_(chatId) {
    const hoje = hojeSemHora_();
    const inicioAno = new Date(hoje.getFullYear(), 0, 1);
    const fimAno = new Date(hoje.getFullYear(), 11, 31);

const r = calcularResumoFinanceiro_(inicioAno, fimAno);

enviarMensagem(
chatId,
`📊 Resumo Financeiro — ANO

💰 Entradas: R$ ${r.entradas.toFixed(2).replace(".", ",")}
💸 Saídas: R$ ${r.saidas.toFixed(2).replace(".", ",")}
🟢 Saldo: R$ ${r.saldo.toFixed(2).replace(".", ",")}`
);
}

/******************************

  • 4.3-C Menu Resumo Financeiro
    ******************************/
    function enviarMenuResumoFinanceiro(chatId) {
    setUserState(chatId, STATE.CONT_RES_MENU);

enviarMensagem(
chatId,
`📊 RESUMO FINANCEIRO
Escolha uma opção:

1️⃣ Hoje
2️⃣ Semana
3️⃣ Mês
4️⃣ Ano

0️⃣ Voltar`
);
}

function tratarMenuResumoFinanceiro(texto, chatId) {
switch (texto) {
case "1":
enviarResumoFinanceiroHoje_(chatId);
break;

case "2":
  enviarResumoFinanceiroSemana_(chatId);
  break;

case "3":
  enviarResumoFinanceiroMes_(chatId);
  break;

case "4":
  enviarResumoFinanceiroAno_(chatId);
  break;

default:
  enviarMensagem(chatId, "❌ Opção inválida.");

}
}

/******************************

  • 4.4 Resumo por categoria — MÊS (com totais)
    ******************************/
    function enviarResumoPorCategoriaMes_(chatId) {
    const ss = getSS_();
    const aba = ss.getSheetByName("CONTABIL_DIARIO");

const lastRow = aba.getLastRow();
if (lastRow < 2) {
enviarMensagem(chatId, "⚠️ Nenhum lançamento encontrado.");
enviarMenuContabil(chatId);
return;
}

const dados = aba.getRange(2, 1, lastRow - 1, 4).getValues();

const hoje = hojeSemHora_();
const inicioMes = new Date(hoje.getFullYear(), hoje.getMonth(), 1);
const fimMes = new Date(hoje.getFullYear(), hoje.getMonth() + 1, 0);

const entradas = {};
const saidas = {};

let totalEntradas = 0;
let totalSaidas = 0;

dados.forEach(l => {
const data = new Date(l[0]);
const tipo = String(l[1]).toUpperCase();
const categoria = String(l[2]).toUpperCase();
const valor = Number(l[3]) || 0;

if (data >= inicioMes && data <= fimMes) {
  if (tipo === "ENTRADA") {
    entradas[categoria] = (entradas[categoria] || 0) + valor;
    totalEntradas += valor;
  }
  if (tipo === "SAIDA") {
    saidas[categoria] = (saidas[categoria] || 0) + valor;
    totalSaidas += valor;
  }
}

});

let msg = 📊 RESUMO POR CATEGORIA — MÊS\n\n;

// ENTRADAS
msg += 📥 ENTRADAS\n;
if (Object.keys(entradas).length === 0) {
msg += - Sem entradas\n;
} else {
Object.keys(entradas).forEach(cat => {
msg += ${cat}: R$ ${entradas[cat].toFixed(2).replace(".", ",")}\n;
});
msg += Total: R$ ${totalEntradas.toFixed(2).replace(".", ",")}\n;
}

msg += \n📤 SAÍDAS\n;
if (Object.keys(saidas).length === 0) {
msg += - Sem saídas\n;
} else {
Object.keys(saidas).forEach(cat => {
msg += ${cat}: R$ ${saidas[cat].toFixed(2).replace(".", ",")}\n;
});
msg += Total: R$ ${totalSaidas.toFixed(2).replace(".", ",")}\n;
}

const saldo = totalEntradas - totalSaidas;

msg += \n💰 TOTAL DO MÊS: R$ ${saldo.toFixed(2).replace(".", ",")};

enviarMensagem(chatId, msg);
enviarMenuContabil(chatId);
}

/******************************

  • 4.5 Acumulado Geral
    ******************************/
    function enviarAcumuladoGeral_(chatId) {
    const ss = getSS_();
    const aba = ss.getSheetByName("CONTABIL_DIARIO");

const lastRow = aba.getLastRow();
if (lastRow < 2) {
enviarMensagem(chatId, "⚠️ Nenhum lançamento encontrado.");
enviarMenuContabil(chatId);
return;
}

const dados = aba.getRange(2, 1, lastRow - 1, 4).getValues();

let entradas = 0;
let saidas = 0;

dados.forEach(l => {
const tipo = String(l[1]).toUpperCase();
const valor = Number(l[3]) || 0;

if (tipo === "ENTRADA") entradas += valor;
if (tipo === "SAIDA") saidas += valor;

});

const saldo = entradas - saidas;

enviarMensagem(
chatId,
`📊 ACUMULADO GERAL

📥 Total Entradas: R$ ${entradas.toFixed(2).replace(".", ",")}
📤 Total Saídas: R$ ${saidas.toFixed(2).replace(".", ",")}

🟢 Resultado Final: R$ ${saldo.toFixed(2).replace(".", ",")}`
);

enviarMenuContabil(chatId);
}

/******************************

  • MENU 5 — CONFIGURAÇÕES
    ******************************/
    function enviarMenuConfiguracoes(chatId) {
    setUserState(chatId, STATE.CONFIG_MENU);

enviarMensagem(
chatId,
`⚙️ CONFIGURAÇÕES
Escolha uma opção:

1️⃣ Alertas (datas / vencimentos)
2️⃣ Resumo avançado

0️⃣ Voltar
00️⃣ Menu principal`
);
}

function doGet() {
return HtmlService.createHtmlOutput("OK");
}

function getChatIdDono_() {
const v = PropertiesService.getScriptProperties().getProperty("CHAT_ID_DONO");
return v ? Number(v) : null;
}
function rodarAlertasDiarios_() {
const chatId = getChatIdDono_();
if (!chatId) return;

const alertas = [];
alertas.push(...coletarAlertasManutencao_());
alertas.push(...coletarAlertasData_());

if (alertas.length) {
enviarMensagem(chatId, "🚨 ALERTAS (diário)\n\n" + alertas.join("\n\n"));
}
}
function coletarAlertasManutencao_() {
const ss = getSS_();
const aba = ss.getSheetByName("CONTROLE_MANUTENCAO");
if (!aba) return [];

const lastRow = aba.getLastRow();
if (lastRow < 2) return [];

const dados = aba.getRange(2, 1, lastRow - 1, 7).getValues();
const out = [];

dados.forEach(r => {
const item = String(r[0] || "").trim();
const faltam = r[5];
const status = String(r[6] || "").trim();
if (!item) return;

if (status === "⚠️ ATENÇÃO" || status === "🛑 ATRASADO") {
  out.push(`🔧 MANUTENÇÃO — ${status}\n${item}\nFaltam: ${faltam} km`);
}

});

return out;
}
function coletarAlertasData_() {
const ss = getSS_();
const aba = ss.getSheetByName("ALERTAS_DATA");
if (!aba) return [];

const lastRow = aba.getLastRow();
if (lastRow < 2) return [];

const dados = aba.getRange(2, 1, lastRow - 1, 6).getValues();
const out = [];

dados.forEach(r => {
const item = String(r[0] || "").trim();
const data = r[1];
const alertar = r[2];
const status = String(r[3] || "").trim();
const ativo = r[4]; // COLUNA E
const obs = String(r[5] || "").trim(); // COLUNA F

// 👇 AQUI É O LUGAR EXATO
if (ativo !== true) return;

if (!item || !data) return;

if (status === "⚠️ PENDENTE" || status === "🛑 VENCIDO") {
const dataFmt = Utilities.formatDate(
new Date(data),
Session.getScriptTimeZone(),
"dd/MM/yyyy"
);

out.push(
  `📅 ${item} — ${status}\nVenc.: ${dataFmt}\nAlertar: ${alertar} dia(s)` +
  (obs ? `\nObs: ${obs}` : "")
);

}
});

return out;
}
function criarGatilhoAlertasDiario_() {
ScriptApp.newTrigger("rodarAlertasDiarios_")
.timeBased()
.everyDays(1)
.atHour(7) // ajuste aqui
.create();
}

function listarAlertasAtivos_(chatId) {
const ss = getSS_();
const aba = ss.getSheetByName("ALERTAS_DATA");
if (!aba) {
enviarMensagem(chatId, "⚠️ Aba ALERTAS_DATA não encontrada.");
enviarMenuAlertas(chatId);
return;
}

const lastRow = aba.getLastRow();
if (lastRow < 2) {
enviarMensagem(chatId, "📭 Nenhum alerta cadastrado.");
enviarMenuAlertas(chatId);
return;
}

const dados = aba.getRange(2, 1, lastRow - 1, 6).getValues();

let msg = "📋 ALERTAS ATIVOS\n\n";
let encontrou = false;

dados.forEach(r => {
const item = r[0];
const data = r[1];
const alertar = r[2];
const status = r[3];
const ativo = r[4];

if (ativo === true && (status === "⚠️ PENDENTE" || status === "🛑 VENCIDO")) {
  encontrou = true;
  const dataFmt = Utilities.formatDate(
    new Date(data),
    Session.getScriptTimeZone(),
    "dd/MM/yyyy"
  );

  msg +=

`📅 ${item}
Status: ${status}
Venc.: ${dataFmt}
Alertar: ${alertar} dia(s)

`;
}
});

if (!encontrou) {
msg += "✅ Nenhum alerta pendente ou vencido.";
}

enviarMensagem(chatId, msg);
enviarMenuAlertas(chatId);
}

function enviarResumoAvancado_(chatId) {
const ss = getSS_();
const aba = ss.getSheetByName("CONTABIL_DIARIO");

if (!aba || aba.getLastRow() < 2) {
enviarMensagem(chatId, "⚠️ Sem dados para resumo avançado.");
enviarMenuConfiguracoes(chatId);
return;
}

const dados = aba.getRange(2, 1, aba.getLastRow() - 1, 4).getValues();

let entradas = 0;
let saidas = 0;

dados.forEach(r => {
const tipo = String(r[1]).toUpperCase();
const valor = Number(r[3]) || 0;
if (tipo === "ENTRADA") entradas += valor;
if (tipo === "SAIDA") saidas += valor;
});

enviarMensagem(
chatId,
`📊 RESUMO AVANÇADO

📥 Total Entradas: R$ ${entradas.toFixed(2).replace(".", ",")}
📤 Total Saídas: R$ ${saidas.toFixed(2).replace(".", ",")}
🟢 Resultado Geral: R$ ${(entradas - saidas).toFixed(2).replace(".", ",")}`
);

enviarMenuConfiguracoes(chatId);
}

/******************************

  • 5.1.3 — GERENCIAR ALERTA EXISTENTE
    ******************************/
    function enviarMenuGerenciarAlertas_(chatId) {
    const ss = getSS_();
    const aba = ss.getSheetByName("ALERTAS_DATA");

if (!aba || aba.getLastRow() < 2) {
enviarMensagem(chatId, "📭 Nenhum alerta cadastrado.");
enviarMenuAlertas(chatId);
return;
}

const dados = aba.getRange(2, 1, aba.getLastRow() - 1, 6).getValues();

let msg = "🛠️ GERENCIAR ALERTA\n\n";
let mapa = {};
let idx = 1;

dados.forEach((r, i) => {
const item = String(r[0] || "").trim();
if (!item) return;

const dataFmt = r[1]
  ? Utilities.formatDate(new Date(r[1]), Session.getScriptTimeZone(), "dd/MM/yyyy")
  : "-";

const status = r[3] || "-";
const ativo = r[4] === true ? "ATIVO" : "INATIVO";

msg += `${idx}️⃣ ${item}\n📅 ${dataFmt} | ${status} | ${ativo}\n\n`;
mapa[idx] = i + 2; // linha real no Excel
idx++;

});

if (idx === 1) {
enviarMensagem(chatId, "📭 Nenhum alerta válido encontrado.");
enviarMenuAlertas(chatId);
return;
}

// salva o mapa número -> linha
PropertiesService.getScriptProperties().setProperty(
ALERTA_GER_MAPA_${chatId},
JSON.stringify(mapa)
);

msg += "Digite o número do alerta para gerenciar\n0️⃣ Voltar";

setUserState(chatId, STATE.ALERTA_GER_SELECIONA);
enviarMensagem(chatId, msg);
}
/******************************

  • 5.1.3 — GERENCIAR ALERTA (SELECIONAR)
    ******************************/
    function tratarSelecionaAlertaGerenciar_(texto, chatId) {
    if (!/^\d+$/.test(texto)) {
    enviarMensagem(chatId, "❌ Digite apenas o número do alerta.");
    return;
    }

const idx = Number(texto);

const props = PropertiesService.getScriptProperties();
const mapaRaw = props.getProperty(ALERTA_GER_MAPA_${chatId});

if (!mapaRaw) {
enviarMensagem(chatId, "⚠️ Lista de alertas expirada. Tente novamente.");
enviarMenuAlertas(chatId);
return;
}

const mapa = JSON.parse(mapaRaw);
const linha = mapa[idx];

if (!linha) {
enviarMensagem(chatId, "❌ Número inválido. Escolha um alerta da lista.");
return;
}

// 🔐 salva a linha REAL do Excel
props.setProperty("ALERTA_GER_LINHA", String(linha));

setUserState(chatId, STATE.ALERTA_GER_MENU);

enviarMensagem(
chatId,
`🛠️ O que deseja fazer com este alerta?

1️⃣ Ativar / Desativar
2️⃣ Alterar data
3️⃣ Alterar prazo de aviso

0️⃣ Voltar`
);
}
/******************************

  • 5.1.3 — GERENCIAR ALERTA (MENU DE AÇÃO)
    ******************************/
    function tratarMenuAcaoGerenciar_(texto, chatId) {
    const ss = getSS_();
    const aba = ss.getSheetByName("ALERTAS_DATA");

const props = PropertiesService.getScriptProperties();
const linha = Number(props.getProperty("ALERTA_GER_LINHA"));

if (!linha) {
enviarMensagem(chatId, "⚠️ Nenhum alerta selecionado.");
enviarMenuAlertas(chatId);
return;
}

if (texto === "1") {
// Ativar / Desativar
const ativoAtual = aba.getRange(linha, 5).getValue(); // COLUNA E
aba.getRange(linha, 5).setValue(!ativoAtual);

enviarMensagem(
  chatId,
  ativoAtual ? "🚫 Alerta desativado." : "✅ Alerta ativado."
);

enviarMenuAlertas(chatId);
return;

}

if (texto === "2") {
setUserState(chatId, STATE.ALERTA_GER_NOVA_DATA);
enviarMensagem(chatId, "📅 Informe a nova data (dd/mm/aaaa):");
return;
}

if (texto === "3") {
setUserState(chatId, STATE.ALERTA_GER_NOVOS_DIAS);
enviarMensagem(chatId, "⏰ Informe o novo prazo de alerta (em dias):");
return;
}

if (texto === "0") {
enviarMenuAlertas(chatId);
return;
}

enviarMensagem(chatId, "❌ Opção inválida.");
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment