The real manual-send route (routes/outreach.mjs) always inserts with
produto='' and lead_id=NULL, so the old single NOT EXISTS predicate
(produto = ? AND (lead_id = leads.id OR numero = leads.whatsapp)) could
never match a real manual send's row -- the numero branch was unreachable
in production even though a prior test fabricated a produto value manual
sends never write. Split into two NOT EXISTS clauses: one scoped to
produto+lead_id (automatic/campaign sends), one scoped to numero+
origem='manual' with no produto requirement (manual sends). Updated the
existing regression test to insert the row exactly like the real route
does (produto: '', lead_id: NULL, origem: 'manual').
Marking a lead opt_out via /estagio only wrote outreach_lead_states/
outreach_stage_events and never called addOptOut, so the lead stayed fully
eligible for future campaign sends. Also normalize numero with onlyDigits
in POST/DELETE opt-out routes and in the estagio lookup, matching how every
other number in the app (leads.whatsapp, queue.numero) is stored, so opt-out
comparisons never silently miss due to formatting differences.
generateCampaignQueue computed candidatosEncontrados after slicing to
quantidade, so it could never differ from adicionadosNaFila. An operator
generating a queue with quantidade smaller than the eligible pool saw no
signal that more eligible leads existed. Now candidatosEncontrados is read
before the slice, while adicionadosNaFila still reflects rows actually
inserted.
Previously, setCampaignStatus did not set semana_iniciada_em, causing
escalateWeeklyLimits to bail early (if (!campaign.semana_iniciada_em) continue).
Now when a campaign transitions to 'rodando' for the first time, semana_iniciada_em
is set to datetime('now'). Subsequent rodando transitions leave it unchanged
(using COALESCE to preserve first-run timestamp).
Regression tests confirm:
- First transition to 'rodando' stamps semana_iniciada_em
- Subsequent transitions do not reset the timestamp
- resumeCampaign continues to work correctly (pause/resume cycle)
- All 105 tests pass
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This work already existed uncommitted in the main working tree (not
authored by this branch) and is required as a runtime dependency for the
campaign outreach port (zpro-client, templates, outreach routes/UI). Copied
in as-is, untouched, so this feature branch has a working baseline; the
original uncommitted state in the main checkout is left exactly as it was.
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>