Números compartilhados entre leads
Detectados automaticamente quando o mesmo número aparece em mais de um lead (ex.: contabilidade que registra o próprio telefone para clientes). Envio automático e manual ficam bloqueados até você revisar e liberar.
```
- [ ] **Step 6: Add the app.js logic**
In `public/js/app.js`, right after the existing `removerOptOut` function and before the `el('btn-optout-add').addEventListener(...)` block (around line 262-270):
```js
async function removerOptOut(numero) {
try {
await API.delete(`/api/outreach/opt-outs/${encodeURIComponent(numero)}`);
el('optout-status').textContent = 'Número removido da lista.';
await carregarOptOuts();
} catch (error) { el('optout-status').textContent = `Erro: ${error.message}`; }
}
async function carregarNumerosCompartilhados() {
const { groups } = await API.get('/api/outreach/shared-numbers');
const list = el('shared-numbers-list');
if (!groups.length) {
list.replaceChildren(h('p', { class: 'muted' }, 'Nenhum número compartilhado detectado.'));
return;
}
list.replaceChildren(...groups.map((group) => {
const empresas = group.leads.map((lead) => lead.empresa).join(' · ');
if (group.liberado) {
let blockButton;
blockButton = h('button', {
class: 'btn-danger btn-sm', type: 'button',
onclick: () => comCarregando(blockButton, () => bloquearNumeroCompartilhado(group.numero))
}, 'Bloquear de novo');
return h('div', { class: 'item opt-out-item' },
h('div', {},
h('strong', {}, group.numero),
h('div', { class: 'sub' }, `${group.leads.length} leads: ${empresas}`),
h('div', { class: 'sub' }, `Liberado: ${group.motivo || 'sem motivo'}`)),
blockButton);
}
const motivoInput = h('input', { type: 'text', placeholder: 'Motivo da liberação' });
let liberarButton;
liberarButton = h('button', {
class: 'btn btn-sm', type: 'button',
onclick: () => comCarregando(liberarButton, () => liberarNumeroCompartilhado(group.numero, motivoInput.value.trim()))
}, 'Liberar');
return h('div', { class: 'item opt-out-item' },
h('div', {},
h('strong', {}, group.numero),
h('div', { class: 'sub' }, `${group.leads.length} leads: ${empresas}`)),
motivoInput, liberarButton);
}));
}
async function liberarNumeroCompartilhado(numero, motivo) {
if (!motivo) {
el('shared-numbers-status').textContent = 'Informe um motivo para liberar.';
return;
}
try {
await API.post(`/api/outreach/shared-numbers/${encodeURIComponent(numero)}/liberar`, { motivo });
el('shared-numbers-status').textContent = 'Número liberado.';
await carregarNumerosCompartilhados();
} catch (error) { el('shared-numbers-status').textContent = `Erro: ${error.message}`; }
}
async function bloquearNumeroCompartilhado(numero) {
try {
await API.delete(`/api/outreach/shared-numbers/${encodeURIComponent(numero)}/liberar`);
el('shared-numbers-status').textContent = 'Número bloqueado novamente.';
await carregarNumerosCompartilhados();
} catch (error) { el('shared-numbers-status').textContent = `Erro: ${error.message}`; }
}
el('btn-optout-add').addEventListener('click', async () => {
```
Finally, wire the loader into the settings-modal open handler. It currently reads (around line 291):
```js
await Promise.all([carregarOutreach(), carregarOptOuts(), refreshWhatsAppStatus(false, generation)]);
```
Change to:
```js
await Promise.all([carregarOutreach(), carregarOptOuts(), carregarNumerosCompartilhados(), refreshWhatsAppStatus(false, generation)]);
```
- [ ] **Step 7: Run test to verify it passes**
Run: `node --test tests/personal-branding.test.mjs`
Expected: PASS
- [ ] **Step 8: Commit**
```bash
git add public/index.html public/js/app.js tests/campaign-ui.test.mjs tests/personal-branding.test.mjs
git commit -m "Add Configuracoes review section for shared numbers"
```
---
### Task 8: Full suite, README note, final check
**Files:**
- Modify: `README.md:37-44` (one bullet in "Automação e segurança comercial")
- [ ] **Step 1: Add the README bullet**
The section currently reads (lines 39-44):
```markdown
- A automação inicia desligada e deve ser ativada conscientemente pelo administrador.
- Configure um limite diário baixo e mantenha os intervalos de envio.
- Faça contato apenas em horários adequados e com uma finalidade legítima.
- Adicione imediatamente à lista de bloqueio quem pedir para não receber mensagens.
- Não faça spam, disparos indiscriminados nem compre listas de contatos.
- A conexão usa integração não oficial por dispositivo vinculado. Mudanças no WhatsApp podem interromper o funcionamento e uso abusivo pode causar bloqueio da conta.
```
Add one bullet after the opt-out line:
```markdown
- A automação inicia desligada e deve ser ativada conscientemente pelo administrador.
- Configure um limite diário baixo e mantenha os intervalos de envio.
- Faça contato apenas em horários adequados e com uma finalidade legítima.
- Adicione imediatamente à lista de bloqueio quem pedir para não receber mensagens.
- Números que aparecem em mais de um lead (ex.: contabilidade que registra o próprio telefone para clientes) ficam bloqueados para envio automático e manual até você revisar e liberar em **Configurações**.
- Não faça spam, disparos indiscriminados nem compre listas de contatos.
- A conexão usa integração não oficial por dispositivo vinculado. Mudanças no WhatsApp podem interromper o funcionamento e uso abusivo pode causar bloqueio da conta.
```
- [ ] **Step 2: Run the complete test suite**
Run: `npm test`
Expected: PASS — every test file in `tests/`, including all files touched by Tasks 1-7.
- [ ] **Step 3: Commit**
```bash
git add README.md
git commit -m "Document shared-number blocking in the operator README"
```