Runbook — Audiência do Sagazchat

Manual operacional para uma IA ou pessoa operar a Audiência (base de contatos) do Sagazchat sem depender de tentativa visual cega.

8 min de leitura Atualizado em 14 de mai. de 2026

Manual operacional para uma IA ou pessoa operar a Audiência (base de contatos) do Sagazchat sem depender de tentativa visual cega.

Status: validado em 2026-05-11 na conta de exemplo (user_id=39, empresa exemplo, app v4.6.0). Contato pré-existente — NÃO DELETAR:

  • Fulano Exemplo (id 97653, +55 98 8120-XXXX)

Outros 3 contatos que existiam (Cliente Exemplo, contato-exemplo, 55119XXXXXXX) foram deletados pelo próprio administrador durante a auditoria (não por mim).

1. Onde fica

  • Sidebar: Gestão › Audiência (item simples, link direto, sem submenu).
  • Lista: https://app.sagazchat.com/contacts
  • Detalhe/edição: modal sobre a lista — NÃO navega URL (URL continua /contacts).

2. Lista /contacts

2.1 Cabeçalho

Botões no topo direito, da esquerda pra direita:

BotãoEstiloAção
Remover todoscinzaModal de confirmação destrutivo (apaga TODOS os contatos + atendimentos relacionados)
ExportarcinzaDispara GET /contacts/export direto, sem modal — baixa arquivo
Importarcinza com chevron ▾Dropdown com 2 opções: Importar XLS/XLSX, Importar do Whatsapp
AdicionarverdeModal “Especialista Virtual” (criar contato)

Acima da tabela: campo Pesquisar... (busca por texto livre).

2.2 Tabela

Colunas:

#ColunaComportamento
0☐ (checkbox no header)Marca/desmarca todos da página
1NomeNome do contato. Mostra avatar (foto do WhatsApp) + ícone do canal (badge verde do WhatsApp). Quando o contato não tem nome, mostra o telefone como nome (ex: 55119XXXXXXX). Coluna sortable ().
2WhatsAppFormatado +55 (XX) XXXXX-XXXX para BR; cru pra internacionais (2349019879608)
3E-mailTexto ou Não informado se vazio
4Data de Inscriçãodd/mm/aaaa. Sortable ().
5AçõesKebab com 3 opções (ver § 4)

Cada linha tem checkbox individual à esquerda do avatar.

Comportamento dos checkboxes ao selecionar (toolbar contextual com bulk actions?) — [a validar]. Não foi explorado nesta auditoria.

Endpoint da lista:

GET /contacts/?searchParam=&pageNumber=1

(Atenção: trailing slash /? — necessário.)

Filtro de busca usa searchParam=<texto>. Não foi validado contra quais campos a busca rastreia (nome+telefone+email óbvios; etiqueta provavelmente não — campo de etiqueta não aparece na UI da lista).

3. Criar contato

Botão + Adicionar abre modal cujo heading é “Especialista Virtual” (quirky — provável leftover de nome de componente; não é nome de feature).

Campos:

CampoTipoDetalhes
Nometext (name="name")Nome completo do contato
Telefonetel (sem name)Combobox de país (default Brasil 🇧🇷 +55) + número. Placeholder +55 (13) 91234 4321.
E-Mailtext (name="email")Opcional
Data de nascimentodate (name="birthDate")dd/mm/aaaa
Informações adicionaisRepeatableContador (0..N) + botão +. Clicar + adiciona um par extraInfo[N].name (Nome do campo) + extraInfo[N].value (Valor)

Botões: Cancelar, Salvar.

Schema do POST: { name, phone, email, birthDate, extraInfo: [{name, value}] }[a validar] (não capturei o POST de criar nesta auditoria pra não popular a base).

4. Kebab da linha

Menu na coluna Ações com 3 itens validados:

ItemAçãoObservação
Editar contatoAbre o mesmo modal “Especialista Virtual” pré-preenchidoEquivalente a clicar no nome
Ir para o chatNavega pra /live-chat-whatsapp (ou outro canal) abrindo a conversa do contatoNão validei navegação completa
DeletarModal de confirmação Deletar contato? Você tem certeza que deseja excluir o contato "<nome>"? Todos os atendimentos relacionados serão perdidos. Botões Cancelar / Deletar. Destrutivo.

IMPORTANTE PARA IA: clicar no nome do contato abre o mesmo modal que “Editar contato” — não é navegação separada.

