Skip to content

fix(quotes): alinhar shipping_type=fob vs fob_pre em UI/RPC/dados#5

Closed
adm01-debug wants to merge 1 commit into
mainfrom
qa/fix-shipping-type-fob-consistency
Closed

fix(quotes): alinhar shipping_type=fob vs fob_pre em UI/RPC/dados#5
adm01-debug wants to merge 1 commit into
mainfrom
qa/fix-shipping-type-fob-consistency

Conversation

@adm01-debug
Copy link
Copy Markdown
Owner

@adm01-debug adm01-debug commented May 19, 2026

O que

Corrige bug financeiro silencioso em shipping_type que causava divergência entre UI, view, banco e RPC server-side.

Diagnóstico

Em 18/mai (commit 72c8639), o frontend (src/hooks/quotes/quoteHelpers.ts:45) mudou para considerar shipping_cost APENAS quando shipping_type='fob_pre'. Mas 3 outras camadas ficaram com a regra antiga 'fob' || 'fob_pre':

Camada Antes Agora (este PR)
quoteHelpers.ts:45 (salva no banco) ✅ Só 'fob_pre' (mudou em 18/mai) mantido
QuoteTotalsSummary.tsx:32 (exibe UI) 'fob' || 'fob_pre' ✅ Só 'fob_pre'
useQuoteViewData.ts:72 (view) 'fob' || 'fob_pre' ✅ Só 'fob_pre'
fn_quotes_recalc (RPC server) IN ('fob','fob_pre') ✅ Só 'fob_pre'

Impacto do bug: orçamentos com shipping_type='fob' AND shipping_cost>0 mostravam valor X na UI mas SALVAVAM Y no banco (o que o trigger fn_quotes_recalc calculasse dependia de se quote_items foi alterado ou não). Cliente fecha contrato com valor visto, sistema persiste outro.

Solução

Semântica adotada (alinhada com o refactor de 18/mai):

  • cif → Cortesia (sem custo no orçamento)
  • fob → Cliente paga frete diretamente (sem cost no orçamento)
  • fob_pre → FOB Pré-negociado (cost no orçamento, repassado ao cliente)

Arquivos

Frontend (3 arquivos, 8+/5-)

  1. src/components/quotes/QuoteTotalsSummary.tsxshippingValue só soma shippingCost se 'fob_pre'. Label da UI ajustado: 'fob' → "FOB — Por conta do cliente" (sem cost); 'fob_pre' → "FOB Pré-negociado (R$ X)".
  2. src/pages/quotes/quote-view/useQuoteViewData.ts — idem.
  3. src/types/quote.ts — comentário doc atualizado.

Backend (2 migrations novas — aplicar em F3)

  1. 20260519225000_fix_fn_quotes_recalc_only_fob_pre.sqlCREATE OR REPLACE do trigger RPC, preservando 100% da lógica de markup/discount/immutability check. Apenas a expressão de shipping foi alterada (IN ('fob','fob_pre')= 'fob_pre').
  2. 20260519225100_backfill_legacy_fob_with_cost.sql — converte orçamentos legados (shipping_type='fob' AND shipping_cost>0) para 'fob_pre' (que é o que eles eram semanticamente). NÃO toca orçamentos com status approved ou converted. Idempotente.

Aplicação das migrations

⚠️ As 2 migrations NÃO foram aplicadas no banco em uso (pqpdolkaeqlyzpdpbizo é gerenciado pelo Lovable). Vão aplicar na Fase 3 (transferência Supabase).

Para validar antes de aplicar:

SELECT id, status, shipping_type, shipping_cost, total 
FROM public.quotes 
WHERE shipping_type IN ('fob','fob_pre');

Validação

✅ Edits TS cirúrgicos (8 ins, 5 del).
✅ SQL: função recriada com mesma assinatura/lógica, só expressão IN trocada.
✅ Backfill com WHERE explícito + proteção contra approved/converted.

Risco

  • Frontend: baixo. Mudança apenas em string de comparação + texto de label. Sem lógica nova.
  • Migration RPC: baixo. CREATE OR REPLACE mantém assinatura (não invalida triggers).
  • Backfill: médio. Modifica dados (de fob para fob_pre). Mitigação: só converte quando shipping_cost>0 (caso onde semanticamente já era 'fob_pre'); rastreável via updated_at.

