Skip to content

fix(crm): QR de conexão WhatsApp para de depender só do webhook, +res…#63

Merged
sejaanomalo-web merged 1 commit into
mainfrom
claude/crm-whatsapp-qr-reconexao
Jul 14, 2026
Merged

fix(crm): QR de conexão WhatsApp para de depender só do webhook, +res…#63
sejaanomalo-web merged 1 commit into
mainfrom
claude/crm-whatsapp-qr-reconexao

Conversation

@sejaanomalo-web

@sejaanomalo-web sejaanomalo-web commented Jul 14, 2026

Copy link
Copy Markdown
Owner

…et e login por código

O botão "Gerar QR" só disparava o pedido pra Evolution e esperava o QR chegar depois via webhook (QRCODE_UPDATED) + Supabase Realtime — dois elos que podem falhar/atrasar em silêncio (webhook mal configurado na VPS, Realtime desligado sem aviso, ou só a corrida entre o refresh e o webhook). Quando isso falhava, o clique não dava nenhum feedback: nem QR, nem erro.

Agora a própria resposta do connect/create da Evolution (que já costuma trazer o QR no corpo) é lida e persistida na hora — o botão funciona mesmo se o canal do webhook estiver quebrado. Complementos:

  • Polling client-side (2.5s por até 45s) enquanto aguarda o scan, em vez de um único refresh que corre contra o webhook.
  • Botão "Recarregar/Reiniciar conexão": desloga + recria a instância na Evolution do zero, pra quando a sessão trava e nada resolve.
  • Segunda opção de login: código de pareamento (a Evolution troca o QR por um código curto digitado no WhatsApp) — o usuário nunca fica sem saída.

Summary by CodeRabbit

  • Novos Recursos

    • Adicionado suporte à conexão de instâncias por QR Code ou código de pareamento.
    • Incluída atualização automática do status até a confirmação da conexão.
    • Disponibilizados controles para gerar, recarregar e reiniciar a conexão.
    • Códigos e QR Codes deixam de ser exibidos após a conexão.
  • Melhorias

    • Números de telefone são normalizados automaticamente durante o processo de conexão.
    • O fluxo de recuperação orienta o usuário quando o código ainda não está disponível.

…et e login por código

O botão "Gerar QR" só disparava o pedido pra Evolution e esperava o QR
chegar depois via webhook (QRCODE_UPDATED) + Supabase Realtime — dois elos
que podem falhar/atrasar em silêncio (webhook mal configurado na VPS,
Realtime desligado sem aviso, ou só a corrida entre o refresh e o webhook).
Quando isso falhava, o clique não dava nenhum feedback: nem QR, nem erro.

Agora a própria resposta do connect/create da Evolution (que já costuma
trazer o QR no corpo) é lida e persistida na hora — o botão funciona mesmo
se o canal do webhook estiver quebrado. Complementos:
- Polling client-side (2.5s por até 45s) enquanto aguarda o scan, em vez de
  um único refresh que corre contra o webhook.
- Botão "Recarregar/Reiniciar conexão": desloga + recria a instância na
  Evolution do zero, pra quando a sessão trava e nada resolve.
- Segunda opção de login: código de pareamento (a Evolution troca o QR por
  um código curto digitado no WhatsApp) — o usuário nunca fica sem saída.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
metas-anomalo Ready Ready Preview, Comment Jul 14, 2026 11:51am

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The CRM instance flow now supports QR and pairing-code authentication. Evolution responses expose connection credentials, CRM state persists and clears them, status polling tracks connection completion, and the management UI provides QR, pairing-code, and restart controls.

Changes

CRM connection authentication

Layer / File(s) Summary
Evolution connection contracts
lib/evolution.ts, lib/crm-telefone.ts, supabase/migrations/...
Evolution connection responses now expose QR and pairing-code data, phone normalization is centralized, and crm_instancias stores ultimo_pairing_code.
Instance authentication actions
lib/crm-instancias-actions.ts, lib/crm-leads-actions.ts
Instance creation, QR generation, pairing-code generation, restart, and status polling persist and return the corresponding authentication state.
Webhook state cleanup
lib/crm-inbound.ts
QR and pairing-code fields are cleared when webhook events report updated QR data or a connected instance.
Instance management controls
components/crm/GerenciadorInstancias.tsx
The UI polls status, displays QR or pairing codes, supports pairing-code fallback input, and adds restart controls.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  actor Client
  participant GerenciadorInstancias
  participant crm-instancias-actions
  participant EvolutionAPI
  participant Supabase
  participant crm-inbound

  Client->>GerenciadorInstancias: Generate QR or pairing code
  GerenciadorInstancias->>crm-instancias-actions: Call authentication action
  crm-instancias-actions->>EvolutionAPI: Create or connect instance
  EvolutionAPI-->>crm-instancias-actions: Return qrBase64 or pairingCode
  crm-instancias-actions->>Supabase: Persist credential and qrcode status
  crm-instancias-actions-->>GerenciadorInstancias: Return authentication result
  GerenciadorInstancias->>crm-instancias-actions: Poll instance status
  crm-instancias-actions->>Supabase: Read connection and credential state
  Supabase-->>crm-instancias-actions: Return status, QR, and pairing code
  crm-instancias-actions-->>GerenciadorInstancias: Return current status
  crm-inbound->>Supabase: Clear credentials after connected event
  GerenciadorInstancias-->>Client: Stop polling and hide credentials
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: WhatsApp connection QR handling no longer depends only on the webhook.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/crm-whatsapp-qr-reconexao

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/crm-inbound.ts (1)