5. Detalhe/edição (modal)

Click no nome OU Editar contato no kebab disparam:

GET /contacts/<id>

Resposta carrega o modal com todos os campos preenchidos. Heading do modal mostra <Nome> + linha com <+55 (XX) XXXXX-XXXX>.

Campos do modal são os mesmos da criação (Nome, Telefone, E-Mail, Data de nascimento, Informações adicionais). Botões Cancelar/Salvar.

Endpoint PUT de salvar edição não capturado — [a validar]. Provável: PUT /contacts/<id>.

6. Importar

Botão Importar abre dropdown com 2 opções.

6.1 Importar XLS/XLSX

Modal “Importar Lista de Contatos” com:

  • Subtítulo (texto residual confuso): Escolha o canal para iniciar a conversa com esse contato — não tem seletor de canal visível na tela; texto provavelmente leftover de versão anterior.
  • Link “Baixe aqui” pra modelo de planilha.
  • Área drag-and-drop: Solte o arquivo aqui, ou clique para selecionar / Formatos suportados: XLS e XLSX.
  • <input type="file" name="file" accept=".xls,.xlsx">
  • Botões: Cancelar, Importar.

Endpoint POST do import não capturado (não enviei arquivo). Provável: POST /contacts/import multipart/form-data.

6.2 Importar do Whatsapp

Sem modal — clique dispara request direto:

POST /contacts/cell-import/all

Resposta não capturada. Hipótese: sincroniza TODOS os contatos do celular conectado ao WhatsApp não-oficial (instância). Provavelmente longo (pode levar minutos).

CUIDADO: ação importa massa de contatos. Validar antes em conta de teste se for usar em produção.

7. Exportar

Botão Exportar dispara direto:

GET /contacts/export

Sem modal de opções/filtros. Exporta a base inteira. Formato não confirmado (provável CSV ou XLSX).

Download não foi capturado pelo response listener — pode ter usado Content-Disposition: attachment que o navegador roteia direto pro disco.

8. Remover todos (DESTRUTIVO)

Botão Remover todos abre modal cujo heading é apenas “Deletar” (não “Remover todos”, quirky):

  • Texto: Tem certeza que deseja deletar TODOS os contatos? Todos os atendimentos relacionados serão perdidos.
  • Botões: Cancelar / Deletar (vermelho).

JAMAIS clicar em Deletar sem ordem explícita e dupla confirmação do administrador.

Endpoint provável: DELETE /contacts/all ou similar — [a validar].

9. Ações seguras vs disparadoras (para IA)

Seguras (pode rodar sem confirmação):

  • GET /contacts/?searchParam=&pageNumber=1 — listar
  • GET /contacts/<id> — detalhe
  • GET /contacts/export — exportar (apenas baixa, não muda dados)
  • Abrir modal Adicionar/Editar e cancelar com Escape
  • Abrir dropdown Importar (sem clicar nas opções)
  • Abrir kebab e cancelar

Disparadoras (exigem ordem explícita do administrador):

  • POST /contacts (criar) — [a validar] endpoint
  • PUT /contacts/<id> (editar) — [a validar]
  • DELETE /contacts/<id> (deletar individual)
  • DELETE /contacts/all (deletar TODOS) — NUNCA sem dupla confirmação
  • POST /contacts/cell-import/all (importar do WhatsApp) — pode trazer centenas de contatos sem revertendo
  • Botão Salvar em qualquer modal

10. Quirks e armadilhas

  • Modal de criar contato se chama “Especialista Virtual” — confuso visualmente. Não é nome de feature, é leftover de naming do componente.
  • Modal de remover todos tem heading só “Deletar” — sem mencionar que é massa. Ler o texto antes de clicar.
  • “Importar do Whatsapp” não tem modal — clique imediato dispara POST /contacts/cell-import/all. UX perigoso pra quem clica explorando.
  • Subtítulo do modal Importar XLS tem texto residual "Escolha o canal para iniciar a conversa com esse contato" que não corresponde à função.
  • Click no nome do contato abre modal de edição — não navega pra tela dedicada. URL continua /contacts.
  • Contato sem nome aparece como o número (ex: 55119XXXXXXX) — buscar contato por “nome” pode falhar se o usuário não nomeou.
  • Empresa “exemplo” no topo da sidebar ≠ contato “Fulano Exemplo” na lista — homonímia perigosa pra seletores que buscam por texto exato. Sempre filtrar pela tabela <TR> ou wrapper de linha.
  • Trailing slash em /contacts/?... — sem ele o endpoint pode falhar (/contacts?searchParam= pode redirecionar ou 404).

