Add Configuracoes review section for shared numbers

Adds an admin-only "Numeros compartilhados entre leads" section to the
settings modal, wired to the Task 4 shared-numbers routes: lists
detected groups, lets an admin liberar (with an inline motivo input)
or bloquear de novo. Updates the two frontend VM-based test files'
default API mocks to answer GET /api/outreach/shared-numbers so
existing settings-modal tests keep passing, and adds a new
interaction test covering the liberar round trip.
This commit is contained in:
Gustavo
2026-07-31 16:33:03 -03:00
parent a6dce5c936
commit 18d427e2bd
4 changed files with 105 additions and 1 deletions
+2
View File
@@ -78,6 +78,7 @@ async function executeApp({ get, post, patch, del, put } = {}) {
if (url === '/api/outreach/templates') return { templates: [] };
if (url === '/api/outreach/status') return { enviadosHoje: 0, mensagensPorDia: 10, ultimos: [] };
if (url === '/api/outreach/opt-outs') return { optOuts: [] };
if (url === '/api/outreach/shared-numbers') return { groups: [] };
if (url === '/api/whatsapp/status') return { state: 'READY' };
if (get) return get(url);
throw new Error(`GET inesperado: ${url}`);
@@ -177,6 +178,7 @@ async function renderCampaign(app, campaigns, options = {}) {
if (url === '/api/outreach/templates') return { templates: [] };
if (url === '/api/outreach/status') return { enviadosHoje: 0, mensagensPorDia: 10, ultimos: [] };
if (url === '/api/outreach/opt-outs') return { optOuts: [] };
if (url === '/api/outreach/shared-numbers') return { groups: [] };
if (url === '/api/whatsapp/status') return { state: 'READY' };
if (origGet) return origGet(url);
throw new Error(`GET inesperado (campaign): ${url}`);
+39
View File
@@ -83,6 +83,7 @@ async function executeApp({ whatsappState = 'READY', statusResponses = [], qrRes
if (url === '/api/outreach/templates') return { templates: [] };
if (url === '/api/outreach/status') return { enviadosHoje: 0, mensagensPorDia: 10, ultimos: [] };
if (url === '/api/outreach/opt-outs') return { optOuts: [] };
if (url === '/api/outreach/shared-numbers') return { groups: [] };
if (url === '/api/whatsapp/status') return statusResponses.length ? statusResponses.shift() : { state: whatsappState };
if (url === '/api/whatsapp/qr') return qrResponses.length ? qrResponses.shift() : { qr: 'data:image/png;base64,qr', expiresAt: null };
if (get) return get(url);
@@ -521,6 +522,44 @@ test('tabela de tratamento sinaliza numero compartilhado por mais de um lead', a
assert.doesNotMatch(texts[1], /compartilhado/i);
});
test('secao de numeros compartilhados lista grupos e libera/bloqueia via API', async () => {
const app = await executeApp();
const groups = [{
numero: '5511999990009',
leads: [{ id: 1, empresa: 'Empresa A', cnpj: '' }, { id: 2, empresa: 'Empresa B', cnpj: '' }],
liberado: false,
motivo: null
}];
app.context.API.get = async (url) => {
if (url === '/api/outreach/shared-numbers') return { groups };
if (url === '/api/outreach/opt-outs') return { optOuts: [] };
if (url === '/api/outreach/status') return { enviadosHoje: 0, mensagensPorDia: 10, ultimos: [] };
if (url === '/api/outreach/templates') return { templates: [] };
if (url === '/api/whatsapp/status') return { state: 'READY' };
throw new Error(`GET inesperado: ${url}`);
};
await app.element('btn-settings').click();
await new Promise((resolve) => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
const list = app.element('shared-numbers-list');
assert.match(nodeText(list), /5511999990009/);
assert.match(nodeText(list), /Empresa A/);
assert.match(nodeText(list), /Empresa B/);
const row = list.children[0];
const motivoInput = row.children.find((child) => child.tagName === 'INPUT');
const liberarButton = row.children.find((child) => child.tagName === 'BUTTON');
motivoInput.value = 'confirmado contato direto';
await liberarButton.click();
assert.deepEqual(JSON.parse(JSON.stringify(app.posts.at(-1))), {
url: '/api/outreach/shared-numbers/5511999990009/liberar',
body: { motivo: 'confirmado contato direto' }
});
});
test('controle de limpar contexto e um botao acessivel', async () => {
const html = await read('public/index.html');
assert.match(html, /<button[^>]+id="btn-limpar-lead-manual"[^>]+type="button"[^>]*>\s*Limpar contexto do lead\s*<\/button>/i);