import test from 'node:test'; import assert from 'node:assert/strict'; import { openDb, createLeadInserter } from '../src/db.mjs'; import { createCampaign, setCampaignStatus } from '../src/outreach/campaign-store.mjs'; import { evaluateCampaignHealth, variantStats } from '../src/outreach/campaign-metrics.mjs'; function inserirLead(db, dedup) { const inserir = createLeadInserter(db); inserir({ dedup_key: dedup, empresa: 'E', legal_name: '', whatsapp: `55119999${dedup.padStart(5, '0')}`, ddd: '11', email: '', cidade: '', uf: '', segmento: 'x', segmento_label: '', interesse: '', cnpj: '', cnae: '', endereco: '', website: '', pains_json: '[]', origem: 't', telefone_valido: 1, cnpj_valido: 0, empresa_valida: 1, score: 50, classificacao: 'bom', motivo_classificacao: '', score_qualidade: 50, score_comercial: 50, prioridade: 1, produto_principal: 'ERP', produtos_json: '[]', capture_run_id: null, captured_at: null, exported_at: null }); return db.prepare('SELECT id, whatsapp FROM leads WHERE dedup_key = ?').get(dedup); } function campanhaRodando(db) { const campanha = createCampaign(db, { nome: 'C', produto: 'ERP', segmentos: ['x'], variantes: [{ code: 'A', mensagem: 'oi' }] }); setCampaignStatus(db, campanha.id, 'rodando'); return campanha; } function simularEnvio(db, campanhaId, variantId, leadId, numero, status) { const sendId = db.prepare( `INSERT INTO outreach_sends (lead_id, produto, numero, mensagem_enviada, status) VALUES (?, 'ERP', ?, 'oi', ?)` ).run(leadId, numero, status).lastInsertRowid; db.prepare( `INSERT INTO outreach_campaign_queue (campaign_id, lead_id, variant_id, numero, rendered_message, status, outreach_send_id) VALUES (?, ?, ?, ?, 'oi', ?, ?)` ).run(campanhaId, leadId, variantId, numero, status === 'erro' ? 'error' : 'sent', sendId); } test('evaluateCampaignHealth nao pausa campanha saudavel', () => { const db = openDb(':memory:'); const campanha = campanhaRodando(db); const variant = db.prepare('SELECT id FROM outreach_campaign_variants WHERE campaign_id = ?').get(campanha.id); for (let i = 0; i < 10; i += 1) { const lead = inserirLead(db, `l${i}`); simularEnvio(db, campanha.id, variant.id, lead.id, lead.whatsapp, 'enviado'); } const resultado = evaluateCampaignHealth(db, campanha.id); assert.equal(resultado.pause, false); db.close(); }); test('evaluateCampaignHealth pausa por high_failure_rate quando erro > 10% com 10+ tentativas', () => { const db = openDb(':memory:'); const campanha = campanhaRodando(db); const variant = db.prepare('SELECT id FROM outreach_campaign_variants WHERE campaign_id = ?').get(campanha.id); for (let i = 0; i < 8; i += 1) { const lead = inserirLead(db, `ok${i}`); simularEnvio(db, campanha.id, variant.id, lead.id, lead.whatsapp, 'enviado'); } for (let i = 0; i < 2; i += 1) { const lead = inserirLead(db, `err${i}`); simularEnvio(db, campanha.id, variant.id, lead.id, lead.whatsapp, 'erro'); } const resultado = evaluateCampaignHealth(db, campanha.id); assert.equal(resultado.pause, true); assert.equal(resultado.reason, 'high_failure_rate'); const atualizada = db.prepare('SELECT status FROM outreach_campaigns WHERE id = ?').get(campanha.id); assert.equal(atualizada.status, 'pausado'); db.close(); }); test('evaluateCampaignHealth nao pausa por falha com menos de 10 tentativas no dia', () => { const db = openDb(':memory:'); const campanha = campanhaRodando(db); const variant = db.prepare('SELECT id FROM outreach_campaign_variants WHERE campaign_id = ?').get(campanha.id); const lead = inserirLead(db, 'so1'); simularEnvio(db, campanha.id, variant.id, lead.id, lead.whatsapp, 'erro'); const resultado = evaluateCampaignHealth(db, campanha.id); assert.equal(resultado.pause, false); db.close(); }); test('evaluateCampaignHealth pausa por high_negative_rate quando >10% dos 20+ contatados estao em sem_interesse/opt_out', () => { const db = openDb(':memory:'); const campanha = campanhaRodando(db); const variant = db.prepare('SELECT id FROM outreach_campaign_variants WHERE campaign_id = ?').get(campanha.id); for (let i = 0; i < 20; i += 1) { const lead = inserirLead(db, `neg${i}`); simularEnvio(db, campanha.id, variant.id, lead.id, lead.whatsapp, 'enviado'); if (i < 3) { db.prepare(`INSERT INTO outreach_lead_states (lead_id, stage) VALUES (?, 'sem_interesse')`).run(lead.id); } } const resultado = evaluateCampaignHealth(db, campanha.id); assert.equal(resultado.pause, true); assert.equal(resultado.reason, 'high_negative_rate'); db.close(); }); test('evaluateCampaignHealth pausa por low_reply_rate quando <5% dos 50+ contatados responderam', () => { const db = openDb(':memory:'); const campanha = campanhaRodando(db); const variant = db.prepare('SELECT id FROM outreach_campaign_variants WHERE campaign_id = ?').get(campanha.id); for (let i = 0; i < 50; i += 1) { const lead = inserirLead(db, `rep${i}`); simularEnvio(db, campanha.id, variant.id, lead.id, lead.whatsapp, 'enviado'); } const resultado = evaluateCampaignHealth(db, campanha.id); assert.equal(resultado.pause, true); assert.equal(resultado.reason, 'low_reply_rate'); db.close(); }); test('evaluateCampaignHealth em campanha ja pausada nao faz nada', () => { const db = openDb(':memory:'); const campanha = campanhaRodando(db); setCampaignStatus(db, campanha.id, 'pausado', 'manual'); const resultado = evaluateCampaignHealth(db, campanha.id); assert.equal(resultado.pause, false); db.close(); }); test('variantStats calcula taxa de resposta e elegibilidade (limiar 40 envios)', () => { const db = openDb(':memory:'); const campanha = campanhaRodando(db); const variant = db.prepare('SELECT id FROM outreach_campaign_variants WHERE campaign_id = ?').get(campanha.id); for (let i = 0; i < 5; i += 1) { const lead = inserirLead(db, `v${i}`); simularEnvio(db, campanha.id, variant.id, lead.id, lead.whatsapp, 'enviado'); if (i < 1) { db.prepare(`INSERT INTO outreach_stage_events (lead_id, campaign_id, stage) VALUES (?, ?, 'interessado')`).run(lead.id, campanha.id); } } const stats = variantStats(db, campanha.id); assert.equal(stats.length, 1); assert.equal(stats[0].enviados, 5); assert.equal(stats[0].respostas, 1); assert.equal(stats[0].taxaResposta, 0.2); assert.equal(stats[0].elegivelParaComparacao, false); db.close(); });