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(); });