5.0 KiB
name, description, layer
| name | description | layer |
|---|---|---|
| proposta-visual | Criação completa de propostas comerciais visuais — página web (propostas.descomplicar.pt) + PDF exportável + PPTX editável. Recolhe dados CRM, gera JSON, renderiza condicionalmente e faz deploy. | wiki |
Skill /proposta-visual — Propostas Comerciais Visuais
Cria propostas comerciais profissionais com 3 outputs: página web interactiva, PDF exportável e PPTX editável.
Triggers
/proposta-visual- "proposta visual", "proposta web", "criar proposta", "nova proposta", "proposta para [cliente]"
Quando usar
- Criar proposta comercial completa para um cliente
- Gerar página web + PDF + PPTX de uma proposta
- Actualizar proposta existente com novos dados
Quando NÃO usar
- Apenas orçamento sem visual (usar
/orcamento) - Apenas PPTX sem página web (usar
/proposal-deck) - Análise de lead sem proposta (usar
/lead-approach)
Projecto base
| Campo | Valor |
|---|---|
| Código fonte | /media/ealmeida/Dados/Dev/Propostas/ |
| Stack | React 18 + Vite + Tailwind + @react-pdf/renderer + Recharts + Framer Motion + Lucide React |
| Live | propostas.descomplicar.pt/{slug} |
| Deploy | Docker Swarm EasyPanel, serviço descomplicar_propostas, ficheiros /opt/propostas/ |
| DNS | propostas.descomplicar.pt → 5.9.90.70 (Cloudflare proxied) |
| Template PPTX | /media/ealmeida/Dados/Hub/90-Templates/Comercial/descomplicar-proposal-template.pptx |
REGRAS CRÍTICAS (ler antes de qualquer implementação)
1. PT-PT obrigatório — acentos em TODO o texto
Todos os textos no JSON, componentes web e componentes PDF DEVEM ter acentos PT-PT correctos:
- ✓ migração, tradução, integração, opções, condições, execução, acção
- ✗ migracao, traducao, integracao, opcoes, condicoes, execucao, accao
Verificar no PDF: labels hardcoded em PDFWorkDone.jsx e PDFDocument.jsx — já corrigidos mas confirmar sempre.
2. JSON data-driven — renderização condicional obrigatória
O ProposalView.jsx e o PDFDocument.jsx renderizam secções condicionalmente com base nos dados. Se um campo é null, [] ou não existe, a secção NÃO aparece.
Regra: NUNCA assumir que todos os campos existem. Cada proposta pode ter um subconjunto diferente de secções.
| Campo JSON | Secção web | Secção PDF | Condição |
|---|---|---|---|
context.brandStrategy |
BrandStrategy | PageBrandStrategy | != null |
salesData |
SalesChart | PageSales | .length > 0 |
context.diagnosis |
Context | — | .length > 0 |
context.scores |
ScoreGrid | PDFScores | .length > 0 |
service360 |
Service360 | PageService360 | != null |
plans |
PricingCards | PDFPricing | .length > 0 |
licenses |
LicensePack | PageLicenses | != null |
workDone |
WorkDone | PDFWorkDone | .length > 0 |
roadmap |
Roadmap | PDFWorkDone | .length > 0 |
conditions |
Conditions | PDFWorkDone (pág. separada) | .length > 0 |
3. Título dinâmico — campo title no JSON
O título da proposta (Hero web + capa PDF) vem do campo title no JSON:
- Proposta 360°:
"title": "Descomplicar 360°" - Migração técnica:
"title": "Migração Bookeo" - Website:
"title": "Website Profissional" - Fallback:
"Proposta Comercial"
NUNCA hardcodar "Descomplicar 360°" — usar data.title.
4. Preços flexíveis — não assumir mensalidades
Os planos suportam dois formatos de preço:
Mensalidade (avença):
{
"monthly": 459,
"hours": 8,
"originalPrice": 540,
"discount": 15
}
Mostra: "459 €/mês" + "8h/mês incluídas · contrato anual"
Preço único (investimento):
{
"price": 750,
"priceLabel": "EUR + IVA",
"priceSubtitle": "Investimento único · prazo 1-2 semanas",
"monthly": null,
"hours": null
}
Mostra: "750 EUR + IVA" + "Investimento único · prazo 1-2 semanas"
Campos usados: plan.price ?? plan.monthly para o valor, plan.priceLabel ?? "€/mês" para a unidade, plan.priceSubtitle para a descrição abaixo.
5. react-pdf — bugs conhecidos e PROIBIÇÕES
| Proibição | Porquê | Alternativa |
|---|---|---|
gap + flexWrap juntos |
Crasha silenciosamente | marginRight + marginBottom nos items |
borderColor com rgba() |
Renderiza verde | Cor sólida (ex: #D4A020, #E8E8E8) |
Import de componentes SVG (<Svg>, <Circle>) |
Crasha web worker do PDFDownloadLink | Implementar gráficos inline com <View> |
Font.register com fontes locais/CDN em multi-página |
Falha silenciosa | Usar Helvetica/Helvetica-Bold (built-in) |
href vazio no PDFDownloadLink |
PDF não gerou — verificar consola | Cache do browser — Ctrl+Shift+R |
width: "100%" em bars/elementos dentro de flex container |
Não resolve — fica invisível | Usar alignSelf: "stretch" no elemento e no container pai |
Imagens com path absoluto (/home/...) |
react-pdf não acede ao filesystem local | Colocar em public/assets/ e referenciar como "assets/foto.png" (s |