Fix final-review findings: last-mile shared-number recheck, symmetric canonical key, per-row treatment query, JS/SQL agreement test

The last-mile validator in campaign-worker.mjs revalidated opt-out/inbound/
dedupe right before sending but never rechecked shared-number state, so
re-blocking a released number after scheduling didn't stop the send.

equivalentPhoneNumbers isn't symmetric for all 8/9-digit pairs, which made
isNumberSharedAcrossLeads and listSharedNumberGroups disagree for a 12-digit
local outside the 6-9 range vs its 13-digit counterpart: one side of the pair
could be enforcement-blocked while invisible and unreleasable in the review
screen. A new canonicalPhoneKey/canonicalPhoneVariants pair in phone.mjs
(plus SQL mirrors) makes the matching relation symmetric by construction.

listSharedNumberGroups did a full leads-table scan on every listLeads/getLead
call, the app's primary paginated screen. Replaced with per-row correlated
subqueries against idx_leads_whatsapp so cost tracks the page, not the table.

normalizedSql/equivalentSql (SQL) duplicated equivalentPhoneNumbers (JS) with
no test proving they agreed; moved into phone.mjs alongside their canonical
counterparts and added tests/phone.test.mjs asserting agreement across a
shared number set, including the asymmetric case.
This commit is contained in:
Gustavo
2026-07-31 20:31:31 -03:00
parent cb03597435
commit afa350de5e
10 changed files with 573 additions and 52 deletions
+31
View File
@@ -576,6 +576,37 @@ test('numero compartilhado por mais de um lead fica fora da fila ate liberacao m
db.close();
});
// 557533334444 vs 5575933334444: equivalentPhoneNumbers so relaciona o segundo
// com o primeiro. Os dois precisam ficar fora da fila.
test('par 12/13 digitos assimetrico fica fora da fila nos dois sentidos', async () => {
const { db, campaign } = setupCampaign();
const doze = insertLead(db, { key: 'assim-12', whatsapp: '557533334444', score: 100 });
const treze = insertLead(db, { key: 'assim-13', whatsapp: '5575933334444', score: 99 });
const solo = insertLead(db, { key: 'assim-solo', whatsapp: '5575992220000', score: 80 });
for (const numero of ['557533334444', '5575933334444', '5575992220000']) confirmed(db, numero);
await prepareDailyQueue({
db,
whatsapp: noSendWhatsapp(async () => { throw new Error('todos os numeros possuem cache'); }),
campaignId: campaign.id,
now: new Date('2026-07-29T14:00:00-03:00')
});
assert.deepEqual(listCampaignQueue(db, campaign.id).map(({ lead_id }) => lead_id), [solo]);
addSharedNumberOverride(db, '557533334444', 'contabilidade confirmada');
await prepareDailyQueue({
db,
whatsapp: noSendWhatsapp(async () => { throw new Error('todos os numeros possuem cache'); }),
campaignId: campaign.id,
now: new Date('2026-07-29T14:00:00-03:00')
});
assert.deepEqual(
listCampaignQueue(db, campaign.id).map(({ lead_id }) => lead_id).sort((a, b) => a - b),
[doze, treze, solo].sort((a, b) => a - b)
);
db.close();
});
test('variantes 8 e 9 digitos do mesmo numero contam como compartilhado na fila', async () => {
const { db, campaign } = setupCampaign();
const nineDigits = insertLead(db, { key: 'nine-digits', whatsapp: '5575999091780', score: 100 });