From 3d2144eefc026a6bcaf3a6f7671d24304da93e1f Mon Sep 17 00:00:00 2001 From: Gustavo Date: Fri, 31 Jul 2026 11:11:54 -0300 Subject: [PATCH] Block manual send reservation for unreleased shared numbers Implement shared-number detection in reserveOutreachSend: reject manual WhatsApp sends with HTTP 409 when the destination number is shared across 2+ leads and hasn't been explicitly released via override. - Add test: reserveOutreachSend bloqueia numero compartilhado sem override e libera com override - Add check in reserveOutreachSend right after isOptedOut check - Uses isNumberSharedAcrossLeads and isSharedNumberOverridden from Task 1 Co-Authored-By: Claude Sonnet 5 --- src/db.mjs | 5 +++++ tests/db.test.mjs | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/db.mjs b/src/db.mjs index b1f7f6e..9c363b1 100644 --- a/src/db.mjs +++ b/src/db.mjs @@ -1127,6 +1127,11 @@ export function reserveOutreachSend(db, { error.statusCode = 409; throw error; } + if (isNumberSharedAcrossLeads(db, normalized) && !isSharedNumberOverridden(db, normalized)) { + const error = new Error('numero compartilhado por multiplos leads (revise em Configuracoes antes de enviar)'); + error.statusCode = 409; + throw error; + } const limit = Number(getSetting(db, 'outreach.mensagensPorDia', '10')) || 0; const used = countDailyOutreachUsage(db, { origem: 'manual' }); if (used >= limit) { diff --git a/tests/db.test.mjs b/tests/db.test.mjs index 51e01ec..890d337 100644 --- a/tests/db.test.mjs +++ b/tests/db.test.mjs @@ -501,6 +501,23 @@ test('limite manual e uso publicado compartilham origem e status exatos', () => db.close(); }); +test('reserveOutreachSend bloqueia numero compartilhado sem override e libera com override', () => { + const db = openDb(':memory:'); + const insert = createLeadInserter(db); + insert(sampleLead({ dedup_key: 'shared-a', empresa: 'Empresa A', whatsapp: '5511999990009' })); + insert(sampleLead({ dedup_key: 'shared-b', empresa: 'Empresa B', whatsapp: '5511999990009' })); + + assert.throws( + () => reserveOutreachSend(db, { numero: '5511999990009', mensagem: 'oi' }), + (error) => error.statusCode === 409 + ); + + database.addSharedNumberOverride(db, '5511999990009', 'liberado'); + const sendId = reserveOutreachSend(db, { numero: '5511999990009', mensagem: 'oi' }); + assert.ok(sendId > 0); + db.close(); +}); + test('cache de verificacao salva somente o numero solicitado e consulta equivalentes', () => { const db = openDb(':memory:'); const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map((row) => row.name);