Dashboard
Visão estratégica e operacional
🔴 Manutenções vencidas — clientes que não retornaram
🟡 Manutenções nos próximos 7 dias
🏆 Ranking VIP — maiores LTVs
📊 Sazonalidade mensal
🎯 Origem dos clientes
📦 Produtos mais vendidos
🎂 Aniversariantes do mês
😴 Clientes sumidos (+90 dias)
Base de Clientes
—
Cliente
Telefone
Cidade / Bairro
Origem
Status
Ações
Central de Reativação
Segmentação inteligente
Produtos & Serviços
Catálogo completo
Pesquisa por Produto
Veja quem comprou cada produto ou serviço
Satisfação — NPS
Net Promoter Score da sua base
Cliente
NPS
Classificação
Ação
Aniversários
Clientes que fazem aniversário este mês
Configurações
Integração e preferências
🔗 Google Sheets
Planilha com 3 abas:
clientes: id|nome|telefone|email|cidade|bairro|aniversario|origem|interesse|tags|status|nps|depoimento|ultimoContato|cadastroEm|obscompras: id|clienteId|produto|data|valor|formaPagamento|vendedor|proximaManutencao|frequencia|obsprodutos: id|nome|categoria|descricao|preco|custo|margem|frequencia|status|tags|cadastroEm
🔐 Senha
Padrão:
A senha é salva no Google Sheets (aba
admin123A senha é salva no Google Sheets (aba
config) e sincroniza em todos os dispositivos automaticamente. Se o Sheets não estiver configurado, fica salva só neste navegador.Exigir senha para entrar
Se desativado, o sistema abre direto, sem pedir senha.
🧹 Limpar Cache
Se o sistema travar ou não carregar, use este botão para limpar o cache do navegador e reiniciar.
💬 Mensagens do WhatsApp
Personalize as mensagens enviadas pelo sistema. Use
{nome} para inserir o nome do cliente automaticamente.⚙️ Código do Apps Script (backend)
Na planilha do Google Sheets: Extensões → Apps Script, apague tudo e cole o código abaixo. Rode a função
setup uma vez (▶ Executar) para criar as abas. Depois Implantar → Nova implantação → App da Web (executar como Eu, acesso Qualquer pessoa) e cole a URL que termina em /exec no campo lá em cima./*************************************************************
* CAIXA ESQUECIDO / CLIENTEBASE — Backend (Google Apps Script)
* Edifício Web — Engenharia de Receita
*
* Compatível 100% com clientebase_v5.html
*
* COMO INSTALAR (faça uma vez):
* 1. Abra a planilha do cliente no Google Sheets.
* 2. Menu Extensões → Apps Script.
* 3. Apague tudo que estiver lá e cole ESTE arquivo inteiro.
* 4. Clique em Salvar (ícone de disquete).
* 5. No menu de funções (em cima), selecione "setup" e clique em ▶ Executar.
* -> Isso cria as 4 abas (clientes, compras, produtos, config) já com
* os cabeçalhos certos. Autorize o acesso quando pedir.
* 6. Clique em Implantar → Nova implantação.
* Tipo: App da Web
* Executar como: Eu (você)
* Quem pode acessar: Qualquer pessoa
* Copie a URL que termina em /exec.
* 7. No sistema, vá em Configurações → cole a URL em "URL do Apps Script" → Salvar.
*
* IMPORTANTE: a cada vez que você ALTERAR este código, precisa
* "Implantar → Gerenciar implantações → Editar (lápis) → Nova versão"
* para a mudança valer na URL.
*************************************************************/
// ── Estrutura das abas (a ORDEM das colunas tem que bater com o sistema) ──
var SCHEMA = {
clientes: ['id','nome','telefone','email','cidade','bairro','aniversario','origem','interesse','tags','status','nps','depoimento','ultimoContato','cadastroEm','obs'],
compras: ['id','clienteId','produto','data','valor','formaPagamento','vendedor','proximaManutencao','frequencia','obs'],
produtos: ['id','nome','categoria','descricao','preco','custo','margem','frequencia','status','tags','cadastroEm'],
config: ['chave','valor']
};
/**
* Rode UMA vez (botão ▶ Executar) para criar todas as abas com cabeçalho.
* Pode rodar quantas vezes quiser — não apaga dados, só garante a estrutura.
*/
function setup() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
Object.keys(SCHEMA).forEach(function(name){ ensureSheet_(ss, name); });
// Remove a aba "Página1"/"Sheet1" vazia, se sobrou da planilha em branco
var lixo = ss.getSheetByName('Página1') || ss.getSheetByName('Sheet1');
if (lixo && ss.getSheets().length > 1 && lixo.getLastRow() === 0) {
ss.deleteSheet(lixo);
}
return 'Setup concluído ✅ — abas criadas/conferidas.';
}
/**
* Garante que a aba existe e que a primeira linha tem o cabeçalho correto.
* Esse é o ponto que faltava no script antigo (criava aba sem cabeçalho).
*/
function ensureSheet_(ss, name) {
var headers = SCHEMA[name];
var sheet = ss.getSheetByName(name);
if (!sheet) {
sheet = ss.insertSheet(name);
}
// Se a aba está vazia OU a primeira coluna não bate, (re)escreve o cabeçalho
if (sheet.getLastRow() === 0 || String(sheet.getRange(1,1).getValue()) !== headers[0]) {
sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
sheet.setFrozenRows(1);
}
return sheet;
}
// ── Saídas padronizadas ──
function jsonOut_(obj) {
return ContentService
.createTextOutput(JSON.stringify(obj))
.setMimeType(ContentService.MimeType.JSON);
}
/**
* Ponto de entrada único. O sistema chama:
* - LEITURA: ...?sheet=clientes -> devolve array de objetos
* - ESCRITA: ...?payload=base64({action,sheet,id,row})
* action = 'salvar' | 'editar' | 'deletar'
*/
function doGet(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// ---------- ESCRITA ----------
if (e && e.parameter && e.parameter.payload) {
try {
var data = JSON.parse(
Utilities.newBlob(
Utilities.base64Decode(e.parameter.payload, Utilities.Charset.UTF_8)
).getDataAsString('UTF-8')
);
var name = data.sheet || 'clientes';
var sheet = SCHEMA[name] ? ensureSheet_(ss, name) : (ss.getSheetByName(name) || ss.insertSheet(name));
if (data.action === 'salvar') {
// Upsert por id (coluna 0): se a linha já existe, sobrescreve; senão, adiciona.
// Isso evita linhas duplicadas caso a fila offline reenvie um "salvar"
// cuja resposta se perdeu depois de já ter sido gravado.
var rowsS = sheet.getDataRange().getValues();
var novoId = String(data.row[0]);
var achouS = -1;
for (var s = 1; s < rowsS.length; s++) {
if (String(rowsS[s][0]) === novoId) { achouS = s; break; }
}
if (achouS >= 0) {
sheet.getRange(achouS + 1, 1, 1, data.row.length).setValues([data.row]);
} else {
sheet.appendRow(data.row);
}
}
else if (data.action === 'editar') {
var rowsE = sheet.getDataRange().getValues();
for (var i = 1; i < rowsE.length; i++) {
if (String(rowsE[i][0]) === String(data.id)) {
sheet.getRange(i + 1, 1, 1, data.row.length).setValues([data.row]);
break;
}
}
}
else if (data.action === 'deletar') {
var rowsD = sheet.getDataRange().getValues();
for (var j = 1; j < rowsD.length; j++) {
if (String(rowsD[j][0]) === String(data.id)) {
sheet.deleteRow(j + 1);
break;
}
}
}
return jsonOut_({ ok: true });
} catch (err) {
return jsonOut_({ ok: false, error: err.message });
}
}
// ---------- LEITURA ----------
try {
var which = (e && e.parameter && e.parameter.sheet) || 'clientes';
var rsheet = SCHEMA[which] ? ensureSheet_(ss, which) : ss.getSheetByName(which);
if (!rsheet) return jsonOut_([]);
var rows = rsheet.getDataRange().getValues();
if (rows.length < 2) return jsonOut_([]); // só cabeçalho ou vazio
var headers = rows[0];
var out = rows.slice(1)
.filter(function(r){ return String(r[0]).trim() !== ''; }) // ignora linhas sem id
.map(function(r){
var o = {};
headers.forEach(function(h, i){ o[h] = r[i]; });
return o;
});
return jsonOut_(out);
} catch (err2) {
return jsonOut_([]);
}
}
// Alguns navegadores fazem POST; aceitamos e tratamos igual ao GET.
function doPost(e) {
return doGet(e);
}