App local (Node + SQLite) para captura, tratamento e exportacao de leads comerciais, com dashboard de gestao, login/usuarios e integracao Z-PRO. Co-Authored-By: Claude <noreply@anthropic.com>
120 lines
4.8 KiB
JavaScript
120 lines
4.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import os from 'node:os';
|
|
import { openDb, createLeadInserter, seedSettings } from '../src/db.mjs';
|
|
import { exportExcel } from '../src/export/excel.mjs';
|
|
import { exportOportunidades, exportBulkDataInput } from '../src/export/zpro.mjs';
|
|
import { stripBom } from '../src/util/csv.mjs';
|
|
|
|
function seedLeads(db) {
|
|
const insert = createLeadInserter(db);
|
|
insert({
|
|
lead_uid: 'GTI-1', dedup_key: 'cnpj:57557263000113', empresa: 'PREMIUM EXPRESS',
|
|
legal_name: 'PREMIUM EXPRESS LTDA', whatsapp: '557193335451', ddd: '71', email: '',
|
|
cidade: 'Salvador', uf: 'BA', segmento: 'restaurantes', segmento_label: 'Restaurantes',
|
|
interesse: 'Autoatendimento para WhatsApp', cnpj: '57557263000113', cnae: '5611203',
|
|
endereco: '', website: '', pains_json: '[]', origem: 'cnpj-base',
|
|
telefone_valido: 1, cnpj_valido: 1, empresa_valida: 1, score: 100,
|
|
classificacao: 'bom', motivo_classificacao: 'ok', capture_run_id: null,
|
|
captured_at: null, exported_at: null
|
|
});
|
|
insert({
|
|
lead_uid: 'GTI-2', dedup_key: 'name:sem telefone:salvador', empresa: 'SEM TELEFONE',
|
|
legal_name: '', whatsapp: '', ddd: '', email: '', cidade: 'Salvador', uf: 'BA',
|
|
segmento: 'restaurantes', segmento_label: 'Restaurantes', interesse: '', cnpj: '',
|
|
cnae: '', endereco: '', website: '', pains_json: '[]', origem: 'cnpj-base',
|
|
telefone_valido: 0, cnpj_valido: 0, empresa_valida: 1, score: 35,
|
|
classificacao: 'ruim', motivo_classificacao: 'score baixo', capture_run_id: null,
|
|
captured_at: null, exported_at: null
|
|
});
|
|
}
|
|
|
|
async function withTempDir(fn) {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gti-export-'));
|
|
try {
|
|
return await fn(dir);
|
|
} finally {
|
|
await fs.rm(dir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
test('exportExcel gera CSV com formulas de texto e xls HTML', async () => {
|
|
const db = openDb(':memory:');
|
|
seedLeads(db);
|
|
|
|
await withTempDir(async (dir) => {
|
|
const ids = [];
|
|
const result = await exportExcel({ db, filtro: { uf: 'BA' }, dir, baseName: 'teste', collectIds: ids });
|
|
|
|
assert.equal(result.total, 2);
|
|
assert.deepEqual(result.files, ['teste.csv', 'teste.xls']);
|
|
assert.equal(ids.length, 2);
|
|
|
|
const csv = stripBom(await fs.readFile(path.join(dir, 'teste.csv'), 'utf8'));
|
|
const lines = csv.trim().split('\n');
|
|
assert.ok(lines[0].startsWith('empresa;whatsapp;ddd;cidade;uf;segmento;segmento_legivel'));
|
|
assert.ok(lines[1].includes('"=""557193335451"""'), 'whatsapp deve ser formula de texto');
|
|
assert.ok(lines[1].includes('"=""57557263000113"""'), 'cnpj deve ser formula de texto');
|
|
assert.ok(lines[1].includes('cnpj:57557263000113'), 'chave_duplicidade presente');
|
|
|
|
const xls = await fs.readFile(path.join(dir, 'teste.xls'), 'utf8');
|
|
assert.ok(xls.includes('mso-number-format'));
|
|
assert.ok(xls.includes('<td class="text">557193335451</td>'));
|
|
});
|
|
|
|
db.close();
|
|
});
|
|
|
|
test('exportOportunidades gera JSONL no formato Z-PRO testado', async () => {
|
|
const db = openDb(':memory:');
|
|
seedSettings(db);
|
|
seedLeads(db);
|
|
|
|
await withTempDir(async (dir) => {
|
|
const result = await exportOportunidades({
|
|
db, filtro: { classificacao: 'bom' }, params: {}, dir, baseName: 'op'
|
|
});
|
|
|
|
assert.equal(result.total, 1);
|
|
const lines = (await fs.readFile(path.join(dir, 'op.jsonl'), 'utf8')).trim().split('\n');
|
|
const payload = JSON.parse(lines[0]);
|
|
|
|
assert.equal(payload.number, '557193335451');
|
|
assert.equal(payload.contactName, 'PREMIUM EXPRESS');
|
|
assert.equal(payload.name, 'PREMIUM EXPRESS - Autoatendimento para WhatsApp');
|
|
assert.equal(payload.pipelineId, 1);
|
|
assert.equal(payload.stageId, 1);
|
|
assert.equal(payload.responsibleId, 15);
|
|
assert.equal(payload.status, 'open');
|
|
assert.equal(payload.validateNumber, true);
|
|
assert.match(payload.closingForecast, /^\d{4}-\d{2}-\d{2}$/);
|
|
assert.ok(payload.description.includes('CNPJ: 57557263000113'));
|
|
});
|
|
|
|
db.close();
|
|
});
|
|
|
|
test('exportBulkDataInput inclui apenas leads com whatsapp', async () => {
|
|
const db = openDb(':memory:');
|
|
seedSettings(db);
|
|
seedLeads(db);
|
|
|
|
await withTempDir(async (dir) => {
|
|
const result = await exportBulkDataInput({
|
|
db, filtro: {}, params: { mensagem: 'Ola {empresa}!' }, dir, baseName: 'bulk'
|
|
});
|
|
|
|
assert.equal(result.total, 1, 'lead sem whatsapp deve ficar de fora');
|
|
const csv = (await fs.readFile(path.join(dir, 'bulk-datainput.csv'), 'utf8')).trim();
|
|
assert.equal(csv, '557193335451,PREMIUM EXPRESS,Salvador,Restaurantes');
|
|
|
|
const info = await fs.readFile(path.join(dir, 'bulk-instrucoes.txt'), 'utf8');
|
|
assert.ok(info.includes('Ola {empresa}!'));
|
|
assert.ok(info.includes('bulkSendMessageWithVariable'));
|
|
});
|
|
|
|
db.close();
|
|
});
|