Files
ealmeida faef9b47dc fix(project-manager): remover Dify KB das descriptions, marcar nota TODO
Dify foi removido 06-03-2026. Skills brainstorm/discover ainda referenciam-no
no corpo. Bump v1.2 + nota top-of-file. Reescrita workflow para próxima sessão.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 04:52:03 +01:00

13 KiB

name, description
name description
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.

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

6. Página 360° (service360) — contextualizar

A secção service360 no PDF usa o label "O QUE PROPOMOS" (genérico) e o título "As nossas soluções para o seu negócio". Os dados vêm do JSON service360.areas[].

Para propostas que NÃO são 360°, adaptar os areas ao contexto:

  • Migração técnica: features da migração
  • Website: áreas do serviço web
  • SEO: pilares do trabalho SEO

Se service360 for null, a secção não aparece.

7. Condições comerciais — página separada no PDF

As condições estão numa página PDF separada (não na mesma que o roadmap) para evitar corte entre páginas. O componente PDFWorkDone retorna um Fragment com 2 Pages.


Protocolo

Sintaxe

/proposta-visual [cliente] [tipo-serviço]

Fase 1: Análise do contexto

ANTES de criar o JSON, responder a estas perguntas:

  1. Que tipo de proposta é? (360°, website, migração técnica, SEO, eCommerce, outro)
  2. O preço é mensal ou único? → define monthly vs price/priceLabel/priceSubtitle
  3. Que secções se aplicam? → define que campos preencher no JSON
  4. Há dados quantitativos? (vendas, scores SEO, KPIs) → define salesData, context.scores
  5. Há trabalho já realizado? → define workDone
  6. Quantas opções de preço? → define plans[]

Mapa de secções por tipo de proposta:

Secção 360° Website Migração SEO eCommerce
title Descomplicar 360° Website Profissional [contexto] Optimização SEO Loja Online
brandStrategy Opcional Opcional
salesData Se existir Se existir
scores ✓ (audit) Opcional ✓ (audit) Opcional
service360
plans ✓ (3 opções)
licenses Se aplicável Se aplicável
workDone Se existir Se existir Se existir Se existir
roadmap
conditions

Fase 2: Recolha de dados do CRM

mcp__desk-crm-v3__search_customers query="[nome]"
mcp__desk-crm-v3__get_customer customer_id=[id]
mcp__desk-crm-v3__get_estimates client_id=[id]

Fase 3: Criar ficheiro JSON

Localização: src/data/[slug].json

Gerar slug: iniciais do cliente + tipo + hash curto. Ex: ccv-bookeo-a3f7c1d2

Schema mínimo obrigatório:

{
  "title": "Título da Proposta",
  "client": {
    "name": "Nome Legal Lda.",
    "brand": "Nome Comercial",
    "website": "exemplo.pt",
    "contact": "Nome Contacto",
    "email": "email@exemplo.pt",
    "phone": "+351 900 000 000"
  },
  "company": {
    "name": "Descomplicar, Lda.",
    "brand": "Descomplicar",
    "nif": "514 785 691",
    "email": "info@descomplicar.pt",
    "phone": "911 510 005",
    "website": "descomplicar.pt"
  },
  "date": "2026-03-23",
  "validDays": 30,
  "context": {
    "description": "Descrição da situação...",
    "diagnosis": [],
    "scores": [],
    "brandStrategy": null
  },
  "service360": null,
  "plans": [],
  "licenses": null,
  "workDone": [],
  "roadmap": [],
  "conditions": [],
  "salesData": [],
  "salesProjection": []
}

Campos opcionais para planos com preço único:

{
  "price": 750,
  "priceLabel": "EUR + IVA",
  "priceSubtitle": "Investimento único · prazo 1-2 semanas",
  "monthly": null,
  "hours": null
}

Checklist PT-PT antes de guardar o JSON:

  • Todos os textos têm acentos correctos (ã, ç, ã, é, ê, í, ó, õ, ú)
  • Zero brasileirismos
  • Caractere → (seta) em vez de -> nos textos visíveis
  • Monetário: "EUR" ou "€" (não "R$" nem "$")

Fase 4: Registar no App.jsx

import novaData from "./data/[slug].json"

const proposals = {
  // existentes...
  "[novo-slug]": novaData,
}

Fase 5: Verificar antes do build

