export function normalizePhone(value = '') { return String(value).replace(/\D/g, ''); } export function equivalentPhoneNumbers(value = '') { const digits = normalizePhone(value); const numbers = digits ? [digits] : []; if (!digits.startsWith('55')) return numbers; const prefix = digits.slice(0, 4); const local = digits.slice(4); if (local.length === 9 && local.startsWith('9')) numbers.push(`${prefix}${local.slice(1)}`); if (local.length === 8 && /^[6-9]/.test(local)) numbers.push(`${prefix}9${local}`); return [...new Set(numbers)]; } // equivalentPhoneNumbers NAO e simetrica: ela so gera a variante de 8 digitos a // partir de um celular de 9 digitos quando o local comeca com 6-9. Um numero // como 557533334444 (local '33334444') nao volta para 5575933334444, embora o // caminho inverso exista — o que fazia um lado do par ser bloqueado e o outro // nao, e escondia o grupo da tela de revisao. // // canonicalPhoneKey resolve isso reduzindo SEMPRE o celular brasileiro de 13 // digitos com nono digito para a forma de 12 digitos (mesma regra de remocao ja // usada por equivalentPhoneNumbers). Dois numeros pertencem a mesma linha // quando compartilham a chave canonica — uma relacao simetrica por construcao. export function canonicalPhoneKey(value = '') { const digits = normalizePhone(value); if (!digits.startsWith('55')) return digits; const prefix = digits.slice(0, 4); const local = digits.slice(4); if (local.length === 9 && local.startsWith('9')) return `${prefix}${local.slice(1)}`; return digits; } // Todas as formas que um numero pode ter armazenado e que reduzem a mesma chave // canonica. O numero informado vem sempre primeiro (o override guarda esse // valor). Usado para casar contra a coluna crua `leads.whatsapp`. export function canonicalPhoneVariants(value = '') { const digits = normalizePhone(value); if (!digits) return []; const key = canonicalPhoneKey(digits); const variants = new Set([digits, key]); if (key.startsWith('55') && key.length === 12) { variants.add(`${key.slice(0, 4)}9${key.slice(4)}`); } return [...variants]; } // Fragmentos SQL equivalentes as funcoes JS acima. Ficam neste modulo para que // exista uma unica origem da regra 8/9 digitos — tests/phone.test.mjs prova que // as duas implementacoes concordam. export function normalizedSql(column) { return `replace(replace(replace(replace(replace(${column}, '+', ''), ' ', ''), '-', ''), '(', ''), ')', '')`; } // Espelha equivalentPhoneNumbers: alterna entre as duas variantes brasileiras. export function equivalentSql(column) { const number = normalizedSql(column); return `CASE WHEN substr(${number}, 1, 2) = '55' AND length(${number}) = 13 AND substr(${number}, 5, 1) = '9' THEN substr(${number}, 1, 4) || substr(${number}, 6) WHEN substr(${number}, 1, 2) = '55' AND length(${number}) = 12 AND substr(${number}, 5, 1) BETWEEN '6' AND '9' THEN substr(${number}, 1, 4) || '9' || substr(${number}, 5) ELSE ${number} END`; } // Espelha canonicalPhoneKey. export function canonicalKeySql(column) { const number = normalizedSql(column); return `CASE WHEN substr(${number}, 1, 2) = '55' AND length(${number}) = 13 AND substr(${number}, 5, 1) = '9' THEN substr(${number}, 1, 4) || substr(${number}, 6) ELSE ${number} END`; } // A outra forma da mesma chave canonica (a de 13 digitos). Junto com // canonicalKeySql cobre as duas formas possiveis de armazenamento, permitindo // `outra.whatsapp IN (chave, variante)` — comparacao contra a coluna crua, que // mantem o uso de idx_leads_whatsapp. export function canonicalVariantSql(column) { const number = normalizedSql(column); return `CASE WHEN substr(${number}, 1, 2) = '55' AND length(${number}) = 13 AND substr(${number}, 5, 1) = '9' THEN ${number} WHEN substr(${number}, 1, 2) = '55' AND length(${number}) = 12 THEN substr(${number}, 1, 4) || '9' || substr(${number}, 5) ELSE ${number} END`; }