Notas


Summary by cubic

Aligns shipping calculation so only 'fob_pre' adds shipping_cost to totals across UI, view, and RPC. Fixes the mismatch where 'fob' + cost showed one total in the UI but saved another, and backfills legacy data.

  • Bug Fixes

    • UI total and labels: only 'fob_pre' adds shipping_cost; 'fob' shows “por conta do cliente”; 'cif' is courtesy.
    • Quote view hook: total uses shipping_cost only when shipping_type = 'fob_pre'.
  • Migration

    • Update RPC function fn_quotes_recalc_subtotal_from_items to include shipping_cost only for 'fob_pre'.
    • Backfill: convert quotes with shipping_type = 'fob' and shipping_cost > 0 to 'fob_pre'; skips statuses 'approved' and 'converted'. Apply RPC update before the backfill.

Written for commit 8d77822. Summary will update on new commits. Review in cubic

BUG: em 18/mai o helper de calculo (quoteHelpers.ts:45) mudou pra considerar
shipping_cost APENAS quando shipping_type='fob_pre'. Mas 3 outras camadas
ficaram com a regra antiga ('fob' OR 'fob_pre'):

  - QuoteTotalsSummary.tsx (UI que mostra o total na visualizacao)
  - useQuoteViewData.ts (view do orcamento)
  - fn_quotes_recalc_subtotal_from_items (trigger RPC server-side)

Resultado: orcamentos com shipping_type='fob' AND shipping_cost>0
mostravam total X na UI mas SALVAVAM Y no banco (o que o trigger recalculasse
dependia de se quote_items foi alterado ou nao). Bug financeiro silencioso.

FIX: alinhar TODAS as 4 camadas com a semantica adotada pelo refactor:
  - cif     -> Cortesia (sem custo)
  - fob     -> Cliente paga frete (sem cost no orcamento)
  - fob_pre -> FOB Pre-negociado (cost no orcamento, repassado ao cliente)

5 arquivos:
  1. QuoteTotalsSummary.tsx: shipping_type==='fob_pre' apenas
  2. useQuoteViewData.ts: shipping_type==='fob_pre' apenas
  3. types/quote.ts: comentario doc atualizado
  4. NOVO migration fix_fn_quotes_recalc_only_fob_pre: trigger RPC alinhado
  5. NOVO migration backfill_legacy_fob_with_cost: converte orcamentos legados
     com shipping_type='fob' AND shipping_cost>0 para 'fob_pre' (semantica
     real); idempotente, nao toca approved/converted

Validacao TS: edits cirurgicos (8 ins, 5 del em 3 arquivos).
Validacao SQL: funcao recriada via CREATE OR REPLACE, preserva 100% da
logica de markup/discount/immutability. Backfill com WHERE explicito.

NOTA: as 2 migrations sao APLICADAS em F3 (transferencia Supabase), nao
agora — banco em uso (pqp) e gerenciado pelo Lovable web. Apos aplicar,
verificar com:
  SELECT id, status, shipping_type, shipping_cost, total FROM quotes
    WHERE shipping_type IN ('fob','fob_pre');
Copilot AI review requested due to automatic review settings May 19, 2026 15:05
@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

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

Project Deployment Actions Updated (UTC)
we-dream-big Building Building Preview, Comment May 19, 2026 3:06pm

@adm01-debug adm01-debug deleted the qa/fix-shipping-type-fob-consistency branch May 19, 2026 15:06
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6688ed7c-3521-4765-8c5a-78175ebf6714

📥 Commits

Reviewing files that changed from the base of the PR and between 6c37d43 and 8d77822.

📒 Files selected for processing (5)
  • src/components/quotes/QuoteTotalsSummary.tsx
  • src/pages/quotes/quote-view/useQuoteViewData.ts
  • src/types/quote.ts
  • supabase/migrations/20260519225000_fix_fn_quotes_recalc_only_fob_pre.sql
  • supabase/migrations/20260519225100_backfill_legacy_fob_with_cost.sql

Walkthrough

