7 Commits

Author SHA1 Message Date
6c23513f42 fix(frontend): cursor de seta nos cantos do FAB do widget (border-radius exclui hit-test)
Pedido do usuário: "vira a mão quando está sobre a letra e não sobre o
retângulo inteiro". O FAB flutuante é rounded-full 56x56 — Chrome exclui
os cantos cortados desse quadrado do hit-test de elementos com
border-radius, então o mouse nos cantos caía pro <div> de posicionamento
por trás (sem cursor:pointer) em vez do botão. Clique dentro do círculo
real sempre funcionou; só o cursor ficava inconsistente. Confirmado com
hover real via Playwright piercing o shadow DOM. Fix: cursor-pointer no
wrapper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-31 16:40:22 -03:00
dd095077dd fix(frontend): widget usava config antigo do localStorage em vez do ramal real do agente
Dois bugs reais reportados pelo usuário testando um ramal de verdade
(teste@teste.com.br / ramal 1501):

1. widgetStorage.mergeConfig fazia `{...externalConfig, ...stored}` —
   um sip_config salvo de sessão/ramal anterior no mesmo navegador
   sempre vencia sobre as credenciais que o backend acabou de injetar
   via data-sip-* pro agente logado agora, então o widget tentava
   registrar com o ramal errado (ou senha vazia) pra sempre, sem erro
   visível. Invertido: externalConfig (sempre fresco, vindo do agente
   logado) vence; stored só preenche o que faltar.

2. crypto.subtle é undefined em contexto inseguro (HTTP puro, mesma
   classe de bug do botão de copiar da PHASE 71) — salvar a senha na
   aba Configurações do widget quebrava com "Cannot read properties of
   undefined (reading 'importKey')". Agora cai pra um fallback base64
   reversível quando SubtleCrypto não está disponível.

Testado com Playwright: config "errado" salvo no localStorage não sobrevive
mais a um reload — o próximo connect() usa o ramal real do agente.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-31 16:14:16 -03:00
e75872faec fix(frontend): widget do softphone preso em cinza — dupla chamada de connect() sem cooldown
Dois efeitos de auto-connect do Widget.jsx (mount + reconnect-on-drop)
podiam disparar connect() em sequência rápida sem respeitar o cooldown
de 5s (só o efeito de reconexão gravava o timestamp), abrindo um
segundo SimpleUser/registro concorrente que sip.js rejeita com
RequestPendingError — e o catch sobrescrevia connectionStatus pra
"disconnected" incondicionalmente, travando o widget em cinza.
Trava por promise em useSip.js::connect() + cooldown também no
auto-connect de mount resolvem: testado com Playwright dentro do app
real, connect() dispara uma única vez e o status nunca mais volta
pra cinza.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-31 13:46:52 -03:00
b33d35db50 fix(softphone): widget nunca calculava o AOR quando config vinha via data-sip-* — ficava preso em "desconectado" pra sempre
Usuário configurou o proxy WebRTC de QA de verdade (Platform > Sistema >
Softphone WebRTC) e tentou registrar o ramal 1501 (tenant teste01) — o
widget só ficava cinza, sem nenhum erro visível.

Descartado infraestrutura/credenciais primeiro (testes diretos: DNS, TCP,
TLS, handshake WebSocket manual, e um REGISTER SIP cru com autenticação
digest através do proxy — chegou no FreeSWITCH de verdade e voltou 200 OK).
O bug estava no código-fonte do próprio widget: `widgetStorage.mergeConfig`
só calculava o campo `aor` (sip:user@domain) no branch de config salva
manualmente no localStorage — no branch usado quando a config vem via
data-sip-*/window.HandphoneConfig (sempre, neste app), `aor` nunca era
calculado, e `connect()` quebrava em `cfg.aor.startsWith(...)` com `aor`
undefined — erro só no console, nunca visível pro usuário.

