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
+123
View File
@@ -0,0 +1,123 @@
import { loadDotEnv } from '../src/env.mjs';
import { openDb, ensureSeeds } from '../src/db.mjs';
import { runCapture, createCaptureRun } from '../src/capture/runner.mjs';
import { describeSources } from '../src/sources/index.mjs';
import { randomUUID } from 'node:crypto';
import { createJobLease } from '../src/jobs/lease.mjs';
// CLI de captura (uso avancado / testes). A interface principal e o app:
// node scripts/servidor.mjs (ou o Maquina-Comercial-Pessoal.exe)
//
// Exemplos:
// node scripts/capturar-leads.mjs --sources=mock
// node scripts/capturar-leads.mjs --sources=cnpj-base --uf=BA --cities="Santo Antonio de Jesus"
// node scripts/capturar-leads.mjs --sources=cnpj-base,overpass --uf=BA --cities=all --segments=all
await loadDotEnv();
const args = new Map(
process.argv.slice(2).map((arg) => {
const [key, value = 'true'] = arg.replace(/^--/, '').split('=');
return [key, value];
})
);
function usage() {
const fontes = describeSources().map((s) => ` ${s.id} — ${s.label}${s.disponivel ? '' : ` (indisponivel: ${s.motivo})`}`);
return [
'Uso: node scripts/capturar-leads.mjs --sources=<ids> [opcoes]',
'',
'Opcoes:',
' --sources=cnpj-base,overpass fontes separadas por virgula (ou "mock")',
' --uf=BA estado alvo',
' --cities=all | "A,B,C" cidades (all = todas as cidades da UF na base)',
' --segments=all | "a,b,c" nichos pelo nome (all = todos os 57)',
' --segment-limit=N usa so os N primeiros nichos',
' --city-limit=N usa so as N primeiras cidades',
' --max=10 max de resultados por consulta (fontes de API)',
' --query-limit=0 max total de consultas (0 = sem limite)',
'',
'Fontes:',
...fontes
].join('\n');
}
async function main() {
if (args.has('help')) {
console.log(usage());
return;
}
const db = openDb();
await ensureSeeds(db, { log: (m) => console.log(`[captura] ${m}`) });
const uf = (args.get('uf') || 'BA').toUpperCase();
const sources = (args.get('sources') || 'mock').split(',').map((s) => s.trim()).filter(Boolean);
let segments = db.prepare('SELECT * FROM segments WHERE active = 1 ORDER BY id').all().map((row) => ({
name: row.name,
label: row.label,
interest: row.interest,
queries: JSON.parse(row.queries_json),
cnaeCodes: JSON.parse(row.cnae_codes_json),
overpassTags: JSON.parse(row.overpass_tags_json),
pains: JSON.parse(row.pains_json)
}));
const segmentsArg = args.get('segments');
if (segmentsArg && segmentsArg !== 'all') {
const wanted = new Set(segmentsArg.split(',').map((s) => s.trim()));
segments = segments.filter((s) => wanted.has(s.name));
}
const segmentLimit = Number(args.get('segment-limit') || 0);
if (segmentLimit > 0) segments = segments.slice(0, segmentLimit);
let cities;
const citiesArg = args.get('cities') || 'all';
if (citiesArg === 'all') {
cities = db.prepare('SELECT nome FROM municipios WHERE uf = ? ORDER BY nome').all(uf).map((r) => r.nome);
} else {
cities = citiesArg.split(',').map((c) => c.trim()).filter(Boolean);
}
const cityLimit = Number(args.get('city-limit') || 0);
if (cityLimit > 0) cities = cities.slice(0, cityLimit);
const params = {
sources,
uf,
cities,
segments,
limits: {
maxPerQuery: Number(args.get('max') || 10),
queryLimit: Number(args.get('query-limit') || 0),
googlePages: Number(args.get('google-pages') || 3)
}
};
console.log(`[captura] fontes: ${sources.join(', ')} | UF: ${uf} | cidades: ${cities.length} | nichos: ${segments.length}`);
const active = db.prepare("SELECT id FROM capture_runs WHERE status IN ('pending','running')").get();
if (active) throw new Error(`ja existe uma captura em andamento (#${active.id})`);
const runId = createCaptureRun(db, { ...params, segments: segments.map((s) => s.name) });
const lease = createJobLease({ db, table: 'capture_runs', id: runId, ownerToken: randomUUID() });
let summary;
try {
summary = await runCapture({
db,
runId,
params,
emit: (p) => console.log(`[captura] ${p.phase}: ${p.message || ''}`),
log: (m) => console.log(`[captura] ${m}`)
});
} finally {
lease.close();
}
console.log(`[captura] resultado: ${summary.raw} brutos, ${summary.new} novos, ${summary.dup} duplicados (${summary.status})`);
db.close();
}
main().catch((error) => {
console.error(`[captura] erro: ${error.stack || error.message}`);
process.exit(1);
});