O PR altera a semântica de cálculo de frete em orçamentos: shipping_cost agora entra no total apenas quando shipping_type === 'fob_pre'. A mudança é aplicada no frontend (QuoteTotalsSummary e useQuoteViewData), na função trigger do banco (fn_quotes_recalc_subtotal_from_items), e dados legados são convertidos via backfill.

Changes

Alteração de semântica de frete FOB

Layer / File(s) Summary
Contrato de tipo e documentação
src/types/quote.ts
Comentário do campo shipping_type atualizado para detalhar os valores possíveis: 'cif', 'fob', 'fob_pre'.
Cálculo de frete no frontend
src/components/quotes/QuoteTotalsSummary.tsx, src/pages/quotes/quote-view/useQuoteViewData.ts
shippingValue passa a ser atribuído apenas quando shippingType === "fob_pre"; UI da descrição de frete diferencia fob_pre ("FOB Pré-negociado" com valor) de fob ("Por conta do cliente"). useQuoteViewData alinha o cálculo de shipValue na proposalData.
Função trigger de recálculo no banco
supabase/migrations/20260519225000_fix_fn_quotes_recalc_only_fob_pre.sql
Migration reescreve fn_quotes_recalc_subtotal_from_items() para somar shipping_cost apenas quando shipping_type = 'fob_pre'. Mantém guarda de imutabilidade para status approved/converted. Preserva lógica de subtotal, markup e desconto.
Backfill de dados legados
supabase/migrations/20260519225100_backfill_legacy_fob_with_cost.sql
Migração converte registros com shipping_type='fob' e shipping_cost > 0 para fob_pre (excluindo imutáveis). Registra quantidade de conversões via NOTICE e GET DIAGNOSTICS ROW_COUNT.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Frontend
  participant API as API/Hook
  participant DB as Banco
  Client->>API: Quote com shipping_type=fob_pre e shipping_cost
  API->>Client: proposalData com shipValue incluído
  Client->>Client: Renderiza "FOB Pré-negociado" com valor
  alt Item do quote alterado
    DB->>DB: fn_quotes_recalc_subtotal_from_items disparado
    DB->>DB: Verifica status (não approved/converted)
    DB->>DB: Calcula total COM shipping_cost (fob_pre)
    DB->>DB: UPDATE quotes.total
  end
  Client->>API: Quote com shipping_type=fob
  API->>Client: proposalData com shipValue=0
  Client->>Client: Renderiza "Por conta do cliente" sem valor
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • adm01-debug/Promo_Gifts#212: Também altera a mesma função trigger fn_quotes_recalc_subtotal_from_items(), introduzindo lógica de shipping_type para cálculo de frete — verifique se há conflito ou duplicação de regras.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Alinha a semântica de shipping_type entre frontend (UI + view) e backend (trigger RPC + dados) para que shipping_cost só componha o total quando shipping_type = 'fob_pre', eliminando divergências financeiras silenciosas.

Changes:

  • Frontend: cálculo/label de frete passam a considerar custo apenas em fob_pre (UI e useQuoteViewData).
  • Backend: fn_quotes_recalc_subtotal_from_items passa a somar frete apenas em fob_pre.
  • Dados: adiciona migration de backfill para converter fob com shipping_cost > 0 para fob_pre.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/components/quotes/QuoteTotalsSummary.tsx Ajusta regra de soma do frete e labels exibidos no resumo de totais.
src/pages/quotes/quote-view/useQuoteViewData.ts Ajusta cálculo do total no payload usado para render/PDF para considerar frete apenas em fob_pre.
src/types/quote.ts Atualiza comentário documental para explicitar a semântica de shipping_type.
supabase/migrations/20260519225000_fix_fn_quotes_recalc_only_fob_pre.sql Atualiza função trigger para incluir frete somente em fob_pre.
supabase/migrations/20260519225100_backfill_legacy_fob_with_cost.sql Backfill de dados para reclassificar fob+cost legado como fob_pre.
Comments suppressed due to low confidence (2)

