Files
COMERCIAL-GTI/tests/campaign-store.test.mjs
T
Gustavo d330bb5ff0 Baseline commit of the existing Maquina Comercial Pessoal project
Tracks the app that was already in place (capture, treatment, outreach,
WhatsApp integration, server, frontend, tests, docs) so future feature
work can branch and use isolated worktrees.
2026-07-31 10:54:07 -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();
});