mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
O fork estava 198 commits atrás. A `main` já tinha absorvido o PR #200 (canal Zernio) e renumerado as migrations que vieram daqui, então boa parte do conflito era o MESMO trabalho com dois números. ─── O que era duplicata ─── `0117_canal_zernio_vocabulario` e `0118_zernio_envio` diferem de `0131` e `0132` em UMA linha: o número no comentário do cabeçalho. São as minhas, renumeradas lá. Ficam as de lá — numeração é verdade compartilhada, e manter as duas deixaria o mesmo schema com dois nomes. `0119_contato_lookup_telefone` e `0122_telefone_do_lid` PARECIAM a mesma convergência e não são: uma acrescenta `phone_lookup_at` (fila do cron de backfill), a outra `wa_lid` (coluna gerada + dedup). Convivem. ─── O defeito que o merge criaria, e que ninguém veria ─── A `0139_kind_check_completo` reconstrói `agent_inbox_items_kind_check`, e a minha `0120` também. Pelo timestamp a minha roda DEPOIS — e a lista dela não conhecia `midia_nao_lida` nem `contact_proposal_expired`. Aplicar as duas em ordem, num clone atualizado, APAGARIA dois kinds em uso: quem reconstrói por último é quem manda, e uma lista incompleta não é "aditiva", é uma remoção silenciosa. A `0120` passou a trazer a lista inteira. No `baseline.sql` continua UM bloco só para essa constraint, agora com os kinds dos dois lados — a regra do #159, que o `update.sh` de todo clone depende. ─── Onde discordei da main, e por quê ─── `zernio.ts` — `isConfigured()` fica `true`, e não `zernioCredsFromEnv() !== null`. A preocupação de lá é real: `isConfigured() === true` não pode implicar "sem credencial", porque o handler grava `sent` quando `send()` não lança. Só que quem fecha esse par mudou de lugar — `send()` LANÇA `zernio_not_configured`, e o caso descrito naquele comentário ("devolve `{externalId: null}` SEM lançar") não existe mais no arquivo. Exigir env reintroduziria um defeito medido: `resolveZernioCreds` procura primeiro na SESSÃO e só depois no ambiente. Uma instalação que conectou pelo botão tem a credencial no banco e nada no `.env` — a checagem por env diria "não configurado" para um canal conectado e funcionando, e toda mensagem ficaria em `queued` sem tentar sair. Mas o teste de lá estava certo no DESFECHO: sem credencial nenhuma, o certo é `queued` com o motivo, não `failed`. Isso não estava atendido de nenhum dos dois lados — o `catch` do handler marcava `failed`. Agora `notConfigured` é traduzido para `queued`, ao lado do `storage_sign_failed` que já tinha tratamento próprio: falta de credencial não é falha DESTA mensagem, é canal ainda não conectado, e marcar `failed` faria o follow-up desistir de algo que sai sozinho assim que alguém conectar. Os dez casos de `messages-handler-canal-intermediado` passam. ─── O que fica vermelho, e não é daqui ─── `lib/ai/dispatcher/rate-limit.test.ts` — 5 casos estourando o timeout de 15s. Falha também com os arquivos da main trocados um a um, então não vem desta resolução; o CI de lá está verde, então é diferença de ambiente. Fica registrado, não escondido. 3647 casos passam. typecheck, lint:channels e build limpos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
46 lines
2.6 KiB
TypeScript
46 lines
2.6 KiB
TypeScript
import { chromium } from "@playwright/test";
|
|
import { readFileSync } from "node:fs";
|
|
const BASE = "http://127.0.0.1:3100";
|
|
const c = JSON.parse(readFileSync("/Users/rafaelmelgaco/DeskcommCRM/.e2e-creds.json", "utf8"));
|
|
async function main() {
|
|
const b = await chromium.launch();
|
|
const p = await b.newPage({ viewport: { width: 1280, height: 1200 } });
|
|
const erros: string[] = [];
|
|
p.on("console", (m) => { if (m.type() === "error") erros.push(m.text()); });
|
|
await p.goto(`${BASE}/login`, { waitUntil: "domcontentloaded" });
|
|
await p.click('input[type="email"]');
|
|
await p.locator('input[type="email"]').pressSequentially(c.users.manager.email, { delay: 8 });
|
|
await p.click('input[type="password"]');
|
|
await p.locator('input[type="password"]').pressSequentially(c.password, { delay: 8 });
|
|
await p.click('button[type="submit"]');
|
|
await p.waitForURL(/\/app/, { timeout: 30000 });
|
|
await p.goto(`${BASE}/app/metrics`, { waitUntil: "networkidle" });
|
|
await p.waitForTimeout(2500);
|
|
const m = await p.evaluate(`(() => {
|
|
var t = document.body.innerText;
|
|
function pega(rot) {
|
|
var re = new RegExp(rot + "[\\\\s\\\\S]{0,260}?(\\\\d+[.,]\\\\d+%)");
|
|
var x = t.match(re); return x ? x[1] : null;
|
|
}
|
|
return {
|
|
repergunta_presente: t.indexOf("Perguntas que a pessoa teve de repetir") >= 0,
|
|
repergunta_valor: pega("Perguntas que a pessoa teve de repetir"),
|
|
repergunta_rotulada_como_PISO: /Piso: \\d+ de \\d+/.test(t),
|
|
repergunta_limitacao_visivel: t.indexOf("escapa desta medida") >= 0,
|
|
espera_presente: t.indexOf("Esperas sem nenhuma resposta por mais de 4h") >= 0,
|
|
espera_valor: pega("Esperas sem nenhuma resposta por mais de 4h"),
|
|
abandono_presente: t.indexOf("morreram no silêncio") >= 0,
|
|
pior_caso_presente: t.indexOf("Insistência no pior caso") >= 0,
|
|
base_sem_ressalva_de_escopo: /Base: \d+ demandas? encerradas? nos últimos 30 dias, e \d+ ainda abertas/.test(t),
|
|
invariante4_presente: t.indexOf("Demandas abertas sem próximo passo") >= 0,
|
|
invariante4_valor: (function(){ var m=t.match(/Demandas abertas sem próximo passo[\s\S]{0,200}?(\d+)\s/); return m?m[1]:null; })(),
|
|
insistencia_declara_denominador: /Medido sobre as \d+ demandas que passaram por atendimento humano/.test(t)
|
|
};
|
|
})()`);
|
|
console.log(JSON.stringify(m, null, 2));
|
|
console.log("erros de console:", erros.length ? erros.slice(0, 3) : "nenhum");
|
|
await p.screenshot({ path: "/private/tmp/claude-501/-Users-rafaelmelgaco-DeskcommCRM/10ffaf65-ac1a-4b1b-b62d-368cbdeef6c5/scratchpad/atrito-fase4.png", fullPage: true });
|
|
await b.close();
|
|
}
|
|
main().catch((e) => { console.error(e); process.exit(1); });
|