supabase/migrations/20260519225100_backfill_legacy_fob_with_cost.sql:39

  • O UPDATE (e o comentário acima) dizem que “não recalcula totals”, mas esta migration converte exatamente os casos do bug (shipping_type='fob' com shipping_cost>0) onde o total pode ter sido persistido sem o frete. Apenas trocar shipping_type para fob_pre não corrige esses totals e pode manter divergência em telas/rotinas que usam quotes.total. Considere atualizar total na mesma migration (p.ex. com base em subtotal/discount_amount + shipping_cost) ou forçar um recálculo server-side para os registros convertidos, mantendo consistência imediata.
UPDATE public.quotes
SET shipping_type = 'fob_pre',
    updated_at = now()
WHERE shipping_type = 'fob'
  AND shipping_cost > 0
  AND status NOT IN ('approved', 'converted');

supabase/migrations/20260519225100_backfill_legacy_fob_with_cost.sql:48

  • O bloco DO $$ ... GET DIAGNOSTICS ... ROW_COUNT não vai capturar o número de linhas afetadas pelo UPDATE anterior, porque ROW_COUNT é local ao último comando SQL executado dentro do próprio bloco DO. Do jeito atual, a mensagem de audit tende a sempre reportar 0. Se a intenção é logar o total convertido, una o UPDATE e o GET DIAGNOSTICS no mesmo bloco (ou use WITH updated AS (UPDATE ... RETURNING 1) SELECT count(*)).
-- Audit log: registrar conversoes para forensics
DO $$
DECLARE
  _converted_count integer;
BEGIN
  GET DIAGNOSTICS _converted_count = ROW_COUNT;
  RAISE NOTICE '[backfill_legacy_fob] Converted % quotes from fob+cost to fob_pre', _converted_count;
END $$;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +100 to +107
-- Persistir
UPDATE public.quotes
SET
subtotal = _new_subtotal,
discount_amount = _disc_value,
total = _new_total,
updated_at = now()
WHERE id = _quote_id;
Comment on lines +21 to +22
-- - Não recalcula totals (sera feito pelo trigger fn_quotes_recalc na proxima
-- alteração de quote_items, ou pode ser feito manualmente via touch).
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d77822e03

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +73 to 74
quote.shipping_type === 'fob_pre'
? quote.shipping_cost || 0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle legacy fob+cost quotes in total calculation

This now excludes shipping_cost whenever shipping_type='fob', but the companion backfill explicitly leaves approved/converted rows untouched (status NOT IN ('approved','converted')), so immutable legacy quotes can still have shipping_type='fob' with positive freight. For those records, the view/PDF total becomes lower than the persisted negotiated total, creating a customer-facing mismatch on historical approved quotes.

Useful? React with 👍 / 👎.

Comment on lines +103 to +107
subtotal = _new_subtotal,
discount_amount = _disc_value,
total = _new_total,
updated_at = now()
WHERE id = _quote_id;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reinstate no-op guard on quote totals UPDATE

This replacement removed the prior IS DISTINCT FROM safeguard (present in 20260515000000_onda17_fn_quotes_recalc_subtotal_completo.sql), so the trigger function now rewrites quotes on every invocation even when subtotal/discount/total did not change. Because quotes has a BEFORE UPDATE version trigger (trg_quotes_increment_version in 20260417112433_c918513d-709c-44cc-87e6-5752a1818c3b.sql), non-financial quote_items edits can now cause unnecessary version bumps and updated_at churn, increasing optimistic-lock conflicts and noisy history.

Useful? React with 👍 / 👎.

Comment on lines +34 to 35
const shippingValue = shippingType === "fob_pre"
? (shippingCost || 0) : 0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve total consistency in QuoteTotalsSummary for legacy FOB

The summary now drops freight whenever shippingType === 'fob', but the backfill intentionally skips approved/converted rows, so legacy immutable quotes can still be fob with positive shipping_cost and persisted totals that include freight. In those cases this card shows a lower recomputed total than the stored approved amount, which can misstate the contracted value on the quote view.

Useful? React with 👍 / 👎.

adm01-debug added a commit that referenced this pull request May 22, 2026
Anexa seção "UPDATE 2" ao log do T14 cobrindo os 4 commits adicionais que
aconteceram entre 20:45 e 20:51 UTC (~6 min):

