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>
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
---
|
||||
name: proposta-visual
|
||||
description: 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):**
|
||||
```json
|
||||
{
|
||||
"monthly": 459,
|
||||
"hours": 8,
|
||||
"originalPrice": 540,
|
||||
"discount": 15
|
||||
}
|
||||
```
|
||||
Mostra: "459 €/mês" + "8h/mês incluídas · contrato anual"
|
||||
|
||||
**Preço único (investimento):**
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
|
||||
```json
|
||||
{
|
||||
"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:**
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```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
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
```jsonl
|
||||
{"date":"","issue":"","fix":"","source":"user|auto"}
|
||||
```
|
||||
|
||||
*Adicionar nova linha após cada erro corrigido.*
|
||||
Reference in New Issue
Block a user