Claude/crm fase1 inbox conexoes whatsapp#49
Conversation
Tira o formulario de trafego da secao Formularios (Configuracoes) + o wiring morto (FormId narrowado, responsaveisTrafego e listarTimePorPapel('gestor_trafego') removidos). A rota publica /formulario e o componente FormularioManual permanecem intactos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Espelha o gerenciador de Trafego pago: listarRelatoriosComerciaisAdmin passa a receber filtros (empresa + periodo, intervalo obrigatorio) e GerenciadorRelatoriosComerciais comeca vazio, mostrando a lista so depois do Buscar. Configuracoes deixa de pre-carregar todos os relatorios. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fecha o ciclo mínimo do CRM: conectar um número real (webhook agora processa QRCODE_UPDATED/CONNECTION_UPDATE, e há UI pra criar instância e escanear o QR em /dashboard/crm/conexoes, sem precisar de SQL manual) e conversar pelo dashboard (inbox com lista de leads + thread, finalmente chamando enviarTextoEvolution). Realtime via crm_realtime_ping. Inclui runbook de provisionamento do servidor Evolution (VPS + Docker Compose). Pesquisa prévia confirmou manter Evolution API como conector (vs WAHA — multi-sessão paga contradiz o requisito de 10 números — e Baileys puro — reinventaria o que a Evolution já entrega pronta). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughChangesCRM operations
Commercial configuration
Estimated code review effort: 5 (Critical) | ~100 minutes Sequence Diagram(s)sequenceDiagram
participant GerenciadorInstancias
participant crm-instancias-actions
participant EvolutionAPI
participant crm-inbound
participant Supabase
GerenciadorInstancias->>crm-instancias-actions: criarInstanciaAction
crm-instancias-actions->>EvolutionAPI: POST /instance/create
EvolutionAPI-->>crm-inbound: QRCODE_UPDATED or CONNECTION_UPDATE
crm-inbound->>Supabase: update crm_instancias
crm-inbound->>Supabase: insert crm_realtime_ping
sequenceDiagram
participant Thread
participant enviarMensagemAction
participant EvolutionAPI
participant Supabase
Thread->>enviarMensagemAction: send lead message
enviarMensagemAction->>EvolutionAPI: enviarTextoEvolution
enviarMensagemAction->>Supabase: persist crm_mensagens
enviarMensagemAction->>Supabase: insert crm_realtime_ping
enviarMensagemAction-->>Thread: ResultadoEnvio
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/api/crm/wa/webhook/[segredo]/route.ts (1)
73-84: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPersist the handler’s actual success state.
processarEventoWebhook()can returnok: false, but this code always writesprocessado: true. The webhook may correctly return HTTP 200 while the audit row falsely claims processing succeeded. Persistprocessado: r.okand retainr.errowhen present.🤖 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 `@app/api/crm/wa/webhook/`[segredo]/route.ts around lines 73 - 84, The webhook audit update in the route handler always marks `processado` as true even when `processarEventoWebhook()` returns `ok: false`. Update the `NextResponse.json` flow so the `crm_wa_eventos` update uses the actual result from `processarEventoWebhook` by persisting `processado: r.ok` and keeping `erro_processamento` populated from `r.erro` when present.
🧹 Nitpick comments (3)
docs/CRM-EVOLUTION-SETUP.md (1)
48-49: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winPin the Evolution image version or digest.
Using
atendai/evolution-api:latestmakes deployments non-reproducible and allows unreviewed upstream changes to alter production behavior. Pin a tested release, preferably by immutable digest.🤖 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 `@docs/CRM-EVOLUTION-SETUP.md` around lines 48 - 49, The Evolution API service definition is using a mutable image tag, which makes deployments non-reproducible; update the evolution-api image reference to a pinned, tested release or immutable digest so the setup stays stable over time. Locate the evolution-api entry in the setup document and replace the current latest tag with a fixed version/digest that matches the intended deployment target.components/GerenciadorFormularios.tsx (1)
196-200: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider guarding the render by
form.idfor future-proofing.Since
FORMScurrently only has the "comercial" entry, this is correct today. But renderingFormularioComercialunconditionally (rather than gated onform.id === "comercial") removes the safety net if another form type is added later without updating this block.Defensive guard
- <div style={{ marginTop: 16 }}> - <FormularioComercial - empresas={empresas} - responsaveis={responsaveisComercial} - clientesPorEmpresa={clientesPorEmpresa} - /> - </div> + <div style={{ marginTop: 16 }}> + {form.id === "comercial" && ( + <FormularioComercial + empresas={empresas} + responsaveis={responsaveisComercial} + clientesPorEmpresa={clientesPorEmpresa} + /> + )} + </div>🤖 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/GerenciadorFormularios.tsx` around lines 196 - 200, The render in GerenciadorFormularios is currently unconditional, which will bypass the intended form-type safety check. Update the conditional branch around FormularioComercial so it is explicitly gated by form.id === "comercial", using the existing form selection logic in GerenciadorFormularios to keep future form types from rendering this component accidentally.components/GerenciadorRelatoriosComerciais.tsx (1)
15-24: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueBRT calculation depends on the server running with TZ=UTC.
agora.getTime()is already a UTC epoch value; addinggetTimezoneOffset() * 60_000only produces the correct BRT offset if the host process's local timezone is UTC. If this ever runs in a non-UTC environment (e.g., local dev on a machine set to BRT), the computed default range shifts by hours, which can flip the "day" component near midnight boundaries.Safer alternative independent of host TZ
-function rangeMesAtual(): { inicio: string; fim: string } { - const agora = new Date() - const utcMs = agora.getTime() + agora.getTimezoneOffset() * 60_000 - const brt = new Date(utcMs - 3 * 60 * 60_000) - const ano = brt.getUTCFullYear() - const mes = String(brt.getUTCMonth() + 1).padStart(2, "0") - const dia = String(brt.getUTCDate()).padStart(2, "0") - return { inicio: `${ano}-${mes}-01`, fim: `${ano}-${mes}-${dia}` } -} +function rangeMesAtual(): { inicio: string; fim: string } { + const brt = new Date(Date.now() - 3 * 60 * 60_000) + const ano = brt.getUTCFullYear() + const mes = String(brt.getUTCMonth() + 1).padStart(2, "0") + const dia = String(brt.getUTCDate()).padStart(2, "0") + return { inicio: `${ano}-${mes}-01`, fim: `${ano}-${mes}-${dia}` } +}
Date.now()is UTC epoch already, so subtracting the fixed 3h BRT offset is TZ-agnostic — no need to round-trip throughgetTimezoneOffset().Also worth checking if this exact helper is duplicated in the tráfego manager referenced in the lib doc comment, to consolidate into a shared utility.
🤖 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/GerenciadorRelatoriosComerciais.tsx` around lines 15 - 24, The BRT range calculation in rangeMesAtual() is host-TZ dependent because it adds getTimezoneOffset() before subtracting the fixed BRT offset, so the default dates can shift on non-UTC machines. Update this helper to derive the BRT date from a UTC epoch source like Date.now() with only the fixed -3h adjustment, and keep the YYYY-MM-DD formatting logic in rangeMesAtual() unchanged. Also check whether the same date-range helper exists in the tráfego manager and consolidate it into a shared utility if so.
🤖 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 `@app/dashboard/crm/page.tsx`:
- Around line 73-76: The Thread component is being reused across different
leads, which can carry over draft and error state between conversations. Update
the lead rendering in the page component so the Thread instance is keyed by the
current lead identity, using the Thread symbol and the lead object’s id to force
a remount when switching conversations. Keep the existing lead conditional
rendering intact and apply the key directly where Thread is created.
In `@components/crm/Thread.tsx`:
- Around line 28-29: The send button can re-enable too early because Thread’s
useTransition pending state stops tracking after the first await. Replace the
reliance on pending in Thread/enviar with a local async loading flag that is set
before calling enviarMensagemAction and cleared only after it fully settles, and
use that flag to keep Enviar disabled so Enter/click cannot trigger a second
send while the request is still in flight.
In `@docs/CRM-EVOLUTION-SETUP.md`:
- Around line 69-70: The port mapping for Evolution is currently published on
all host interfaces, which conflicts with the intended Caddy-only exposure.
Update the docker compose port entry in the setup docs to bind 8080 explicitly
to localhost, using the existing ports block for the Evolution service so it
cannot be reached directly from external networks.
In `@lib/crm-inbound.ts`:
- Around line 114-127: The QR/status update path in the handler around the
crm_instancias update and crm_realtime_ping insert is swallowing Supabase write
failures and still returning success. Update the logic in the affected
handler(s) so each db.from(...).update(...) and db.from(...).insert(...) call
checks for errors, and if any write fails, return { ok: false, erro: ... }
instead of { ok: true }; use the existing handler flow in crm-inbound.ts to
propagate the failure so the route can record the failed processing state.
In `@lib/crm-instancias-actions.ts`:
- Around line 30-42: Add authorization checks to the CRM instance server actions
before any Supabase admin read/write. In listarInstancias and the related mutate
actions (the ones that call getSupabaseAdmin and operate on crm_instancias),
first validate the authenticated caller and ensure their role/company scope
permits the operation; reject unauthorized requests before querying or updating.
Use the existing action entry points and helpers around listarInstancias and the
other crm-instancias actions to centralize this guard so all instance listing,
deactivation, and reactivation paths enforce the same access rules.
- Around line 98-106: The retry flow in criarInstanciaEvolution handling is
incorrect: when instance/create fails, the local row stays saved but the UI
tells the user to use gerarQrAction, which only retries instance/connect and
cannot recover a never-created Evolution instance. Update the recovery path in
lib/crm-instancias-actions.ts so failed provisioning either retries the create
step for that instance or removes/recreates the local row before allowing QR
generation, and apply the same fix to the related flow in the 111-130 block.
In `@lib/crm-mensagens-actions.ts`:
- Around line 61-84: In the CRM message send flow that performs the
crm_mensagens insert, crm_leads update, and crm_realtime_ping insert, stop
ignoring Supabase write results and explicitly inspect each returned error. If
any of these operations fails, log/return the failure instead of continuing so
the caller does not get a false success when the WhatsApp send succeeded but
persistence did not. Update the same action that contains the
db.from(...).insert/update chain to propagate the error and only report success
after all three writes complete without errors.
- Around line 19-30: Both CRM server actions are missing the required CRM
authorization guard. In enviarMensagemAction, after confirming the current user
via getUsuarioAtual, add requererPermissao("crm") before proceeding with the
Supabase work. In marcarLeadComoLidoAction, add the same session check and CRM
permission check at the start so both actions are protected consistently. Use
the existing getUsuarioAtual and requererPermissao helpers in
lib/crm-mensagens-actions.ts to locate the affected paths.
---
Outside diff comments:
In `@app/api/crm/wa/webhook/`[segredo]/route.ts:
- Around line 73-84: The webhook audit update in the route handler always marks
`processado` as true even when `processarEventoWebhook()` returns `ok: false`.
Update the `NextResponse.json` flow so the `crm_wa_eventos` update uses the
actual result from `processarEventoWebhook` by persisting `processado: r.ok` and
keeping `erro_processamento` populated from `r.erro` when present.
---
Nitpick comments:
In `@components/GerenciadorFormularios.tsx`:
- Around line 196-200: The render in GerenciadorFormularios is currently
unconditional, which will bypass the intended form-type safety check. Update the
conditional branch around FormularioComercial so it is explicitly gated by
form.id === "comercial", using the existing form selection logic in
GerenciadorFormularios to keep future form types from rendering this component
accidentally.
In `@components/GerenciadorRelatoriosComerciais.tsx`:
- Around line 15-24: The BRT range calculation in rangeMesAtual() is host-TZ
dependent because it adds getTimezoneOffset() before subtracting the fixed BRT
offset, so the default dates can shift on non-UTC machines. Update this helper
to derive the BRT date from a UTC epoch source like Date.now() with only the
fixed -3h adjustment, and keep the YYYY-MM-DD formatting logic in
rangeMesAtual() unchanged. Also check whether the same date-range helper exists
in the tráfego manager and consolidate it into a shared utility if so.
In `@docs/CRM-EVOLUTION-SETUP.md`:
- Around line 48-49: The Evolution API service definition is using a mutable
image tag, which makes deployments non-reproducible; update the evolution-api
image reference to a pinned, tested release or immutable digest so the setup
stays stable over time. Locate the evolution-api entry in the setup document and
replace the current latest tag with a fixed version/digest that matches the
intended deployment target.
🪄 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: 98778b86-aa1f-4b1a-9fbe-c4a9aeed7103
📒 Files selected for processing (17)
app/api/crm/wa/webhook/[segredo]/route.tsapp/dashboard/configuracoes/page.tsxapp/dashboard/crm/conexoes/page.tsxapp/dashboard/crm/page.tsxcomponents/GerenciadorFormularios.tsxcomponents/GerenciadorRelatoriosComerciais.tsxcomponents/crm/CrmRealtime.tsxcomponents/crm/GerenciadorInstancias.tsxcomponents/crm/ListaConversas.tsxcomponents/crm/Thread.tsxdocs/CRM-EVOLUTION-SETUP.mdlib/crm-inbound.tslib/crm-instancias-actions.tslib/crm-leads.tslib/crm-mensagens-actions.tslib/evolution.tslib/relatorios-comerciais.ts
| <div style={{ minWidth: 0 }}> | ||
| {lead ? ( | ||
| <Thread lead={lead} mensagens={mensagens} /> | ||
| ) : ( |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map relevant files first
git ls-files 'app/dashboard/crm/*' 'app/dashboard/**/Thread*' 'app/**/Thread*' 'components/**/Thread*'
# Find the page and Thread component definitions/usages
rg -n --hidden --no-ignore-vcs '\bThread\b|lead=' app/dashboard/crm app -g '!**/node_modules/**'
# If the page file is small enough, print it with line numbers
if [ -f app/dashboard/crm/page.tsx ]; then
wc -l app/dashboard/crm/page.tsx
cat -n app/dashboard/crm/page.tsx | sed -n '1,220p'
fiRepository: sejaanomalo-web/metas-anomalo
Length of output: 3957
🏁 Script executed:
#!/bin/bash
set -euo pipefail
wc -l components/crm/Thread.tsx
cat -n components/crm/Thread.tsx | sed -n '1,260p'Repository: sejaanomalo-web/metas-anomalo
Length of output: 5823
Add key={lead.id} to remount Thread per conversation. Without it, switching leads reuses the same client instance, so the draft and error state can bleed into the next thread.
🤖 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 `@app/dashboard/crm/page.tsx` around lines 73 - 76, The Thread component is
being reused across different leads, which can carry over draft and error state
between conversations. Update the lead rendering in the page component so the
Thread instance is keyed by the current lead identity, using the Thread symbol
and the lead object’s id to force a remount when switching conversations. Keep
the existing lead conditional rendering intact and apply the key directly where
Thread is created.
| const [pending, startTransition] = useTransition() | ||
| const router = useRouter() |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Thread.tsx outline ==\n'
ast-grep outline components/crm/Thread.tsx --view expanded || true
printf '\n== Relevant lines in Thread.tsx ==\n'
nl -ba components/crm/Thread.tsx | sed -n '1,220p'
printf '\n== Search for useTransition/startTransition/enviarMensagemAction ==\n'
rg -n "useTransition|startTransition|enviarMensagemAction|disabled=.*pending|setPending|pending" components/crm -SRepository: sejaanomalo-web/metas-anomalo
Length of output: 388
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== Thread.tsx outline ==\n'
ast-grep outline components/crm/Thread.tsx --view expanded || true
printf '\n== Relevant lines in Thread.tsx ==\n'
nl -ba components/crm/Thread.tsx | sed -n '1,220p'
printf '\n== Search for useTransition/startTransition/enviarMensagemAction/disabled logic ==\n'
rg -n "useTransition|startTransition|enviarMensagemAction|disabled=.*pending|setPending|pending" components/crm -SRepository: sejaanomalo-web/metas-anomalo
Length of output: 388
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== package.json react versions ==\n'
if [ -f package.json ]; then
rg -n '"react"|"react-dom"|"next"' package.json -n -S
fi
printf '\n== Thread.tsx with line numbers ==\n'
sed -n '1,240p' components/crm/Thread.tsx | cat -n
printf '\n== Nearby references in components/crm ==\n'
rg -n "useTransition|startTransition|pending|enviarMensagemAction|router.refresh|disabled=" components/crm -SRepository: sejaanomalo-web/metas-anomalo
Length of output: 8613
🌐 Web query:
React 18.3.1 useTransition async callback pending state first await official docs
💡 Result:
In React 18.3.1, the useTransition hook does not natively support asynchronous callbacks in the way introduced in React 19 [1][2]. Specifically, if you pass an async function to the startTransition returned by useTransition, the transition completes immediately at the first await [2]. Consequently, the isPending state will revert to false even though the asynchronous operation is still in progress [3][2]. To manage isPending correctly in React 18.3.1 during an asynchronous operation, you must manually ensure that any state updates occurring after an await are also wrapped in startTransition [4][5][6]. However, even with this workaround, the isPending flag will not remain true for the entire duration of the asynchronous callback [4][6][2]. The native support for async actions, where isPending automatically stays true until the entire asynchronous callback (including all awaited work) completes, was officially introduced in React 19 [1][7][2].
Citations:
- 1: docs: update startTransition async guidance for React 19 reactjs/react.dev#8496
- 2: https://fixdevs.com/blog/react-use-transition-not-working/
- 3: https://stackoverflow.com/questions/79290597/usetransitions-ispending-not-awaiting-api-call-completion-before-changing-to-fa
- 4: https://react.dev/reference/react/startTransition
- 5: useTransition(): startTransition after await vercel/next.js#79549
- 6: https://uk.react.dev/reference/react/useTransition
- 7: https://github.com/typescript-cheatsheets/react/blob/main/docs/basic/getting-started/concurrent.md
🌐 Web query:
site:react.dev useTransition async actions pending first await React 18 19
💡 Result:
In React 19, useTransition allows you to pass async functions to startTransition [1][2]. When you use an async function in a transition, the isPending state becomes true immediately when the transition starts and remains true until all actions initiated within the transition are completed [3]. A critical detail for React 19 is how state updates are handled after an await expression within an async transition [3][4]. React calls the transition function synchronously, and any state updates scheduled during that synchronous phase are tracked as part of the transition [3]. However, if you perform an async operation (e.g., fetch) and need to update state after the await, React requires that those subsequent state updates be wrapped in an additional startTransition to ensure they are correctly associated with the pending state [3][4][5]. Without this additional wrapping, updates occurring after an await may not correctly maintain the isPending state, or they may be treated as standard, non-transition updates [3][4]. While the initial isPending flag is triggered at the start of the transition, maintaining that "pending" UI feedback throughout the entire async lifecycle—especially after asynchronous data is returned—often necessitates explicit handling or the use of higher-level hooks like useActionState, which are designed to manage these async patterns and their pending states automatically [4][5].
Citations:
- 1: https://react.dev/blog/2024/12/05/react-19
- 2: https://az.react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024
- 3: https://uk.react.dev/reference/react/useTransition
- 4: https://react.dev/reference/react/useActionState
- 5: https://react.dev/reference/rsc/server-functions
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== GerenciadorInstancias.tsx relevant lines ==\n'
sed -n '1,360p' components/crm/GerenciadorInstancias.tsx | cat -n
printf '\n== crm-mensagens-actions.ts ==\n'
sed -n '1,260p' lib/crm-mensagens-actions.ts | cat -nRepository: sejaanomalo-web/metas-anomalo
Length of output: 16924
Keep send disabled until the async action settles In React 18.3.1, startTransition drops pending at the first await, so Enviar can re-enable while enviarMensagemAction is still running. That lets Enter/click trigger the same WhatsApp send twice. Track loading with a local async flag around enviar() instead.
🤖 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/Thread.tsx` around lines 28 - 29, The send button can
re-enable too early because Thread’s useTransition pending state stops tracking
after the first await. Replace the reliance on pending in Thread/enviar with a
local async loading flag that is set before calling enviarMensagemAction and
cleared only after it fully settles, and use that flag to keep Enviar disabled
so Enter/click cannot trigger a second send while the request is still in
flight.
| ports: | ||
| - "8080:8080" # atrás do Caddy, não exposto direto |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Bind port 8080 to localhost explicitly.
"8080:8080" publishes Evolution on all host interfaces, contradicting the claim that it is only reachable through Caddy. Use "127.0.0.1:8080:8080" so a firewall mistake cannot expose the API directly.
🤖 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 `@docs/CRM-EVOLUTION-SETUP.md` around lines 69 - 70, The port mapping for
Evolution is currently published on all host interfaces, which conflicts with
the intended Caddy-only exposure. Update the docker compose port entry in the
setup docs to bind 8080 explicitly to localhost, using the existing ports block
for the Evolution service so it cannot be reached directly from external
networks.
| await db | ||
| .from("crm_instancias") | ||
| .update({ | ||
| ultimo_qr: qr, | ||
| status_conexao: "qrcode", | ||
| updated_at: new Date().toISOString(), | ||
| }) | ||
| .eq("id", inst.id) | ||
|
|
||
| await db | ||
| .from("crm_realtime_ping") | ||
| .insert({ empresa_slug: inst.empresa_slug as string, kind: "conexao" }) | ||
|
|
||
| return { ok: true, info: "qrcode atualizado" } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Propagate Supabase write failures.
Both handlers ignore errors from crm_instancias.update() and crm_realtime_ping.insert(), then return { ok: true }. A transient database failure can therefore lose the QR/status update while the webhook responds successfully and will not retry. Capture each error, return { ok: false, erro: ... }, and let the route record the failed processing state.
Also applies to: 163-169
🤖 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 114 - 127, The QR/status update path in the
handler around the crm_instancias update and crm_realtime_ping insert is
swallowing Supabase write failures and still returning success. Update the logic
in the affected handler(s) so each db.from(...).update(...) and
db.from(...).insert(...) call checks for errors, and if any write fails, return
{ ok: false, erro: ... } instead of { ok: true }; use the existing handler flow
in crm-inbound.ts to propagate the failure so the route can record the failed
processing state.
| /** Todas as instâncias (ativas e inativas) — a UI decide o que mostrar. */ | ||
| export async function listarInstancias(): Promise<CrmInstanciaRow[]> { | ||
| const db = getSupabaseAdmin() | ||
| if (!db) return [] | ||
| const { data, error } = await db | ||
| .from("crm_instancias") | ||
| .select("*") | ||
| .order("created_at", { ascending: false }) | ||
| if (error) { | ||
| console.error("[crm_instancias] list error", error.message) | ||
| return [] | ||
| } | ||
| return (data ?? []) as CrmInstanciaRow[] |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Enforce authorization before using the admin client.
These server actions list and mutate CRM instances through the service-role client without checking the caller’s identity, role, or company scope. Since Server Actions are callable from the client, a user who can invoke them could enumerate all instances or deactivate/reactivate arbitrary records by ID. Validate the authenticated user and permitted company/role inside every action before querying or updating.
Also applies to: 59-92, 111-168
🤖 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-instancias-actions.ts` around lines 30 - 42, Add authorization checks
to the CRM instance server actions before any Supabase admin read/write. In
listarInstancias and the related mutate actions (the ones that call
getSupabaseAdmin and operate on crm_instancias), first validate the
authenticated caller and ensure their role/company scope permits the operation;
reject unauthorized requests before querying or updating. Use the existing
action entry points and helpers around listarInstancias and the other
crm-instancias actions to centralize this guard so all instance listing,
deactivation, and reactivation paths enforce the same access rules.
| const r = await criarInstanciaEvolution(instanceName) | ||
| revalidarConexoes() | ||
| if (!r.ok) { | ||
| return { | ||
| ok: true, | ||
| erro: `Instância salva, mas a Evolution falhou (${r.erro}). Use "Gerar QR" para tentar de novo.`, | ||
| } | ||
| } | ||
| return { ok: true } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Retry instance creation, not only QR generation.
When criarInstanciaEvolution() fails, the local row remains, but the UI directs the user to gerarQrAction; that action calls only instance/connect/:instanceName, which requires the Evolution instance to already exist. A failed instance/create therefore cannot be recovered through the advertised retry flow. Either retry instance/create for rows that were never provisioned or remove/recreate the local row.
Also applies to: 111-130
🤖 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-instancias-actions.ts` around lines 98 - 106, The retry flow in
criarInstanciaEvolution handling is incorrect: when instance/create fails, the
local row stays saved but the UI tells the user to use gerarQrAction, which only
retries instance/connect and cannot recover a never-created Evolution instance.
Update the recovery path in lib/crm-instancias-actions.ts so failed provisioning
either retries the create step for that instance or removes/recreates the local
row before allowing QR generation, and apply the same fix to the related flow in
the 111-130 block.
| export async function enviarMensagemAction( | ||
| leadId: string, | ||
| texto: string | ||
| ): Promise<ResultadoEnvio> { | ||
| const textoLimpo = texto.trim() | ||
| if (!textoLimpo) return { ok: false, erro: "Mensagem vazia." } | ||
|
|
||
| const db = getSupabaseAdmin() | ||
| if (!db) return { ok: false, erro: "Supabase indisponível." } | ||
|
|
||
| const usuario = await getUsuarioAtual() | ||
| if (!usuario) return { ok: false, erro: "Sessão expirada." } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
git ls-files 'lib/crm-mensagens-actions.ts' 'app/dashboard/crm/page.tsx' | sed 's#^`#FILE` #'
echo
for f in lib/crm-mensagens-actions.ts app/dashboard/crm/page.tsx; do
echo "### $f"
wc -l "$f"
echo
sed -n '1,220p' "$f"
echo
doneRepository: sejaanomalo-web/metas-anomalo
Length of output: 5717
🏁 Script executed:
#!/bin/bash
set -euo pipefail
for f in lib/auth.ts components/crm/Thread.tsx components/crm/ListaConversas.tsx; do
echo "### $f"
wc -l "$f"
echo
sed -n '1,240p' "$f"
echo
doneRepository: sejaanomalo-web/metas-anomalo
Length of output: 15417
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "export async function getUsuarioAtual|function getUsuarioAtual|getUsuarioAtual\(" lib/auth.ts lib/crm-mensagens-actions.ts
echo
sed -n '220,330p' lib/auth.tsRepository: sejaanomalo-web/metas-anomalo
Length of output: 4162
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "enviarMensagemAction|marcarLeadComoLidoAction" .Repository: sejaanomalo-web/metas-anomalo
Length of output: 630
Add CRM authorization checks to both server actions lib/crm-mensagens-actions.ts:19-30,92-97
enviarMensagemAction only checks that a session exists; it still needs requererPermissao("crm"). marcarLeadComoLidoAction has no session or permission guard at all, so add the same check there too.
🤖 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-mensagens-actions.ts` around lines 19 - 30, Both CRM server actions
are missing the required CRM authorization guard. In enviarMensagemAction, after
confirming the current user via getUsuarioAtual, add requererPermissao("crm")
before proceeding with the Supabase work. In marcarLeadComoLidoAction, add the
same session check and CRM permission check at the start so both actions are
protected consistently. Use the existing getUsuarioAtual and requererPermissao
helpers in lib/crm-mensagens-actions.ts to locate the affected paths.
| await db.from("crm_mensagens").insert({ | ||
| lead_id: leadId, | ||
| instancia_id: inst.id, | ||
| empresa_slug: lead.empresa_slug, | ||
| direcao: "out", | ||
| tipo: "texto", | ||
| conteudo: textoLimpo, | ||
| wa_message_id: envio.messageId ?? null, | ||
| status: envio.ok ? "enviada" : "falha", | ||
| erro: envio.ok ? null : envio.erro ?? null, | ||
| autor_id: usuario.id, | ||
| autor_nome: usuario.nome, | ||
| from_me: true, | ||
| wa_timestamp: agora, | ||
| }) | ||
|
|
||
| await db | ||
| .from("crm_leads") | ||
| .update({ ultima_interacao_em: agora, updated_at: agora }) | ||
| .eq("id", leadId) | ||
|
|
||
| await db | ||
| .from("crm_realtime_ping") | ||
| .insert({ empresa_slug: lead.empresa_slug, lead_id: leadId, kind: "msg" }) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Supabase write errors are silently ignored — message can be lost while the caller still sees success.
None of the three writes (message insert, lead update, realtime ping insert) check the returned error. If the crm_mensagens insert fails (RLS, constraint, transient DB issue) while envio.ok is true, the function still returns { ok: true } even though the message was never persisted — it disappears from the thread entirely despite having actually been sent via WhatsApp. Same silent-failure risk applies to the lead's ultima_interacao_em update and the realtime ping.
🐛 Suggested fix
- await db.from("crm_mensagens").insert({
+ const { error: insertError } = await db.from("crm_mensagens").insert({
lead_id: leadId,
instancia_id: inst.id,
empresa_slug: lead.empresa_slug,
direcao: "out",
tipo: "texto",
conteudo: textoLimpo,
wa_message_id: envio.messageId ?? null,
status: envio.ok ? "enviada" : "falha",
erro: envio.ok ? null : envio.erro ?? null,
autor_id: usuario.id,
autor_nome: usuario.nome,
from_me: true,
wa_timestamp: agora,
})
+ if (insertError) {
+ console.error("[crm_mensagens] insert error", insertError.message)
+ return { ok: false, erro: "Falha ao registrar a mensagem." }
+ }🤖 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-mensagens-actions.ts` around lines 61 - 84, In the CRM message send
flow that performs the crm_mensagens insert, crm_leads update, and
crm_realtime_ping insert, stop ignoring Supabase write results and explicitly
inspect each returned error. If any of these operations fails, log/return the
failure instead of continuing so the caller does not get a false success when
the WhatsApp send succeeded but persistence did not. Update the same action that
contains the db.from(...).insert/update chain to propagate the error and only
report success after all three writes complete without errors.
Summary by CodeRabbit
New Features
Bug Fixes