1. 5a16641 — test.fixme em 22.1 e 22.2 (bugs pré-existentes, não T14)
2. 8665829 — 🚨 corrupção base64-duplo no playwright.config.ts (sessão paralela)
3. c033e71 — script T-FIX-5 (não relacionado, na linhagem)
4. 3a9d718 — recovery + per-test timeout 90s aplicado em UTF-8 puro

Estado real do T14: fix-complete (todos os patches necessários aplicados),
mas verificação direta do smoke gate ainda não aconteceu — está bloqueado
upstream pelos Bugs #3 (Test Coverage) e #5 (ESLint baseline gate) do plano
"10/10", que são tarefas separadas.

Validação INDIRETA OK: o job "Edge Functions — Deno typecheck" do CI #463
rodou normalmente, confirmando que playwright.config.ts é TypeScript válido
(seria o primeiro a quebrar se o arquivo ainda estivesse base64-corrompido).

Documenta também a lição da corrupção:
- github_create_or_update_file ESPERA UTF-8 plain text no `content`
- a tool faz seu próprio base64-encode antes de submeter ao Git
- passar string já em base64 produz blob double-encoded "salvo OK" mas inválido

Refs: T14 do plano redeploy 2026-05; Bugs #3 e #5 do plano "10/10".
adm01-debug added a commit that referenced this pull request May 23, 2026
…TS2698

Commit 5bde589 introduced the importOriginal pattern but didn't type it.
Without a type parameter, importOriginal() returns Promise<unknown>, so
`const actual = await importOriginal()` makes actual: unknown — spreading
unknown is TS2698.

Fix: `import type * as ProductsBarrel` + `importOriginal<typeof ProductsBarrel>()`
gives TS a concrete module type so { ...actual, ... } is valid. Top-of-file
import-type form is used to satisfy @typescript-eslint/consistent-type-imports
(inline import() type annotations are forbidden).

Net drift: 1330 errors (-3 from baseline 1333) — typecheck gate now green.

Validates plan 10/10 #3, #4 (and #5 T-FIX-5 guardrail) on top of #138.
All 6 quality gates green locally:
- typecheck:           0 regressions (drift -3)
- lint:baseline:       0 regressions (drift -47)
- check:no-inline-cors:    0 violations
- check:edge-cors:         81 functions via SSOT
- check:toast-leaks:       0 new (106 legacy)
- check:proposed-configs:  no orphans

