mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 09:34:46 +08:00
Dois consertos de borda no PR #322. O trabalho dele é sólido — a tripla de migration está completa nas duas, o baseline passa em install E update com `ON_ERROR_STOP=1`, não há anti-pattern nº 10, e as funções novas revogam as duas origens de EXECUTE. O que segue é o que a medição encontrou nas pontas. ─── 1. O falso-verde do `resend` — na rota que o próprio PR adiciona ────────── `app/api/v1/automation-rules/runs/[runId]/resend/route.ts:98` const failed = results.filter((r) => r.status === "failed").length; const status = failed === 0 ? "success" : … `failed === 0` é VERDADEIRO para lista vazia. Uma regra que perdeu as ações de webhook — o operador removeu a ação no editor, e o botão "Reenviar" segue renderizado no run antigo — grava uma linha `status: "success"` com `actions_result: []`. A tela mostra o toast verde e o badge "Sucesso" com corpo vazio: o operador é informado de um reenvio que não aconteceu. É exatamente a classe de defeito que este PR existe para fechar — "a automação para de dizer que deu certo quando não deu" —, reaparecendo pela porta nova. 409 `no_actions_to_resend`, código próprio pelo mesmo critério que criou o `event_gone` nesta mesma rota: o estado do MUNDO mudou desde o run original, e isso é informação, não erro do chamador. ─── 2. `limitarCampos` cortava só string; valor aninhado entrava inteiro ────── `lib/webhooks/captacao.ts:80` cortava o ramo `typeof valor === "string"` e o resto caía num `else` que guardava o valor INTEIRO. Um campo aninhado (array de carrinho, objeto de endereço, qualquer JSON) entrava sem limite — enquanto a prosa do módulo e o cálculo de retenção (`retencao-da-captacao.ts:20`, que dimensiona a tabela em "60 campos × 2.000 caracteres") prometiam o contrário. Quem manda o corpo é o site do cliente, então o tamanho não é escolha nossa: é a superfície de quem quiser encher a tabela que a tela lê. `limitarValor` aplica o teto a qualquer valor. Pequeno passa INTACTO com o tipo original (número segue número, objeto segue objeto); só quando o JSON passa do teto vira a string cortada, porque guardar "quase o objeto" seria guardar um objeto que não é o que chegou. Referência circular — em que `JSON.stringify` LANÇA — vira `[valor não serializável]` em vez de derrubar a captação: é do formulário estranho que quem depura mais precisa do registro. **Não havia um único teste** (`git grep limitarCampos -- tests` voltava vazio). Agora são 6, e a sabotagem bate a previsão escrita antes: revertendo para o corte só-string, **3 falhas** (array, objeto, circular) e os DOIS controles verdes — o de valor pequeno intacto existe porque serializar tudo passaria nos outros três e destruiria a feature. (Meu primeiro teste falhou por erro MEU: media `JSON.stringify` do resultado já cortado, e as aspas/escapes inflam o tamanho. A asserção certa mede o valor guardado, não a re-serialização dele.) ─── Sobre o relógio: quatro sessões, o mesmo bug ────────────────────────────── Este PR também achou o defeito do relógio de parede, com medição própria (22:29 local, 01:37 UTC) — depois do #319 e do #323. Quando fui resolver o conflito, a sessão deste PR já havia mergeado a main e resolvido do MESMO jeito: clock declarado no topo, as duas guardas usando `clock()`. Convergência independente de novo, e nada a fazer — só confirmar que as duas guardas ficaram cobertas (`grep -n 'const agora = clock()\|janelaDeAtendimento, clock()'` → 1087 e 1179). ─── O que NÃO foi consertado aqui, e por quê ────────────────────────────────── `webhook_lead_captures` não está em `lib/database.types.ts`. Medido antes de agir: `agent_inbox_items`, `channel_knobs`, `api_audit_log` e `platform_branding` também não estão, e não há script `gen:types` no `package.json`. É dívida antiga e generalizada do repo — o PR seguiu a prática, não criou o problema. Consertar só a tabela dele seria conserto por instância, e inflaria este PR com dívida alheia. `typecheck` exit 0 · `lint` 0 erros · `lint:channels` ok · `pnpm test:unit` **483 arquivos, 5402 testes** · `pnpm test:db` **115 arquivos, 857 testes, exit 0** (rodado às 08:02 sobre a base anterior, com o baseline em install E update). NÃO MEDIDO: nada foi dirigido num browser (DoD 12) — a tela nova de Capturas, o badge do resend e a aba de Atividade foram lidos no código, não vistos. O `.or()` duplicado em `lead-captures/route.ts:70,80` (busca livre + cursor na mesma query) não foi exercitado contra um PostgREST vivo: se filtros repetidos não forem ANDados, a segunda página de uma busca devolve o conjunto errado em silêncio. E a poda de captação nunca rodou sobre meses de histórico real. Co-authored-by: melgarafael <melgarafael@users.noreply.github.com>
lib/api/
Helpers e convenções compartilhadas por toda rota /api/v1/*.
wrappers.ts—ok(data, opts)/fail(code, message, status, opts)/noContent()+ tiposApiSuccess<T>/ApiErrorerrors.ts—ApiErrorCodes(constante canônica de códigos)
Exemplo
import { ok, fail } from "@/lib/api/wrappers";
import { ApiErrorCodes } from "@/lib/api/errors";
export async function GET(req: Request) {
const data = await fetchSomething();
if (!data) return fail(ApiErrorCodes.not_found, "Lead não encontrado", 404);
return ok(data, { meta: { cursor: nextCursor, has_more: true } });
}
A adicionar (próximas specs)
auth.ts— extrai user / tenant da request (cookie OU bearer); valida MFA; retornaAuthContextidempotency.ts— middleware que validaIdempotency-Keyvia Upstash (TTL 24h)rate-limit.ts— sliding window via Upstash; injeta headersX-RateLimit-*pagination.ts— encode/decode de cursor opaco base64 + HMACaudit.ts— fire-and-forget write emapi_audit_logcors.ts— allowlist por tenant