feat(documents): pré-visualização em PDF do rascunho via Chromium

Novo endpoint POST /documents/templates/:id/draft/preview: renderiza o
conteúdo atual (ainda não salvo) do editor com placeholders {{variavel}}
substituídos por [variavel], via Chromium real, sem persistir nada
(sem generation, sem upload S3) — permite validar visualmente o
resultado antes do "Publicar", que é uma ação permanente.

Corrige de passagem um bug real: css_content do template nunca chegava
ao renderPdf na geração real de documento (createGeneration), sendo
descartado silenciosamente.

No frontend, a pré-visualização aparece embutida num iframe na própria
tela (não em nova aba): abrir em nova aba se mostrou não-confiável ao
vivo — a navegação tardia de uma aba pré-aberta pode ser ignorada pelo
navegador quando o gesto do usuário que a autorizou já expirou no tempo
que o Chromium leva pra renderizar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 23:59:02 -03:00
parent 8ab18ababc
commit 553c0d9533
7 changed files with 222 additions and 5 deletions

View File

@@ -830,3 +830,24 @@ Data: 2026-09-18. Usuário mandou uma captura de tela de como o legado montava e
### Achado não resolvido (fora de escopo, registrado pelo agente)
Na tela de sucesso pós-criação, o campo "Implantação total" mostra só `implementation_fee` (implantação dos produtos), sem somar `impl_geral` — quando esse campo é preenchido, o rótulo sub-representa o valor que efetivamente compõe o "Total do contrato". Não é um bug introduzido por esta leva, é pré-existente; não corrigido por estar fora do escopo pedido (só a tela de montagem da oferta).
---
## Pré-visualização em PDF do rascunho de Documentos (Chromium)
Data: 2026-09-18. Usuário pediu pra ajustar a tela de Contratos, mas esclareceu que era na verdade a de Documentos ("templates de contrato e outros documentos"), "aonde teria que rodar com o chromium pra eu poder editar os arquivos com as variáveis". Implementado diretamente (contido, reaproveita 100% da infraestrutura de Chromium já existente, sem tocar em schema).
**Contexto investigado antes de implementar**: o editor de rascunho (`TemplateDetailPage.tsx`) é uma `<textarea>` simples com placeholders `{{variavel}}` — sem preview nenhum do rascunho em si (só a versão JÁ PUBLICADA tinha uma prévia, em HTML cru renderizado no navegador, nunca via Chromium). Publicar é uma ação **permanente** (a versão não pode mais ser editada depois) — ou seja, não existia nenhuma forma de validar visualmente o resultado antes de um passo irreversível.
**Achado real, corrigido no caminho**: `createGeneration` (a geração REAL de documento, usada em produção) nunca passava `css_content` pro `renderPdf` — o campo "CSS (avançado, opcional)" do editor existia, era salvo, mas **era descartado silenciosamente na hora de gerar o PDF final**. Corrigido (`documents.service.ts`), coberto por teste novo.
### Backend
`POST /v1/documents/templates/:id/draft/preview` — recebe o `editorContent`/`cssContent` **atual do editor, ainda não salvo** (não busca o rascunho persistido — permite iterar editar→pré-visualizar→editar sem precisar salvar a cada volta), gera o HTML da mesma forma que o rascunho real (`renderEditorContentToHtml`, reaproveitado), extrai toda `{{variavel}}` encontrada (reaproveitando `mergeVariables` com contexto vazio — tudo que "falta" É a lista completa de variáveis), substitui cada uma por `[variavel]` (não existe cliente/revenda selecionado nesta tela pra ter um valor real), renderiza via `renderPdf` (Chromium real) com o CSS aplicado, e devolve o PDF direto — **nunca persiste nada** (sem `document_generations`, sem upload S3), é puramente descartável.
### Frontend
Novo botão "Pré-visualizar (PDF)" ao lado de "Salvar rascunho". **Decisão de UX importante, mudada no meio do caminho por um achado real**: a primeira versão abria o PDF numa aba nova (`window.open`) — testado ao vivo e descoberto que, mesmo abrindo a aba de forma síncrona (técnica padrão pra não ser bloqueada por pop-up blocker) e só navegando ela depois que o PDF ficasse pronto, o navegador às vezes ignora essa navegação tardia silenciosamente: a "janela de gesto do usuário" que autoriza a navegação de uma aba já aberta expira rápido demais pra uma renderização Chromium real no servidor (que não é instantânea). Confirmado isolando o mecanismo: um blob sintético em memória (sem rede) navegava a aba certinho; o mesmo código com uma chamada de rede real pro endpoint de preview não navegava. Resolvido de forma mais robusta: em vez de nova aba, o PDF aparece **embutido na própria tela** (`<iframe>` logo abaixo dos botões, com blob URL) — elimina esse problema por completo, e nem exige contexto de navegação nova.
### Verificação
`pnpm turbo run typecheck build` 28/28. Suíte 115/115 (113→115: 1 teste cobrindo o preview de rascunho — variáveis viram placeholder, CSS aplicado, nada persiste —, 1 confirmando que `css_content` agora chega no `renderPdf` real da geração). Testado ao vivo contra Chromium de verdade (não mockado): PDF real de 1 página gerado sem nunca ter salvo um rascunho, texto extraído via `pdftotext` confirmando "Prezado(a) [nome_cliente], seu plano é [plano]." e a formatação CSS (cor/tamanho de fonte) presente no stream do PDF. A renderização visual do PDF dentro do iframe não pôde ser confirmada por screenshot (visualizador nativo de PDF do Chromium não aparece em capturas de tela no modo headless do Playwright — mesma limitação de teste automatizado já registrada antes nesta sessão), mas o mecanismo (blob correto, `src` do iframe apontando pro blob certo, bytes do PDF válidos e corretos) foi confirmado de forma independente.