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.
This commit is contained in:
Gustavo
2026-07-31 10:54:07 -03:00
parent 6cd336a41e
commit d330bb5ff0
132 changed files with 29969 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { openDb, saveWhatsAppVerification } from '../src/db.mjs';
import { seedCampaigns } from '../src/outreach/campaign-store.mjs';
import { runOutreachBatch } from '../src/outreach/run.mjs';
import { renderTemplate } from '../src/outreach/templates.mjs';
function addScheduledQueue(db, scheduledAt = '2026-07-29T12:00:00.000Z') {
seedCampaigns(db);
const campaign = db.prepare('SELECT * FROM outreach_campaigns ORDER BY id LIMIT 1').get();
db.prepare("UPDATE outreach_campaigns SET status = 'running' WHERE id = ?").run(campaign.id);
const leadId = Number(db.prepare(
`INSERT INTO leads (dedup_key, empresa, whatsapp, segmento, telefone_valido)
VALUES ('queue-lead', 'Empresa Fila', '5511999990001', 'restaurantes', 1)`
).run().lastInsertRowid);
saveWhatsAppVerification(db, { numero: '5511999990001', status: 'confirmado' });
const variantId = db.prepare(
'SELECT id FROM outreach_campaign_variants WHERE campaign_id = ? ORDER BY id LIMIT 1'
).get(campaign.id).id;
return Number(db.prepare(
`INSERT INTO outreach_campaign_queue
(campaign_id, lead_id, variant_id, produto, numero, segmento,
rendered_message, selection_reason, status, scheduled_at)
VALUES (?, ?, ?, ?, '5511999990001', 'restaurantes',
'Mensagem aprovada e imutavel', 'teste', 'scheduled', ?)`
).run(campaign.id, leadId, variantId, campaign.produto, scheduledAt).lastInsertRowid);
}
function whatsappFake() {
const sent = [];
return {
sent,
getStatus: () => ({ state: 'READY' }),
sendText: async (args) => {
const context = await args.beforeSend();
sent.push({ number: args.number, message: args.message });
const result = { messageId: 'queue-message-1' };
await args.afterSend(result, null, context);
return result;
}
};
}
test('renderTemplate inclui produto no contexto pessoal', () => {
assert.equal(renderTemplate('{empresa}: {produto}', { empresa: 'Loja', produto: 'ERP' }), 'Loja: ERP');
});
test('runOutreachBatch consome somente uma linha aprovada e vencida da fila', async () => {
const db = openDb(':memory:');
const queueId = addScheduledQueue(db);
const whatsapp = whatsappFake();
const result = await runOutreachBatch({
db,
whatsapp,
now: new Date('2026-07-29T12:00:00.000Z'),
log: () => {}
});
assert.deepEqual(result, { tentados: 1, enviados: 1, erros: 0, pulados: 0 });
assert.deepEqual(whatsapp.sent, [{
number: '5511999990001',
message: 'Mensagem aprovada e imutavel'
}]);
assert.equal(db.prepare('SELECT status FROM outreach_campaign_queue WHERE id = ?').get(queueId).status, 'sent');
assert.deepEqual(
{ ...db.prepare(
'SELECT mensagem_enviada, provider_message_id, status, canal, origem FROM outreach_sends'
).get() },
{
mensagem_enviada: 'Mensagem aprovada e imutavel',
provider_message_id: 'queue-message-1',
status: 'aceito',
canal: 'local',
origem: 'automatico'
}
);
db.close();
});
test('runOutreachBatch sem WhatsApp READY preserva fila e registra adiamento', async () => {
const db = openDb(':memory:');
const queueId = addScheduledQueue(db);
const logs = [];
let sends = 0;
const result = await runOutreachBatch({
db,
whatsapp: {
getStatus: () => ({ state: 'DISCONNECTED' }),
sendText: async () => { sends += 1; }
},
now: new Date('2026-07-29T12:00:00.000Z'),
log: (line) => logs.push(line)
});
assert.deepEqual(result, { tentados: 0, enviados: 0, erros: 0, pulados: 0 });
assert.equal(sends, 0);
assert.equal(db.prepare('SELECT status FROM outreach_campaign_queue WHERE id = ?').get(queueId).status, 'scheduled');
assert.match(logs[0], /nao esta conectado/i);
db.close();
});
test('runOutreachBatch nunca seleciona lead cru sem fila supervisionada', async () => {
const db = openDb(':memory:');
db.prepare(
`INSERT INTO leads
(dedup_key, empresa, whatsapp, segmento, telefone_valido, produto_principal)
VALUES ('raw-lead', 'Lead Cru', '5511999990002', 'restaurantes', 1, 'G-Delivery')`
).run();
saveWhatsAppVerification(db, { numero: '5511999990002', status: 'confirmado' });
const whatsapp = whatsappFake();
const result = await runOutreachBatch({
db,
whatsapp,
now: new Date('2026-07-29T12:00:00.000Z'),
log: () => {}
});
assert.deepEqual(result, { tentados: 0, enviados: 0, erros: 0, pulados: 0 });
assert.equal(whatsapp.sent.length, 0);
assert.equal(db.prepare('SELECT COUNT(*) AS total FROM outreach_sends').get().total, 0);
db.close();
});
test('runOutreachBatch respeita cancelamento antes da reserva', async () => {
const db = openDb(':memory:');
const queueId = addScheduledQueue(db);
const whatsapp = whatsappFake();
const controller = new AbortController();
controller.abort();
const result = await runOutreachBatch({
db,
whatsapp,
now: new Date('2026-07-29T12:00:00.000Z'),
signal: controller.signal,
log: () => {}
});
assert.deepEqual(result, { tentados: 0, enviados: 0, erros: 0, pulados: 0 });
assert.equal(db.prepare('SELECT status FROM outreach_campaign_queue WHERE id = ?').get(queueId).status, 'scheduled');
assert.equal(whatsapp.sent.length, 0);
db.close();
});