Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> git-subtree-dir: maquina-comercial-pessoal git-subtree-mainline:f5af038e19git-subtree-split:a9ef4d776d
212 lines
9.4 KiB
JavaScript
212 lines
9.4 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
managementFocus,
|
|
naturalBusinessName,
|
|
renderCampaignMessage,
|
|
variantForLead
|
|
} from '../src/outreach/campaign-messages.mjs';
|
|
|
|
const refusal = 'Se nao fizer sentido para sua empresa, e so me avisar que nao volto a entrar em contato.';
|
|
|
|
const approvedCases = [
|
|
{
|
|
segment: 'restaurantes',
|
|
template: `Ola, tudo bem? Sou Gustavo, representante comercial do Grupo GTI. Hoje voces controlam {foco_gestao} em um unico sistema? ${refusal}`,
|
|
expected: `Ola, tudo bem? Sou Gustavo, representante comercial do Grupo GTI. Hoje voces controlam vendas, estoque, comandas e emissão fiscal em um unico sistema? ${refusal}`
|
|
},
|
|
{
|
|
segment: 'restaurantes',
|
|
template: `Ola! Aqui e Gustavo, do Grupo GTI. Trabalhamos com gestao para empresas de Santo Antonio de Jesus e regiao. Como voces organizam hoje {foco_gestao}? ${refusal}`,
|
|
expected: `Ola! Aqui e Gustavo, do Grupo GTI. Trabalhamos com gestao para empresas de Santo Antonio de Jesus e regiao. Como voces organizam hoje vendas, estoque, comandas e emissão fiscal? ${refusal}`
|
|
},
|
|
{
|
|
segment: 'restaurantes',
|
|
template: `Ola, tudo bem? Sou Gustavo, do Grupo GTI. Ha mais de 7 anos atendemos empresas da regiao e ja temos mais de 100 clientes. Voces utilizam algum sistema para {foco_gestao}? ${refusal}`,
|
|
expected: `Ola, tudo bem? Sou Gustavo, do Grupo GTI. Ha mais de 7 anos atendemos empresas da regiao e ja temos mais de 100 clientes. Voces utilizam algum sistema para vendas, estoque, comandas e emissão fiscal? ${refusal}`
|
|
},
|
|
{
|
|
segment: 'pizzarias',
|
|
template: `Ola, tudo bem? Sou Gustavo, representante comercial do Grupo GTI. Queria entender como voces recebem os pedidos de delivery hoje: possuem um canal proprio ou dependem de aplicativos que cobram comissao? ${refusal}`,
|
|
expected: `Ola, tudo bem? Sou Gustavo, representante comercial do Grupo GTI. Queria entender como voces recebem os pedidos de delivery hoje: possuem um canal proprio ou dependem de aplicativos que cobram comissao? ${refusal}`
|
|
},
|
|
{
|
|
segment: 'pizzarias',
|
|
template: `Ola! Aqui e Gustavo, do Grupo GTI. Voces ja possuem um link proprio para receber pedidos, sem pagar comissao por venda a aplicativos? ${refusal}`,
|
|
expected: `Ola! Aqui e Gustavo, do Grupo GTI. Voces ja possuem um link proprio para receber pedidos, sem pagar comissao por venda a aplicativos? ${refusal}`
|
|
},
|
|
{
|
|
segment: 'pizzarias',
|
|
template: `Ola, tudo bem? Sou Gustavo, representante comercial do Grupo GTI. Trabalho com uma solucao de pedidos online por canal proprio e queria primeiro entender como funciona o delivery de voces hoje. ${refusal}`,
|
|
expected: `Ola, tudo bem? Sou Gustavo, representante comercial do Grupo GTI. Trabalho com uma solucao de pedidos online por canal proprio e queria primeiro entender como funciona o delivery de voces hoje. ${refusal}`
|
|
}
|
|
];
|
|
|
|
test('nome natural aceita marcas curtas com pelo menos duas letras', () => {
|
|
assert.equal(naturalBusinessName({ empresa: 'OX' }), 'OX');
|
|
assert.equal(naturalBusinessName({ empresa: 'BR2' }), 'BR2');
|
|
assert.equal(naturalBusinessName({ empresa: 'Pizzaria Central' }), 'Pizzaria Central');
|
|
});
|
|
|
|
test('nome natural rejeita identificadores, nomes legais, controles, placeholders e ruido generico', () => {
|
|
const rejected = [
|
|
'',
|
|
'7',
|
|
'7 Mares',
|
|
'61.590.668 A ARAUJO',
|
|
'12.345.678/0001-90',
|
|
'A ARAUJO LTDA',
|
|
'Loja Sol MEI',
|
|
'Loja Sol ME',
|
|
'Loja Sol EPP',
|
|
'Loja Sol EIRELI',
|
|
'Loja Sol S/A',
|
|
'Loja\nSol',
|
|
'Loja\u0000Sol',
|
|
'Loja\u200bSol',
|
|
'Loja {nome}',
|
|
'Loja {{nome}}',
|
|
'Empresa',
|
|
'Nome Fantasia',
|
|
'Nao informado',
|
|
'Comercio',
|
|
'Pizzaria Central com um nome comercial excessivamente longo para uma saudacao natural e segura'
|
|
];
|
|
|
|
for (const empresa of rejected) {
|
|
assert.equal(naturalBusinessName({ empresa, legal_name: 'A ARAUJO LTDA' }), '', empresa);
|
|
}
|
|
});
|
|
|
|
test('foco de gestao segue os quatro grupos aprovados', () => {
|
|
assert.equal(managementFocus('restaurantes'), 'vendas, estoque, comandas e emissão fiscal');
|
|
assert.equal(managementFocus('mercados_de_bairro'), 'vendas, estoque e emissão fiscal');
|
|
assert.equal(managementFocus('oficinas_mecanicas'), 'ordens de serviço, estoque e financeiro');
|
|
assert.equal(managementFocus('lojas_de_roupa'), 'vendas, estoque, financeiro e notas fiscais');
|
|
});
|
|
|
|
test('variante permanece estavel e considera apenas variantes ativas', () => {
|
|
const variants = [{ code: 'A', ativo: 1 }, { code: 'B', ativo: 1 }, { code: 'C', ativo: 1 }];
|
|
assert.equal(variantForLead(variants, 4).code, 'B');
|
|
assert.equal(variantForLead(variants, 4).code, 'B');
|
|
assert.equal(variantForLead([{ code: 'A', ativo: 0 }, ...variants.slice(1)], 4).code, 'B');
|
|
assert.throws(() => variantForLead([], 4), /variante ativa/i);
|
|
});
|
|
|
|
test('variante usa a ordem ativa mesmo com ids nao contiguos', () => {
|
|
const variants = [
|
|
{ id: 8, code: 'A', ativo: 1 },
|
|
{ id: 37, code: 'B', ativo: 1 },
|
|
{ id: 104, code: 'C', ativo: 1 }
|
|
];
|
|
assert.equal(variantForLead(variants, 4), variants[1]);
|
|
});
|
|
|
|
test('variante rejeita array nao vazio quando todas estao inativas', () => {
|
|
assert.throws(() => variantForLead([
|
|
{ id: 8, code: 'A', ativo: 0 },
|
|
{ id: 37, code: 'B', ativo: false }
|
|
], 4), /variante ativa/i);
|
|
});
|
|
|
|
test('renderizacao preserva os seis textos aprovados fora das substituicoes permitidas', () => {
|
|
for (const { segment, template, expected } of approvedCases) {
|
|
assert.equal(renderCampaignMessage(template, {
|
|
empresa: '61.590.668 A ARAUJO',
|
|
legal_name: 'A ARAUJO LTDA',
|
|
segmento: segment
|
|
}), expected);
|
|
}
|
|
});
|
|
|
|
test('renderizacao personaliza somente a saudacao inicial com nome natural', () => {
|
|
const template = approvedCases[0].template;
|
|
const rendered = renderCampaignMessage(template, {
|
|
empresa: 'Pizzaria Central',
|
|
segmento: 'restaurantes'
|
|
});
|
|
|
|
assert.equal(rendered, `Olá, pessoal da Pizzaria Central, tudo bem? Sou Gustavo, representante comercial do Grupo GTI. Hoje voces controlam vendas, estoque, comandas e emissão fiscal em um unico sistema? ${refusal}`);
|
|
});
|
|
|
|
test('validacao aceita texto portugues legitimo sem remover diacriticos', () => {
|
|
const template = `Olá, tudo bem? Sou Gustavo, do Grupo GTI. Gestão ágil para comércio. Se não fizer sentido para sua empresa, é só me avisar que não volto a entrar em contato.`;
|
|
assert.equal(renderCampaignMessage(template, { empresa: '', segmento: 'lojas_de_roupa' }), template);
|
|
|
|
const punctuation = `Olá, tudo bem? Sou Gustavo, do Grupo GTI. Gestão simples (vendas/estoque), ágil e segura. Se não fizer sentido para sua empresa, é só me avisar que não volto a entrar em contato.`;
|
|
assert.equal(renderCampaignMessage(punctuation, { empresa: '' }), punctuation);
|
|
});
|
|
|
|
test('validacao rejeita placeholders simples ou duplos que restarem no texto', () => {
|
|
const safe = `Ola, tudo bem? Sou Gustavo, do Grupo GTI. ${refusal}`;
|
|
const placeholders = [
|
|
'{produto}',
|
|
'{{produto_2}}',
|
|
'{ produto }',
|
|
'{produto-nome}',
|
|
'{123}',
|
|
'{{ produto }}',
|
|
'{{produto-nome}}',
|
|
'{{123}}'
|
|
];
|
|
|
|
for (const placeholder of placeholders) {
|
|
const template = safe.replace('Grupo GTI.', `Grupo GTI. ${placeholder}`);
|
|
assert.throws(() => renderCampaignMessage(template, {}), /mensagem inicial insegura/i, placeholder);
|
|
}
|
|
});
|
|
|
|
test('validacao de placeholders preserva portugues normal sem chaves', () => {
|
|
const template = `Olá, tudo bem? Sou Gustavo, do Grupo GTI. Produto 123 - nome e gestão simples. Se não fizer sentido para sua empresa, é só me avisar que não volto a entrar em contato.`;
|
|
assert.equal(renderCampaignMessage(template, {}), template);
|
|
});
|
|
|
|
test('validacao rejeita localhost, enderecos IP e caminhos absolutos', () => {
|
|
const safe = `Ola, tudo bem? Sou Gustavo, do Grupo GTI. ${refusal}`;
|
|
const addresses = [
|
|
'localhost',
|
|
'localhost:3000/agenda',
|
|
'127.0.0.1',
|
|
'192.168.0.1:8080/agenda',
|
|
'[::1]',
|
|
'[2001:db8::1]:8080/agenda',
|
|
'2001:db8::1',
|
|
'/agenda/demonstracao'
|
|
];
|
|
|
|
for (const address of addresses) {
|
|
const template = safe.replace('Grupo GTI.', `Grupo GTI. ${address}`);
|
|
assert.throws(() => renderCampaignMessage(template, {}), /mensagem inicial insegura/i, address);
|
|
}
|
|
});
|
|
|
|
test('renderizacao e selecao nao alteram os objetos de entrada', () => {
|
|
const template = { mensagem: approvedCases[0].template };
|
|
const lead = { empresa: 'Pizzaria Central', segmento: 'restaurantes' };
|
|
const variants = [{ id: 8, code: 'A', ativo: 0 }, { id: 37, code: 'B', ativo: 1 }];
|
|
const before = structuredClone({ template, lead, variants });
|
|
|
|
renderCampaignMessage(template, lead);
|
|
variantForLead(variants, 7);
|
|
|
|
assert.deepEqual({ template, lead, variants }, before);
|
|
});
|
|
|
|
test('validacao rejeita URL, CNPJ, preco e requisitos obrigatorios ausentes', () => {
|
|
const safe = `Ola, tudo bem? Sou Gustavo, do Grupo GTI. ${refusal}`;
|
|
const unsafe = [
|
|
safe.replace('Grupo GTI.', 'Grupo GTI. https://gti.example'),
|
|
safe.replace('Grupo GTI.', 'Grupo GTI. gti.com.br'),
|
|
safe.replace('Grupo GTI.', 'Grupo GTI. 12.345.678/0001-90'),
|
|
safe.replace('Grupo GTI.', 'Grupo GTI. 12345678000190'),
|
|
safe.replace('Grupo GTI.', 'Grupo GTI. R$ 99'),
|
|
safe.replace('Gustavo', 'Consultor'),
|
|
safe.replace('Grupo GTI', 'nossa empresa'),
|
|
safe.replace(` ${refusal}`, '')
|
|
];
|
|
|
|
for (const template of unsafe) {
|
|
assert.throws(() => renderCampaignMessage(template, { empresa: '' }), /mensagem inicial insegura/i);
|
|
}
|
|
});
|