Files
COMERCIAL-GTI/maquina-comercial-pessoal/tests/campaign-store.test.mjs
T
GustavoandClaude Sonnet 5 417da56bd6 chore: import maquina-comercial-pessoal history via subtree
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

git-subtree-dir: maquina-comercial-pessoal
git-subtree-mainline: f5af038e19
git-subtree-split: a9ef4d776d
2026-09-28 15:50:17 -03:00

162 lines
7.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { openDb } from '../src/db.mjs';
import {
CAMPAIGN_DEFINITIONS,
CAMPAIGN_PRODUCTS,
CAMPAIGN_STAGES
} from '../src/outreach/campaign-catalog.mjs';
import {
getCampaign,
getLeadCommercialState,
listCampaigns,
seedCampaigns,
setLeadCommercialStage
} from '../src/outreach/campaign-store.mjs';
function insertLead(db) {
db.prepare("INSERT INTO leads (dedup_key, empresa) VALUES ('campaign-test-lead', 'Empresa Teste')").run();
return Number(db.prepare('SELECT id FROM leads').get().id);
}
test('catalogo fixa produtos, etapas e os tres grupos de segmentos aprovados', () => {
assert.deepEqual(CAMPAIGN_PRODUCTS, ['GTI Vendas', 'G-Delivery']);
assert.deepEqual(CAMPAIGN_STAGES, [
'fila_preparada',
'aprovado_para_envio',
'enviado',
'respondeu',
'interessado_gti_vendas',
'interessado_g_delivery',
'demonstracao_agendada',
'teste_g_delivery_iniciado',
'sem_interesse',
'opt_out',
'venda_fechada'
]);
assert.deepEqual(CAMPAIGN_DEFINITIONS.map(({ code, produto, segments }) => ({ code, produto, segments })), [
{
code: 'gdelivery-food',
produto: 'G-Delivery',
segments: ['hamburguerias', 'pizzarias', 'acaiterias', 'marmitarias', 'lanchonetes']
},
{
code: 'gti-food',
produto: 'GTI Vendas',
segments: ['restaurantes', 'mercados_de_bairro', 'mercearias', 'minimercados', 'padarias', 'conveniencias']
},
{
code: 'gti-retail',
produto: 'GTI Vendas',
segments: ['lojas_de_roupa', 'material_de_construcao', 'autopecas', 'oficinas_mecanicas']
}
]);
});
test('seed cria tres campanhas e nove variantes usando seis textos unicos', () => {
const db = openDb(':memory:');
seedCampaigns(db);
seedCampaigns(db);
const campaigns = listCampaigns(db);
assert.deepEqual(campaigns.map(({ code, produto }) => ({ code, produto })), [
{ code: 'gdelivery-food', produto: 'G-Delivery' },
{ code: 'gti-food', produto: 'GTI Vendas' },
{ code: 'gti-retail', produto: 'GTI Vendas' }
]);
assert.equal(db.prepare('SELECT COUNT(*) total FROM outreach_campaign_variants').get().total, 9);
assert.equal(db.prepare('SELECT COUNT(DISTINCT mensagem) total FROM outreach_campaign_variants').get().total, 6);
assert.deepEqual(campaigns.map(({ variants }) => variants.map(({ code }) => code)), [
['A', 'B', 'C'],
['A', 'B', 'C'],
['A', 'B', 'C']
]);
const gDeliveryMessages = {
A: '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? Se nao fizer sentido para sua empresa, e so me avisar que nao volto a entrar em contato.',
B: 'Ola! Aqui e Gustavo, do Grupo GTI. Voces ja possuem um link proprio para receber pedidos, sem pagar comissao por venda a aplicativos? Se nao fizer sentido para sua empresa, e so me avisar que nao volto a entrar em contato.',
C: '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. Se nao fizer sentido para sua empresa, e so me avisar que nao volto a entrar em contato.'
};
const gtiVendasMessages = {
A: 'Ola, tudo bem? Sou Gustavo, representante comercial do Grupo GTI. Hoje voces controlam {foco_gestao} em um unico sistema? Se nao fizer sentido para sua empresa, e so me avisar que nao volto a entrar em contato.',
B: '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}? Se nao fizer sentido para sua empresa, e so me avisar que nao volto a entrar em contato.',
C: '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}? Se nao fizer sentido para sua empresa, e so me avisar que nao volto a entrar em contato.'
};
assert.deepEqual(Object.fromEntries(campaigns[0].variants.map(({ code, mensagem }) => [code, mensagem])), gDeliveryMessages);
assert.deepEqual(Object.fromEntries(campaigns[1].variants.map(({ code, mensagem }) => [code, mensagem])), gtiVendasMessages);
assert.deepEqual(Object.fromEntries(campaigns[2].variants.map(({ code, mensagem }) => [code, mensagem])), gtiVendasMessages);
db.close();
});
test('seed completa ausencias sem sobrescrever controles editados pelo operador', () => {
const db = openDb(':memory:');
seedCampaigns(db);
const campaign = listCampaigns(db)[0];
db.prepare("UPDATE outreach_campaigns SET status = 'running', week_number = 3, approved_daily_limit = 15 WHERE id = ?")
.run(campaign.id);
db.prepare("DELETE FROM outreach_campaign_variants WHERE campaign_id = ? AND code = 'C'").run(campaign.id);
seedCampaigns(db);
const reloaded = getCampaign(db, campaign.id);
assert.equal(reloaded.status, 'running');
assert.equal(reloaded.week_number, 3);
assert.equal(reloaded.approved_daily_limit, 15);
assert.deepEqual(reloaded.variants.map(({ code }) => code), ['A', 'B', 'C']);
assert.equal(getCampaign(db, 999999), null);
db.close();
});
test('seed recria campanha excluida e preserva controles das campanhas sobreviventes', () => {
const db = openDb(':memory:');
seedCampaigns(db);
const removed = listCampaigns(db).find(({ code }) => code === 'gdelivery-food');
const survivor = listCampaigns(db).find(({ code }) => code === 'gti-food');
db.prepare("UPDATE outreach_campaigns SET status = 'running', week_number = 4, approved_daily_limit = 18 WHERE id = ?")
.run(survivor.id);
db.prepare('DELETE FROM outreach_campaigns WHERE id = ?').run(removed.id);
assert.equal(getCampaign(db, removed.id), null);
assert.equal(db.prepare('SELECT COUNT(*) total FROM outreach_campaign_variants WHERE campaign_id = ?').get(removed.id).total, 0);
seedCampaigns(db);
const restored = listCampaigns(db).find(({ code }) => code === 'gdelivery-food');
const unchanged = getCampaign(db, survivor.id);
assert.notEqual(restored.id, removed.id);
assert.deepEqual(restored.variants.map(({ code }) => code), ['A', 'B', 'C']);
assert.equal(unchanged.status, 'running');
assert.equal(unchanged.week_number, 4);
assert.equal(unchanged.approved_daily_limit, 18);
assert.equal(db.prepare('SELECT COUNT(*) total FROM outreach_campaigns').get().total, 3);
assert.equal(db.prepare('SELECT COUNT(*) total FROM outreach_campaign_variants').get().total, 9);
db.close();
});
test('mudanca de etapa atualiza estado comercial e registra historico', () => {
const db = openDb(':memory:');
seedCampaigns(db);
const leadId = insertLead(db);
const campaignId = listCampaigns(db)[0].id;
assert.equal(getLeadCommercialState(db, leadId), null);
const state = setLeadCommercialStage(db, {
leadId,
campaignId,
stage: 'respondeu',
note: 'Resposta recebida no WhatsApp'
});
assert.equal(state.lead_id, leadId);
assert.equal(state.stage, 'respondeu');
assert.deepEqual(db.prepare(
'SELECT lead_id, campaign_id, stage, note FROM outreach_stage_events'
).all().map((row) => ({ ...row })), [{
lead_id: leadId,
campaign_id: campaignId,
stage: 'respondeu',
note: 'Resposta recebida no WhatsApp'
}]);
assert.throws(() => setLeadCommercialStage(db, { leadId, stage: 'etapa_invalida' }), /etapa comercial invalida/i);
db.close();
});