Corrigido na mesma cópia local do handphone-2.0 (nunca enviado pro repo
externo do usuário), rebuildado e revendorizado. Testado ponta a ponta com
Playwright + navegador real fora do app (data-sip-* do ramal 1501 de
verdade, sem depender do login do tenant): REGISTER 200 OK, botão do
widget vira verde.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-31 13:03:38 -03:00
97ef8a6ba8 feat: edição de rotas de entrada, fix de 2 bugs reais no ESL, diagnóstico de NAT/áudio e softphone WebRTC (PHASE 65/66)
Três achados reportados pelo usuário numa mensagem só: (1) Rotas de Entrada
não tinha edição depois de criada — implementada no mesmo padrão de Filas;
(2) telas de Platform > Infraestrutura sempre davam "Timeout no ESL" nesta
VM — não era limitação permanente como o comentário antigo dizia, e sim
ESL_HOST=freeswitch (nome DNS que só existe dentro da rede do Docker) mais
um segundo bug independente (`show gateways as json` não é comando válido
nesta versão do FreeSWITCH); (3) ramal externo registrava mas sem áudio —
diagnosticado com contadores de pacote do iptables: a VM está atrás de um
roteador sem port-forward pra faixa de RTP, achado de infraestrutura de
rede, não bug de código.

Também integra o softphone WebRTC (handphone.js/OpenSIPS, já em produção):
código-fonte encontrado em git.falehandix.com.br/Handix/handphone-2.0,
patch mínimo pra aceitar o endereço do proxy em runtime (era build-time),
nova config global (Platform > Infraestrutura > Softphone WebRTC) e widget
na topbar do tenant que pega usuário/domínio/senha do ramal vinculado ao
agente logado.

Adiciona docs/QA_SETUP.md — runbook completo pra subir o ambiente do zero
numa máquina nova (Docker, migrations, seed, systemd), e completa o
.env.example que estava faltando a maioria das variáveis reais.

Testado ponta a ponta com Playwright: edição de rota (criar/editar/F5),
as 3 telas de Infraestrutura com dado real, e um tenant/ramal/agente de
teste criados na hora confirmando que o script do softphone recebe as
credenciais certas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-30 22:14:39 -03:00
a15ea26cb5 feat(frontend): branding "B2BCall by Handix" — logos novos em todo o app
Usuário forneceu 3 logos novos (B2BCall horizontal, B2BCall quadrado, Handix —
empresa mãe). Processados com sharp (trim de whitespace + resize) e salvos em
apps/frontend/public/branding/, substituindo o antigo b2blogo.png (mesmo desenho,
só com mais espaço em branco ao redor).

Achado ao processar: Logo_Handix.png original era RGB sem canal alpha (fundo branco
opaco) — aplicar o mesmo filtro brightness-0 invert já usado no logo B2BCall pro
painel escuro do login virava um retângulo branco sólido. Corrigido recolorindo
pixels próximos de branco como transparentes via sharp antes de salvar.

Sidebar (tenant e platform) ganha o ícone do B2BCall ao lado do nome do tenant/
"PLATFORM" no cabeçalho (canto superior esquerdo); topbar ganha o logo da Handix
com link pra www.handix.com.br (canto superior direito, escondido no mobile). Login
ganha "BY HANDIX" abaixo do logo principal e um rodapé com o logo da Handix + link.

Testado ponta a ponta via Puppeteer: login, dashboard tenant/platform (claro e
escuro), sidebar recolhida, drawer mobile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
2026-08-30 11:01:55 -03:00
2f130622d1 feat: bootstrap b2bcall saas architecture
- monorepo skeleton (apps/, packages/, infrastructure/, scripts/, docs/)
- docker-compose with PostgreSQL 18 and Redis 7 (localhost-only)
- .gitignore and .env.example
- initial TODO.md and docs/ARCHITECTURE.md
2026-08-27 23:12:29 -03:00