Vitest validated on 24 modified test files (1 skipped intentionally per
FIXME(useCatalogState-unit-OOM)):
NotificationDrawer x6 (closes #119, #120, #121), useCatalogState,
MainLayout breadcrumbs, AdminStandardRules, AppLogo.visual,
PriceFreshnessBadge snapshots, QuoteBuilderDiscount, QuoteBuilderStepper,
AuthBranding x2, MagicUp, simulation-orchestrator, syntax-integrity,
ProtectedRoute, AdminRoute, DevRoute, AdminConexoesAccess,
route-no-error-element, reduced-app-navigation.
adm01-debug added a commit that referenced this pull request May 26, 2026
…eset, dead code

Bug #2: handleOpenChange não revogava blobUrlRef.current ao fechar.
  Cada ciclo abrir→gerar→fechar vazava um blob:// na memória do browser.
  Fix: revokeBlobUrl() extraído como helper, chamado no fechamento e antes
  de criar novo blob.

Bug #4: ESC ou clique fora fechava o dialog durante generateProposalPDFv2.
  A operação assíncrona continuava em background (zumbi), podendo tentar
  atualizar state em componente já desmontado.
  Fix: onInteractOutside + onEscapeKeyDown previnem fechamento enquanto
  stage === 'generating'. Mensagem 'Aguarde, não feche esta janela' adicionada.

Bug #5: progressLabel e pdfVersion não resetavam ao fechar o dialog.
  Na próxima abertura, progressLabel mostrava valor antigo; pdfVersion
  começava em v2/v3 em vez de v1.
  Fix: ambos resetados em handleOpenChange junto com demais states.

Bug #8: clientPhone, approvalLink, onWhatsApp, onShareLink declarados na
  interface mas nunca usados. Marcados como @deprecated para backward-compat.

Bug #9: ActionButton.variant 'whatsapp' tinha CSS idêntico a 'primary'.
  Tipo simplificado para 'default' | 'primary'.
adm01-debug added a commit that referenced this pull request May 26, 2026
…Propostas PDF (#438)

* fix(pdf-generator): root.unmount() movido para finally externo — sem memory leak em erro

Bug: se html2canvas lançava exceção durante captura de qualquer página,
root.unmount() nunca era chamado. ReactDOM.createRoot ficava pendurado,
consumindo memória indefinidamente.

Fix:
- `let root` declarado fora do try block
- `root?.unmount()` movido para o finally externo (sempre executa)
- `document.body.removeChild(container)` mantido no mesmo finally
- img.complete check aprimorado: naturalWidth > 0 evita tratar imagem
  com src vazio como 'carregada'

* fix(PdfGenerationDialog): 5 bugs — memory leak, zombie async, state reset, dead code

Bug #2: handleOpenChange não revogava blobUrlRef.current ao fechar.
  Cada ciclo abrir→gerar→fechar vazava um blob:// na memória do browser.
  Fix: revokeBlobUrl() extraído como helper, chamado no fechamento e antes
  de criar novo blob.

Bug #4: ESC ou clique fora fechava o dialog durante generateProposalPDFv2.
  A operação assíncrona continuava em background (zumbi), podendo tentar
  atualizar state em componente já desmontado.
  Fix: onInteractOutside + onEscapeKeyDown previnem fechamento enquanto
  stage === 'generating'. Mensagem 'Aguarde, não feche esta janela' adicionada.

Bug #5: progressLabel e pdfVersion não resetavam ao fechar o dialog.
  Na próxima abertura, progressLabel mostrava valor antigo; pdfVersion
  começava em v2/v3 em vez de v1.
  Fix: ambos resetados em handleOpenChange junto com demais states.

Bug #8: clientPhone, approvalLink, onWhatsApp, onShareLink declarados na
  interface mas nunca usados. Marcados como @deprecated para backward-compat.

Bug #9: ActionButton.variant 'whatsapp' tinha CSS idêntico a 'primary'.
  Tipo simplificado para 'default' | 'primary'.

* fix(ProposalProductTable): imagens em branco no PDF + coluna Total ausente

Bug #3a: loading="lazy" em imagens offscreen.
  O browser só carrega lazy quando o elemento entra no viewport.
  O template é renderizado a -10000px (fora do viewport), então
  nenhuma imagem carregava → PDF gerado com espaços em branco.
  Fix: loading="eager".

Bug #3b: useState("") causa img.complete falso positivo.
  Com src="" inicial, img.complete retorna true imediatamente (sem
  nenhuma imagem ter sido carregada). O generator do PDF interpreta
  isso como "imagem carregada" e chama html2canvas com src vazio.
  Fix: useState(src) — imagem sempre tem src válido desde o início;
  processLogoTransparent atualiza depois com versão sem fundo.

Bug #6: coluna "Total" ausente na tabela de produtos.
  Cliente recebia proposta sem totais por linha, precisando calcular
  manualmente. Fix: nova coluna Total = allInUnitPrice × quantity −
  itemDiscount. colSpan dos cabeçalhos de kit ajustado (3→4 / 4→5).

* fix(PropostaComercialTailwind): mutable itemIndex in render → React 18 StrictMode bug

Bug #7: `let itemIndex = 0` era mutado dentro do .map() do JSX.
  Em React 18 StrictMode (desenvolvimento), React renderiza o componente
  duas vezes para detectar side-effects indesejados. Na segunda passagem,
  itemIndex já tinha o valor acumulado da primeira, dobrando os índices
  de linha: item 0 virava item 4, item 4 virava item 8, etc.
  Resultado: numeração errada das linhas na tabela do PDF em dev.

Fix: startIndices[] pré-computado com Array.reduce() antes do JSX.
  Imutável — não sofre efeito de dupla renderização.
  Substitui: `const startIdx = itemIndex; itemIndex += pageItems.length;`
  Por:       `const startIdx = startIndices[pageIdx];`
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.

2 participants