Checklist pré-build:

  • JSON válido (sem trailing commas, sem campos undefined)
  • Campos null/[] para secções que não se aplicam (NÃO omitir)
  • title definido (não depender do fallback)
  • plans[].price OU plans[].monthly definido (não ambos null)
  • plans[].priceLabel definido se preço único
  • Slug registado em App.jsx
  • Nenhum flexWrap + gap nos componentes PDF
  • Nenhum borderColor com rgba() nos componentes PDF

Fase 6: Build e deploy

cd /media/ealmeida/Dados/Dev/Propostas
npm run build

# Limpar dist
cd dist
rm -rf assets/template-media assets/template-referencia.pptx assets/slide-referencia.svg
cd assets/brochura && ls *.{jpg,png} 2>/dev/null | grep -v image8.jpg | xargs rm -f 2>/dev/null

# Empacotar
cd /media/ealmeida/Dados/Dev/Propostas/dist
tar czf /tmp/propostas-dist.tar.gz .

# Upload + deploy
mcp__ssh-unified__sftp_upload server=easy localPath=/tmp/propostas-dist.tar.gz remotePath=/tmp/propostas-dist.tar.gz overwrite=true
mcp__ssh-unified__ssh_execute server=easy command="cd /opt/propostas && rm -rf assets/* index.html; tar xzf /tmp/propostas-dist.tar.gz && docker service update --force descomplicar_propostas && rm -f /tmp/propostas-dist.tar.gz"

# Verificar
curl -sI https://propostas.descomplicar.pt/[slug] | head -3
# Deve retornar HTTP/2 200

# Limpar
rm -f /tmp/propostas-dist.tar.gz

Fase 7: Testar PDF

OBRIGATÓRIO antes de entregar:

  1. Abrir https://propostas.descomplicar.pt/[slug] com Ctrl+Shift+R
  2. Clicar "Exportar PDF"
  3. Verificar que href do link não está vazio (PDF gerou)
  4. Abrir PDF e verificar:
    • Título correcto (não "Descomplicar 360°" hardcoded)
    • Preços correctos (não "€/mês" se é preço único)
    • Sem borders verdes (rgba renderiza verde no react-pdf)
    • Sem secções que não se aplicam (360°, vendas, licenças, scores)
    • Acentos PT-PT em todo o texto
    • Condições comerciais numa página inteira (sem corte)
    • Todas as 3 opções de preço com layout uniforme

Se o PDF não gera (href vazio):

  1. Verificar consola do browser (F12)
  2. Causa mais provável: import de componente SVG dos infographics
  3. Verificar que PDFDocument não importa de ./infographics/
  4. Verificar que não há gap + flexWrap no PDF
  5. Ctrl+Shift+R e tentar novamente (cache)

Fase 8: Entrega e registo

  1. Guardar em Hub/03-Propostas/[Cliente]/
  2. Registar estimate no CRM (se não existe)
  3. Comunicar URL ao utilizador
  4. Comentar tarefa CRM

Design system ACIDA 2.0

Token Valor
Dourado #C88900
Dourado claro #EED59F
Dourado border PDF #D4A020 (usar em vez de rgba no PDF)
Dark #262626
Background #F8F8F8
Fonte web Inter (Google Fonts)
Fonte PDF Helvetica / Helvetica-Bold
Logo escuro /assets/logo-descomplicar.png
Logo claro /assets/logo-descomplicar-white.png

Propostas existentes

Slug Cliente Tipo Data
ljm-360-61a8aecc A Loja da Maria 360° (mensal) 2026-03-20
ccv-bookeo-a3f7c1d2 Carvoeiro Caves Migração técnica (único) 2026-03-23

Skills relacionadas

  • /proposal-deck — PPTX standalone (16 layouts, XML editing)
  • /orcamento — Estimate no CRM (sem visual)
  • /lead-approach — Estratégia de abordagem de lead
  • /crm — Operações CRM genéricas

Changelog

v2.0.0 (2026-03-23)

  • Regras críticas documentadas (7 regras)
  • Renderização condicional obrigatória (ProposalView + PDFDocument)
  • Título dinâmico via campo title no JSON
  • Preços flexíveis (mensal vs único) com price/priceLabel/priceSubtitle
  • Bugs react-pdf documentados com proibições explícitas
  • Mapa de secções por tipo de proposta
  • Checklist pré-build e teste PDF obrigatório
  • Condições comerciais em página PDF separada

v1.0.0 (2026-03-23)

  • Versão inicial

Versão: 2.0.0 | Data: 2026-03-23


Healing Log

Registo de erros conhecidos e como evitá-los. Lido automaticamente antes de executar.

{"date":"","issue":"","fix":"","source":"user|auto"}

Adicionar nova linha após cada erro corrigido.