Files
COMERCIAL-GTI/gti-maquina-comercial/src/lead-engine.mjs
T
GustavoandClaude Sonnet 5 f5af038e19 chore: import gti-maquina-comercial history via subtree
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

git-subtree-dir: gti-maquina-comercial
git-subtree-mainline: 93ca82979e
git-subtree-split: b77484946f
2026-09-28 15:50:08 -03:00

412 lines
12 KiB
JavaScript

const TARGET_CITY_ALIASES = new Set([
'santo antonio de jesus',
'santo antonio de jesus ba',
'saj'
]);
const TARGET_SEGMENTS = new Set([
'mercado',
'supermercado',
'farmacia',
'drogaria',
'restaurante',
'delivery',
'distribuidora',
'autopecas',
'material de construcao',
'clinica',
'loja',
'varejo',
'atacado',
'oficina',
'escola',
'salao de beleza',
'barbearia',
'estetica',
'celular',
'transportadora',
'logistica'
]);
const SEGMENT_STOPWORDS = new Set(['de', 'da', 'do', 'e']);
function segmentTokens(value = '') {
return cleanText(value)
.split(/[^a-z0-9]+/)
.filter((token) => token.length > 2 && !SEGMENT_STOPWORDS.has(token));
}
const TARGET_SEGMENT_TOKENS = new Set(
[...TARGET_SEGMENTS].flatMap((term) => segmentTokens(term))
);
function tokenMatches(token, targetToken) {
return token === targetToken
|| (token.length > 4 && targetToken.length > 4 && (token.startsWith(targetToken) || targetToken.startsWith(token)));
}
function isTargetSegmentMatch(segmentKey) {
if (!segmentKey) return false;
if (TARGET_SEGMENTS.has(segmentKey)) return true;
return segmentTokens(segmentKey).some((token) =>
[...TARGET_SEGMENT_TOKENS].some((targetToken) => tokenMatches(token, targetToken))
);
}
const SERVICE_LABELS = {
erp: 'ERP / sistema de gestao',
ti: 'Gestao de TI',
whatsapp: 'Autoatendimento para WhatsApp',
indefinido: 'A definir'
};
const URGENCY_LABELS = {
alta: 'Alta',
media: 'Media',
baixa: 'Baixa',
indefinida: 'Indefinida'
};
export function stripAccents(value = '') {
return String(value)
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '');
}
export function cleanText(value = '') {
return stripAccents(value)
.toLowerCase()
.replace(/[^a-z0-9\s@._+-]/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
function firstDefined(...values) {
return values.find((value) => value !== undefined && value !== null && value !== '') ?? '';
}
function numberFrom(value) {
if (value === undefined || value === null || value === '') return 0;
if (typeof value === 'number' && Number.isFinite(value)) return value;
const match = String(value).match(/\d+/);
return match ? Number(match[0]) : 0;
}
function toArray(value) {
if (Array.isArray(value)) return value.filter(Boolean).map(String);
if (!value) return [];
return String(value)
.split(/[,;|]/)
.map((item) => item.trim())
.filter(Boolean);
}
export function normalizePhone(value = '') {
let digits = String(value).replace(/\D/g, '');
while (digits.startsWith('0')) digits = digits.slice(1);
if (digits.length === 10 || digits.length === 11) {
digits = `55${digits}`;
}
return digits;
}
export function detectService(value = '') {
const text = cleanText(value);
if (!text) return 'indefinido';
if (/\b(1|erp|sistema|gestao empresarial|estoque|financeiro|fiscal|nfce|nfe|pdv)\b/.test(text)) {
return 'erp';
}
if (/\b(2|ti|suporte|servidor|rede|backup|seguranca|computador|infraestrutura)\b/.test(text)) {
return 'ti';
}
if (/\b(3|whatsapp|autoatendimento|chatbot|bot|atendimento automatico|mensagem)\b/.test(text)) {
return 'whatsapp';
}
return 'indefinido';
}
export function normalizeUrgency(value = '') {
const text = cleanText(value);
if (!text) return 'indefinida';
if (/\b(alta|urgente|imediato|hoje|agora|essa semana|parado|problema serio|1)\b/.test(text)) return 'alta';
if (/\b(media|medio|proximo mes|em breve|avaliando|2)\b/.test(text)) return 'media';
if (/\b(baixa|sem pressa|futuro|pesquisa|so pesquisando|3)\b/.test(text)) return 'baixa';
return 'indefinida';
}
export function normalizeCurrentSystem(value = '') {
const text = cleanText(value);
if (!text) return 'nao_informado';
if (/\b(trocar|insatisfeito|migrar|outro sistema|uso outro|4)\b/.test(text)) return 'quer_trocar';
if (/\b(planilha|excel|manual|papel|3)\b/.test(text)) return 'planilhas';
if (/\b(sim|tenho|uso sistema|ja uso|1)\b/.test(text)) return 'usa_sistema';
if (/\b(nao|nenhum|nao tenho|sem sistema|2)\b/.test(text)) return 'sem_sistema';
return 'nao_informado';
}
export function normalizeLead(input = {}) {
const body = input.body && typeof input.body === 'object' ? input.body : input;
const contact = body.contact ?? body.contato ?? {};
const company = body.company ?? body.empresa_dados ?? {};
const message = body.message ?? body.mensagem ?? body.text ?? body.texto ?? body.description ?? '';
const serviceRaw = firstDefined(
body.service,
body.servico,
body.interest,
body.interesse,
body.solution,
body.solucao,
message
);
const city = firstDefined(company.city, company.cidade, body.city, body.cidade);
const state = firstDefined(company.state, company.uf, body.state, body.uf, body.estado);
const segment = firstDefined(company.segment, company.segmento, body.segment, body.segmento);
const phone = normalizePhone(firstDefined(contact.phone, contact.whatsapp, body.phone, body.telefone, body.whatsapp, body.from));
const name = firstDefined(contact.name, contact.nome, body.name, body.nome, body.contact_name, 'Lead sem nome');
const email = firstDefined(contact.email, body.email);
const companyName = firstDefined(company.name, company.nome, body.company_name, body.empresa, body.nome_empresa, 'Empresa nao informada');
const pains = toArray(firstDefined(body.pains, body.dores, body.pain, body.dor, body.maior_dificuldade));
const source = firstDefined(body.source, body.origem, body.canal, 'indefinida');
const service = detectService(serviceRaw);
const urgency = normalizeUrgency(firstDefined(body.urgency, body.urgencia, body.prazo, message));
const currentSystem = normalizeCurrentSystem(firstDefined(body.current_system, body.sistema_atual, body.usa_sistema, body.sistema, message));
const employees = numberFrom(firstDefined(company.employees, company.funcionarios, body.employees, body.funcionarios));
const computers = numberFrom(firstDefined(company.computers, company.computadores, body.computers, body.computadores, body.usuarios));
const whatsappVolume = numberFrom(firstDefined(body.whatsapp_volume, body.volume_whatsapp, body.mensagens_dia));
const consent = Boolean(firstDefined(body.consent, body.consentimento, body.opt_in, body.lgpd, false));
const cityKey = cleanText(city);
const segmentKey = cleanText(segment);
return {
id: body.id ?? body.lead_id ?? `GTI-${Date.now()}-${Math.random().toString(36).slice(2, 8).toUpperCase()}`,
createdAt: body.created_at ?? body.createdAt ?? new Date().toISOString(),
source: String(source || 'indefinida'),
name: String(name),
phone,
email: String(email || ''),
companyName: String(companyName),
city: String(city || ''),
state: String(state || ''),
segment: String(segment || ''),
service,
serviceLabel: SERVICE_LABELS[service],
urgency,
urgencyLabel: URGENCY_LABELS[urgency],
currentSystem,
pains,
message: String(message || ''),
employees,
computers,
whatsappVolume,
consent,
isLocal: TARGET_CITY_ALIASES.has(cityKey) || (cityKey.includes('santo antonio') && cityKey.includes('jesus')),
isTargetSegment: isTargetSegmentMatch(segmentKey),
raw: body
};
}
export function prioritizeLead(lead) {
const hasWhatsapp = Boolean(lead.phone);
return {
priority: hasWhatsapp ? 'com_whatsapp' : 'sem_whatsapp',
hasWhatsapp,
reasons: hasWhatsapp ? ['Possui WhatsApp/telefone para contato'] : []
};
}
export function buildNextQuestion(lead) {
if (lead.service === 'indefinido') {
return [
'Para te direcionar melhor, escolha uma opcao:',
'1 - Sistema ERP / gestao empresarial',
'2 - Suporte e gestao de TI',
'3 - Autoatendimento para WhatsApp',
'4 - Ainda nao sei, preciso de orientacao'
].join('\n');
}
if (lead.service === 'erp') {
if (!lead.segment) {
return [
'Qual o segmento da sua empresa?',
'1 - Comercio / loja',
'2 - Restaurante / delivery',
'3 - Mercado / supermercado',
'4 - Distribuidora',
'5 - Servicos',
'6 - Outro'
].join('\n');
}
if (lead.currentSystem === 'nao_informado') {
return [
'Hoje voce ja usa algum sistema?',
'1 - Sim',
'2 - Nao',
'3 - Uso planilhas',
'4 - Uso outro sistema, mas quero trocar'
].join('\n');
}
if (!lead.pains.length) {
return [
'Qual sua maior dificuldade hoje?',
'1 - Controle financeiro',
'2 - Estoque',
'3 - Vendas',
'4 - Emissao fiscal',
'5 - Relatorios',
'6 - Tudo muito manual'
].join('\n');
}
}
if (lead.service === 'ti') {
if (!lead.computers) {
return [
'Sua empresa possui quantos computadores ou usuarios?',
'1 - Ate 5',
'2 - 6 a 15',
'3 - 16 a 30',
'4 - Mais de 30'
].join('\n');
}
if (!lead.pains.length) {
return [
'Qual sua maior necessidade de TI?',
'1 - Suporte tecnico',
'2 - Servidores',
'3 - Rede / internet',
'4 - Backup',
'5 - Seguranca',
'6 - Terceirizar o TI'
].join('\n');
}
}
if (lead.service === 'whatsapp') {
if (!lead.pains.length) {
return [
'Voce quer automatizar qual tipo de atendimento?',
'1 - Vendas',
'2 - Suporte',
'3 - Agendamento',
'4 - Cardapio / pedidos',
'5 - Triagem de mensagens',
'6 - Financeiro / segunda via'
].join('\n');
}
if (!lead.whatsappVolume) {
return [
'Em media, quantas mensagens chegam no WhatsApp da empresa por dia?',
'1 - Ate 20',
'2 - 21 a 50',
'3 - 51 a 100',
'4 - Mais de 100'
].join('\n');
}
}
if (lead.urgency === 'indefinida') {
return [
'Qual a urgencia para resolver isso?',
'1 - Alta, preciso resolver agora',
'2 - Media, quero avaliar para breve',
'3 - Baixa, estou pesquisando'
].join('\n');
}
return [
'Pelo que voce informou, faz sentido um diagnostico rapido com a GTI.',
'Escolha o melhor horario:',
'1 - Hoje pela manha',
'2 - Hoje a tarde',
'3 - Amanha pela manha',
'4 - Amanha a tarde'
].join('\n');
}
export function buildInternalWhatsAppMessage(lead, priorityInfo) {
const pains = lead.pains.length ? lead.pains.join(', ') : 'Nao informado';
const phone = lead.phone || 'Nao informado';
const priority = priorityInfo.priority === 'com_whatsapp' ? 'COM WHATSAPP' : 'SEM WHATSAPP';
return [
`Novo lead captado - ${priority}`,
'',
`Empresa: ${lead.companyName}`,
`Contato: ${lead.name}`,
`WhatsApp: ${phone}`,
`Cidade/UF: ${lead.city || 'Nao informada'}${lead.state ? `/${lead.state}` : ''}`,
`Segmento: ${lead.segment || 'Nao informado'}`,
`Origem: ${lead.source}`,
`Dores: ${pains}`,
'',
'Acao recomendada: tratar manualmente pela equipe GTI.'
].join('\n');
}
export function buildCrmRecord(lead, priorityInfo) {
return {
lead_id: lead.id,
data_criacao: lead.createdAt,
status: 'Captado',
prioridade: priorityInfo.priority,
tem_whatsapp: Boolean(lead.phone),
origem: lead.source,
servico: lead.service,
servico_label: lead.serviceLabel,
empresa: lead.companyName,
contato: lead.name,
whatsapp: lead.phone,
email: lead.email,
cidade: lead.city,
uf: lead.state,
segmento: lead.segment,
urgencia: lead.urgency,
sistema_atual: lead.currentSystem,
dores: lead.pains.join('; '),
funcionarios: lead.employees,
computadores: lead.computers,
volume_whatsapp: lead.whatsappVolume,
consentimento: lead.consent,
proxima_acao: 'Tratamento manual',
observacao: lead.message
};
}
export function processLead(input = {}, options = {}) {
const lead = normalizeLead(input);
const priority = prioritizeLead(lead);
const commercialWhatsApp = options.commercialWhatsApp || process.env.GTI_COMMERCIAL_WHATSAPP || '{{GTI_COMMERCIAL_WHATSAPP}}';
return {
lead,
priority,
crmRecord: buildCrmRecord(lead, priority),
whatsapp: {
internal: {
to: commercialWhatsApp,
message: buildInternalWhatsAppMessage(lead, priority)
}
},
nextAction: 'tratamento_manual'
};
}