11. Snippets — comandos Playwright via CDP

Pressupõe sessão CDP ativa em scripts/_out/cdp.txt.

11.1 Listar contatos

await page.goto('https://app.sagazchat.com/contacts', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(2500);
// captura: GET /contacts/?searchParam=&pageNumber=1

11.2 Buscar por texto

await page.locator('input[placeholder="Pesquisar..."]').fill('Exemplo');
await page.waitForTimeout(1000);
// dispara GET /contacts/?searchParam=Exemplo&pageNumber=1

11.3 Kebab da linha (defensivo)

// procura o cell EXATO e usa wrapper de linha pra evitar acertar outro item
const kebab = await page.evaluate((nome) => {
  const cell = Array.from(document.querySelectorAll('*')).find((el) => (el.textContent || '').trim() === nome && el.children.length === 0);
  if (!cell) return null;
  // sobe até wrapper de linha (<TR> com 5-19 children)
  let row = cell;
  for (let i = 0; i < 10; i++) {
    if (!row.parentElement) break;
    row = row.parentElement;
    if (row.children.length >= 5 && row.children.length < 20) break;
  }
  const ic = Array.from(row.querySelectorAll('.MuiIconButton-root, button')).filter((b) => {
    const r = b.getBoundingClientRect();
    return r.width < 60 && r.height < 60;
  });
  const last = ic[ic.length - 1];
  if (!last) return null;
  const r = last.getBoundingClientRect();
  return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
}, 'Fulano Exemplo');
await page.mouse.click(kebab.x, kebab.y);

11.4 Abrir modal de edição

const coord = await page.evaluate((nome) => {
  const cell = Array.from(document.querySelectorAll('*')).find((el) => (el.textContent || '').trim() === nome && el.children.length === 0);
  if (!cell) return null;
  const r = cell.getBoundingClientRect();
  return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
}, 'Fulano Exemplo');
await page.mouse.click(coord.x, coord.y);
// modal abre; GET /contacts/<id>

11.5 Fechar dialog defensivo

async function closeAll() {
  for (let i = 0; i < 3; i++) {
    const has = await page.evaluate(() => !!document.querySelector('[role="dialog"], .MuiDialog-paper, [role="menu"], .MuiMenu-paper, .MuiPopover-paper'));
    if (!has) return;
    await page.keyboard.press('Escape');
    await page.waitForTimeout(400);
  }
}

12. Como iniciar nova auditoria

  1. Garantir sessão CDP ativa (scripts/_out/cdp.txt).
  2. Confirmar empresa: exemplo no topo da sidebar.
  3. Verificar contato Fulano Exemplo na lista — sentinela. Não deletar.
  4. Pra criar contatos de teste, prefixar nome com LAB-CONTATO-* para fácil identificação e cleanup posterior.
  5. Pra confirmar comportamento de bulk actions (checkboxes), marcar 1+ contatos e ver se aparece toolbar contextual no topo. Documentar e atualizar § 2.2.

13. Pendências para próxima auditoria

  • Bulk actions ao selecionar checkboxes — verificar se aparece toolbar contextual e quais ações disponíveis.
  • POST de criar contato — schema do body completo (extraInfo, country code, etc).
  • PUT de editar contato — endpoint e payload.
  • POST do Importar XLS/XLSX — endpoint e structure multipart.
  • Resposta de POST /contacts/cell-import/all — síncrona ou job assíncrono? Como saber quando terminou?
  • Formato real do export (CSV ou XLSX)? Quais colunas?
  • DELETE /contacts/all — endpoint real (e payload se houver).
  • Itens da sidebar ainda não auditados: Conexões/integrations, Agendamentos, Campanhas, N8N, Prompts, Assistente Interno, Gerente de grupo.

14. Scripts criados

  • scripts/sagaz-audiencia-investigar.mjs — listing inicial
  • scripts/sagaz-audiencia-detalhar.mjs — primeira tentativa (kebab pegou linha errada — abandonado, defeitos corrigidos no próximo)
  • scripts/sagaz-audiencia-detalhar2.mjs — kebab + click nome via wrapper de linha
  • scripts/sagaz-audiencia-acoes-topo.mjs — Exportar, Remover todos (cancela), Importar XLS, Importar WhatsApp, botão +