201-212: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Stale QR/pairing code not cleared on disconnect.

The patch only nulls ultimo_qr/ultimo_pairing_code when status === "conectado". If the session goes straight from "qrcode" to "close" (e.g., pairing timeout/failure) without ever connecting, the now-invalid QR/code stays in the row and is still rendered by the UI (status_conexao !== "conectado" condition in GerenciadorInstancias.tsx), misleading the user into scanning/typing a dead code.

🛡️ Proposed fix
   const agora = new Date().toISOString()
   const patch: Record<string, unknown> = {
     status_conexao: status,
     updated_at: agora,
   }
   if (status === "conectado") {
     patch.conectado_em = agora
     patch.ultimo_qr = null // QR/codigo ja foi usado, evita reexibir na UI
     patch.ultimo_pairing_code = null
+  } else if (status === "desconectado") {
+    patch.ultimo_qr = null
+    patch.ultimo_pairing_code = null
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crm-inbound.ts` around lines 201 - 212, Update the patch construction in
the connection-status update flow so ultimo_qr and ultimo_pairing_code are
cleared whenever the session is no longer awaiting connection, including
transitions to "close" from "qrcode". Preserve conectado_em updates only for
status "conectado", and keep the existing crm_instancias update operation
unchanged.
🧹 Nitpick comments (2)
components/crm/GerenciadorInstancias.tsx (1)

303-332: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Polling doesn't resume on mount, and stale codes aren't flagged after the 45s timeout.

Two related gaps in the polling lifecycle:

  • If the component mounts while instancia.status_conexao === "qrcode" (e.g., reload or navigating back mid-flow), polling never (re)starts — only explicit button clicks call iniciarPolling().
  • When pararPolling fires from the 45s setTimeout, qrLocal/pairingLocal are left as-is, so a likely-expired QR/code keeps rendering with no indication that auto-refresh has stopped.

Consider starting polling on mount when already in "qrcode" status, and clearing/flagging the displayed code when the timeout elapses without connecting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/crm/GerenciadorInstancias.tsx` around lines 303 - 332, Update the
polling lifecycle around iniciarPolling and pararPolling so the component starts
polling on mount when instancia.status_conexao is "qrcode", including the
appropriate effect cleanup. When the 45-second timeout expires without
connection, clear or mark qrLocal and pairingLocal as expired while preserving
the existing cleanup behavior for normal polling stops and successful
connection.
lib/evolution.ts (1)

419-436: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate error-extraction logic across criarInstanciaEvolution and conectarInstanciaEvolution.

Both functions now inline the same message/error/status extraction instead of the existing extrairErroResposta helper (still used in excluirInstanciaEvolution). Understandable since the body must be parsed once for both success and failure paths, but the duplicated 6-line block could drift over time.

♻️ Suggested shared helper
+function extrairErroDeJson(json: unknown, status: number): string {
+  const obj = (json ?? {}) as Record<string, unknown>
+  return (
+    (typeof obj.message === "string" && obj.message) ||
+    (typeof obj.error === "string" && obj.error) ||
+    `HTTP ${status}`
+  )
+}

Then reuse extrairErroDeJson(json, resp.status) in both criarInstanciaEvolution and conectarInstanciaEvolution failure branches.

Also applies to: 492-514

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/evolution.ts` around lines 419 - 436, Replace the duplicated inline
message and status extraction in the failure branches of criarInstanciaEvolution
and conectarInstanciaEvolution with the existing extrairErroDeJson(json,
resp.status) helper. Preserve the single JSON parse used by each function and
continue logging the full response payload before returning the helper-derived
error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/crm/GerenciadorInstancias.tsx`:
- Around line 348-370: Update aplicarResultadoQr so iniciarPolling() also runs
when r.ok is true but neither qrBase64 nor pairingCode is provided, preserving
the existing immediate QR/pairing-code handling and notice state. Ensure
successful webhook-waiting responses begin polling for the persisted QR data.

---

Outside diff comments:
In `@lib/crm-inbound.ts`:
- Around line 201-212: Update the patch construction in the connection-status
update flow so ultimo_qr and ultimo_pairing_code are cleared whenever the
session is no longer awaiting connection, including transitions to "close" from
"qrcode". Preserve conectado_em updates only for status "conectado", and keep
the existing crm_instancias update operation unchanged.

---

Nitpick comments:
In `@components/crm/GerenciadorInstancias.tsx`:
- Around line 303-332: Update the polling lifecycle around iniciarPolling and
pararPolling so the component starts polling on mount when
instancia.status_conexao is "qrcode", including the appropriate effect cleanup.
When the 45-second timeout expires without connection, clear or mark qrLocal and
pairingLocal as expired while preserving the existing cleanup behavior for
normal polling stops and successful connection.

In `@lib/evolution.ts`:
- Around line 419-436: Replace the duplicated inline message and status
extraction in the failure branches of criarInstanciaEvolution and
conectarInstanciaEvolution with the existing extrairErroDeJson(json,
resp.status) helper. Preserve the single JSON parse used by each function and
continue logging the full response payload before returning the helper-derived
error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c17f329-bdfd-4563-addf-5541912f61ed

📥 Commits

Reviewing files that changed from the base of the PR and between d78d096 and 44985f6.

📒 Files selected for processing (7)
  • components/crm/GerenciadorInstancias.tsx
  • lib/crm-inbound.ts
  • lib/crm-instancias-actions.ts
  • lib/crm-leads-actions.ts
  • lib/crm-telefone.ts
  • lib/evolution.ts
  • supabase/migrations/20260717_crm_instancias_pairing_code.sql

Comment on lines +348 to +370
function aplicarResultadoQr(r: {
ok: boolean
erro?: string
qrBase64?: string
pairingCode?: string
}) {
if (!r.ok) {
setErro(r.erro ?? "Erro")
setAviso(null)
return
}
setErro(null)
setAviso(r.erro ?? null)
if (r.qrBase64) {
setQrLocal(r.qrBase64)
setPairingLocal(null)
}
if (r.pairingCode) {
setPairingLocal(r.pairingCode)
setQrLocal(null)
}
if (r.qrBase64 || r.pairingCode) iniciarPolling()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Polling never starts for the exact "webhook hasn't confirmed yet" case.

iniciarPolling() is only invoked when r.qrBase64 || r.pairingCode is truthy. But gerarQrAction/reiniciarInstanciaAction can legitimately return ok:true with neither field set, specifically when Evolution accepted the request but didn't return the QR inline (the exact scenario the polling was built to cover, per those actions' own comments about waiting for the webhook). In that branch, aviso shows "aguardando o webhook confirmar", but nothing then polls the DB to pick up ultimo_qr/ultimo_pairing_code once the webhook writes them — the user is stuck until they manually retry.

🐛 Proposed fix
     setErro(null)
     setAviso(r.erro ?? null)
     if (r.qrBase64) {
       setQrLocal(r.qrBase64)
       setPairingLocal(null)
     }
     if (r.pairingCode) {
       setPairingLocal(r.pairingCode)
       setQrLocal(null)
     }
-    if (r.qrBase64 || r.pairingCode) iniciarPolling()
+    // Sempre sonda: mesmo sem QR/código na resposta, o webhook pode
+    // escrever ultimo_qr/ultimo_pairing_code em segundos.
+    iniciarPolling()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function aplicarResultadoQr(r: {
ok: boolean
erro?: string
qrBase64?: string
pairingCode?: string
}) {
if (!r.ok) {
setErro(r.erro ?? "Erro")
setAviso(null)
return
}
setErro(null)
setAviso(r.erro ?? null)
if (r.qrBase64) {
setQrLocal(r.qrBase64)
setPairingLocal(null)
}
if (r.pairingCode) {
setPairingLocal(r.pairingCode)
setQrLocal(null)
}
if (r.qrBase64 || r.pairingCode) iniciarPolling()
}
function aplicarResultadoQr(r: {
ok: boolean
erro?: string
qrBase64?: string
pairingCode?: string
}) {
if (!r.ok) {
setErro(r.erro ?? "Erro")
setAviso(null)
return
}
setErro(null)
setAviso(r.erro ?? null)
if (r.qrBase64) {
setQrLocal(r.qrBase64)
setPairingLocal(null)
}
if (r.pairingCode) {
setPairingLocal(r.pairingCode)
setQrLocal(null)
}
// Sempre sonda: mesmo sem QR/código na resposta, o webhook pode
// escrever ultimo_qr/ultimo_pairing_code em segundos.
iniciarPolling()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/crm/GerenciadorInstancias.tsx` around lines 348 - 370, Update
aplicarResultadoQr so iniciarPolling() also runs when r.ok is true but neither
qrBase64 nor pairingCode is provided, preserving the existing immediate
QR/pairing-code handling and notice state. Ensure successful webhook-waiting
responses begin polling for the persisted QR data.

@sejaanomalo-web
sejaanomalo-web merged commit 15837db into main Jul 14, 2026
3 checks passed
@sejaanomalo-web
sejaanomalo-web deleted the claude/crm-whatsapp-qr-reconexao branch July 14, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant