--- name: expense description: > Gestão de despesas Desk CRM v2.0. Documento PDF obrigatório para cada despesa. Registar, categorizar e analisar despesas com verificação de categorias e duplicados. Upload SFTP + arquivo mensal automatico. NUNCA cria sem PDF (excepto bypass explicito). Use when "despesa", "expense", "gasto", "custo", "categoria despesa", "registar despesa", "expense report", "processar tickets contabilidade", "recibo", "factura fornecedor". author: Descomplicar® Crescimento Digital version: 2.0.0 quality_score: 90 user_invocable: true category: finance tags: [expense, despesas, finance, categories, desk-crm, costs, reporting, tickets, receipts, pdf, sftp] desk_task: 1710 desk_project: 65 allowed-tools: Read, mcp__desk-crm-v3, mcp__ssh-unified mcps: desk-crm-v3, ssh-unified dependencies: mcps: [desk-crm-v3, ssh-unified] triggers: - "User wants to create expense" - "User mentions 'despesa', 'expense', 'gasto', 'custo'" - "User wants to categorize expenses" - "User asks about expense categories" - "User needs expense report or analysis" - "User wants to process expenses from tickets" - "User mentions 'tickets contabilidade', 'recibos', 'receipts'" anti_patterns: critical: - "NEVER create category without checking if exists" - "NEVER assume category doesn't exist" - "ALWAYS list categories before creating new one" categories: - "Creating duplicate categories" - "Using similar name instead of existing category" - "Not checking existing categories first" technical: - "Creating expense without category_id" - "Using category name instead of ID" performance: baseline_duration_ms: 60000 target_duration_ms: 30000 last_run_duration_ms: null success_rate: 0.95 --- # /expense - Gestão de Despesas Skill para gestão de despesas com verificação obrigatória de categorias existentes. ## Metadata - **Version**: 2.0.0 - **Author**: Descomplicar - **Date**: 2026-02-12 - **Status**: Active --- ## Quando Usar - Registar nova despesa - Criar/gerir categorias de despesas - Consultar despesas por período/categoria - Associar despesas a projectos/clientes - Análise e relatórios de despesas - **Processar despesas de tickets de contabilidade (departamento 3)** - Importar recibos de serviços (Anthropic, Hetzner, etc.) ## Quando NÃO Usar - Para facturas a clientes (usar /invoice) - Para orçamentos (usar /orcamento) - Para pagamentos recebidos (usar /crm) --- ## REGRA CRÍTICA: CATEGORIAS > **PROIBIDO criar categoria sem verificar se já existe.** Esta regra é INVIOLÁVEL. SEMPRE: ``` 1. Listar categorias: get_expense_categories(with_stats=true) 2. Pesquisar por nome similar na lista 3. SE encontrar match ou similar: - USAR a categoria existente - NÃO criar duplicado 4. SE realmente não existe: - PERGUNTAR: "Não encontrei categoria para X. Criar nova?" 5. SÓ CRIAR após confirmação explícita ``` **Violação desta regra causa duplicados que prejudicam relatórios financeiros.** --- ## Categorias Activas (top 8 - consolidacao 12-02-2026) | ID | Nome | Fornecedores tipicos | |----|------|---------------------| | 1 | Telecomunicacoes | MEO | | 4 | Alojamento web (Hosting) | Hetzner, CWP | | 6 | Servicos Externos | Make/Celonis, Gamma | | 14 | Subscricoes e Servicos Digitais | YouTube Premium, BdThemes | | 28 | Licencas Software | Canva, Cursor, Descript, GitHub, Softaculous | | 30 | Servicos Cloud e Infraestrutura | Google One, Google Workspace, ElasticEmail | | 37 | Dominios | PTisp, Namecheap | | 38 | Servicos IA e APIs | Anthropic, OpenRouter, CapSolver, Replicate | **NOTA:** Existem ~30 categorias na BD mas apenas 8 sao usadas regularmente. SEMPRE usar `get_expense_categories()` para lista completa e actual. --- ## Protocolo ### 1. Registar Despesa > **Gate PDF obrigatorio:** Sem documento, nao regista. Unica excepcao: bypass explicito do utilizador (AT/Salario sem recibo). ``` 1. GATE PDF: Verificar que existe ficheiro PDF - SE utilizador forneceu PDF: prosseguir - SE nao forneceu: PEDIR o ficheiro - SE nao tem e pede bypass: PERGUNTAR "Confirmas despesa sem documento?" - Bypass valido apenas para: AT, Salarios, transferencias bancarias 2. LER O PDF: Extrair dados reais do documento - NUNCA copiar valor de um PDF para outro, mesmo com nomes semelhantes - Cada documento tem o seu valor proprio 3. OBRIGATORIO: get_expense_categories(with_stats=true) 4. Identificar categoria correcta na lista - SE categoria nao existe: PERGUNTAR antes de criar 5. VERIFICAR DUPLICADOS: - get_expenses(search: "") - Mesmo fornecedor + valor + data = duplicado - Mesmo numero factura = duplicado 6. Recolher dados: - category_id (obrigatorio) - amount (obrigatorio - valor REAL extraido do documento) - currency (obrigatorio - EUR=3, USD=2) - date (obrigatorio, YYYY-MM-DD) - note (obrigatorio, incluir numero factura) - send_invoice_to_customer = 0 (obrigatorio na BD) - client_id (opcional) - project_id (opcional) - billable (opcional, default false) - tax (opcional) 7. CONFIRMAR com utilizador (mostrar resumo) 8. create_expense (ou SQL directo se MCP indisponivel) 9. UPLOAD PDF ao Desk via SFTP: a. mkdir -p no servidor: /home/ealmeida/desk.descomplicar.pt/uploads/expenses/ b. mcp__ssh-unified__sftp_upload(server:"desk", local_path, remote_path) c. chown -R ealmeida:ealmeida d. INSERT INTO tblfiles (rel_id, rel_type='expense', file_name, filetype='application/pdf', visible_to_customer=0, staffid=1, contact_id=0, dateadded=NOW()) 10. ARQUIVO MENSAL: Mover PDF para pasta correspondente a data da factura: /media/ealmeida/Dados/GDrive/Cloud/ADM_Descomplicar/Financeiro/Contabilidade/YYYY/NN-NomeMes/ (mkdir -p se pasta nao existir) 11. Confirmar criacao ao utilizador ``` ### 2. Criar Categoria (APENAS se necessário) ``` 1. get_expense_categories() - listar todas 2. Verificar se existe categoria similar 3. SE existe similar: USAR ESSA, não criar 4. SE não existe nenhuma similar: - Perguntar confirmação ao utilizador - Aguardar resposta explícita 5. SÓ APÓS confirmação: criar via SQL ou interface ``` ### 3. Consultar Despesas ``` 1. get_expenses com filtros: - category (ID da categoria) - date_from / date_to (período) - client_id (cliente específico) - project_id (projecto específico) - limit (número resultados) 2. Apresentar resumo ao utilizador ``` ### 4. Análise de Despesas ``` 1. expense_analytics com parâmetros: - period: "month", "quarter", "year" - breakdown_by: "category", "client", "project" 2. Apresentar insights: - Total por categoria - Tendências - Categorias mais usadas ``` ### 5. Processar Despesas de Tickets (Contabilidade) > **REGRA 1:** Processar tickets UM A UM com confirmação do utilizador (ou em lote se solicitado). > **REGRA 2:** Verificar SEMPRE se despesa já foi lançada (prevenir duplicados). > **REGRA 3:** NÃO adicionar respostas aos tickets - apenas criar despesa e anexos. > **REGRA 4:** Converter SEMPRE USD para EUR antes de criar despesa. > **REGRA 5:** Saltar tickets que são apenas recibos de pagamento (não são facturas). ``` 1. Obter ticket: get_ticket(ticket_id) 2. Verificar anexos: SELECT * FROM tblticket_attachments WHERE ticketid = X 3. SE não tem PDFs: SALTAR ticket (apenas notificação) 4. VERIFICAR DUPLICADOS (OBRIGATÓRIO): SELECT id, amount, date, note FROM tblexpenses WHERE reference_no LIKE '%{receipt_number}%' OR note LIKE '%ticket #{ticket_id}%' SE encontrar: PARAR e informar "Despesa já existe: #ID" 5. Extrair dados: - Do HTML do ticket (valor, data, referência) - OU usar script Python: python3 /home/ealmeida/scripts/extract_invoice_data.py {pdf_path} 6. APRESENTAR resumo ao utilizador e AGUARDAR confirmação 7. Apos confirmacao: a. SQL: INSERT INTO tblexpenses (..., send_invoice_to_customer, ...) VALUES (..., 0, ...) OU create_expense via MCP (se disponivel) b. UPDATE tblexpenses SET expense_name = '{Fornecedor}' WHERE id = {id} c. mcp__ssh-unified__ssh_execute: mkdir -p uploads/expenses/{expenseid} d. mcp__ssh-unified__sftp_upload: PDF para uploads/expenses/{expenseid}/ e. mcp__ssh-unified__ssh_execute: chown -R ealmeida:ealmeida uploads/expenses/{expenseid} f. SQL: INSERT INTO tblfiles (rel_id, rel_type, file_name, filetype, visible_to_customer, staffid, contact_id, dateadded) VALUES ({id}, 'expense', '{file}', 'application/pdf', 0, 1, 0, NOW()) g. Mover PDF para arquivo mensal: Contabilidade/YYYY/NN-NomeMes/ 8. Confirmar ao utilizador e passar ao proximo ``` #### Script Python para Extracção de PDFs Localização: `/home/ealmeida/scripts/extract_invoice_data.py` ```bash python3 /home/ealmeida/scripts/extract_invoice_data.py /path/to/invoice.pdf ``` Retorna JSON com: vendor, cat_id, total, date, invoice, currency, text #### Moedas e Valores > **Despesas em USD:** Manter valor original em USD com `currency = 2`. **Nao converter** manualmente. > **Despesas em EUR:** Usar `currency = 3`. | Moeda | currency ID | Exemplo | |-------|------------|---------| | EUR | **3** | Anthropic 180.00 EUR | | USD | **2** | Cursor 20.00 USD | > **CRITICO:** `currency = 1` nao existe e causa despesas invisiveis nos relatorios. SEMPRE usar 2 (USD) ou 3 (EUR). #### Tickets a Saltar | Tipo | Descrição | Acção | |------|-----------|-------| | Payment Receipt | Recibo de pagamento (não é fatura) | SALTAR - não criar despesa | | Duplicado | Mesmo nº fatura já processado | SALTAR - informar utilizador | | Sem anexo PDF | Apenas notificação por email | SALTAR - dados insuficientes | Exemplos de tickets a saltar: - "Invoice Payment Confirmation" (é recibo, não fatura) - Ticket com mesmo invoice_number de outro já processado #### Credenciais Base de Dados Localização: `/home/ealmeida/desk.descomplicar.pt/application/config/app-config.php` ```bash grep -E "APP_DB" /home/ealmeida/desk.descomplicar.pt/application/config/app-config.php ``` #### Processamento em Lote Se utilizador pedir "criar todas" ou "processar em lote": 1. Obter todos os tickets pendentes em paralelo 2. Identificar duplicados e tickets a saltar 3. Criar despesas em paralelo via `create_expense` 4. Actualizar `expense_name` e `reference_no` em batch via SQL 5. Copiar PDFs em batch 6. Registar ficheiros em batch via SQL INSERT múltiplo #### Mapeamento Fornecedor → Categoria (consolidado 12-02-2026) | Fornecedor | Categoria ID | Nome | |-----------|-------------|------| | Anthropic, OpenRouter, CapSolver, Replicate | 38 | Servicos IA e APIs | | Canva, Cursor, Descript, GitHub, Softaculous, CWP | 28 | Licencas Software | | Google One, Google Workspace, ElasticEmail | 30 | Servicos Cloud e Infraestrutura | | Hetzner | 4 | Alojamento web (Hosting) | | MEO | 1 | Telecomunicacoes | | PTisp, Namecheap | 37 | Dominios | | Make/Celonis, Gamma | 6 | Servicos Externos | | YouTube Premium, BdThemes | 14 | Subscricoes e Servicos Digitais | #### Mapeamento Email → Categoria | Dominio Email | Categoria ID | Fornecedor | |---------------|--------------|------------| | anthropic.com | 38 | Anthropic | | openrouter.ai | 38 | OpenRouter | | payproglobal.com | 38 | CapSolver | | replicate.com | 38 | Replicate | | cursor.com, anysphere.dev | 28 | Cursor | | canva.com | 28 | Canva | | descript.com | 28 | Descript | | github.com | 28 | GitHub | | softaculous.com | 28 | Softaculous | | centos-webpanel.com | 28 | CWP | | elasticemail.com | 30 | ElasticEmail | | google.com (One/Workspace) | 30 | Google Cloud | | hetzner.com | 4 | Hetzner | | meoempresas.pt | 1 | MEO | | namecheap.com | 37 | Namecheap | | ptisp.pt | 37 | PTisp | | make.com, celonis.com | 6 | Make/Celonis | | gamma.app | 6 | Gamma | > **NOTA:** `moloni.com` NAO incluido - e plataforma de facturacao, emails podem ser de qualquer fornecedor. #### Mapeamento Padrao PDF → Categoria Para documentos financeiros, identificar fornecedor pelo conteudo do PDF: | Padrao no PDF | Fornecedor | Categoria ID | Nome | |---------------|-----------|--------------|------| | `Gondooffice`, `Cubic Choices` | Gondooffice | 21 | Contabilidade | | `Autoridade Tributaria`, `emiteDoc`, `AT -` | AT | 15 | Planos Prestacionais | | `Staples`, `STP_ECOFACTURA` | Staples | 3 | Material de Escritorio | | `MEO`, `meoempresas` | MEO | 1 | Telecomunicacoes | | `TOConline`, `Recibo de Vencimento` | Salario | 22 | Salarios e Vencimentos | | `Seguranca Social` | SS | 25 | Contribuicoes SS | | `Cursor`, `Anysphere` | Cursor | 28 | Licencas Software | | `Anthropic` | Anthropic | 38 | Servicos IA e APIs | | `Hetzner` | Hetzner | 4 | Alojamento web (Hosting) | | `ElasticEmail` | ElasticEmail | 30 | Servicos Cloud e Infraestrutura | #### Extracção de Dados de PDFs **Método 1: Script Python (recomendado)** ```bash python3 /home/ealmeida/scripts/extract_invoice_data.py /path/to/invoice.pdf ``` **Método 2: pdfplumber directo** ```bash python3 -c " import pdfplumber with pdfplumber.open('/path/to/file.pdf') as pdf: for p in pdf.pages: t = p.extract_text() if t: print(t) " | grep -iE "total|iva|€" ``` **Método 3: pdftotext (fallback)** ```bash pdftotext /path/to/file.pdf - | head -80 ``` > **NOTA:** Se PDF não extrair texto (imagem), usar dados do HTML do ticket email. #### Comandos SQL ```sql -- 1. VERIFICAR DUPLICADOS (EXECUTAR PRIMEIRO!) SELECT id, amount, date, note, reference_no FROM tblexpenses WHERE reference_no LIKE '%{receipt_number}%' OR note LIKE '%ticket #{ticket_id}%' -- 2. Verificar anexos do ticket SELECT id, file_name, filetype FROM tblticket_attachments WHERE ticketid = {ID} -- 3. Actualizar nome fornecedor UPDATE tblexpenses SET expense_name = '{Fornecedor}' WHERE id = {expense_id} -- 4. Registar anexo na despesa (contact_id obrigatorio!) INSERT INTO tblfiles (rel_id, rel_type, file_name, filetype, visible_to_customer, staffid, contact_id, dateadded) VALUES ({expense_id}, 'expense', '{filename}', 'application/pdf', 0, 1, 0, NOW()) ``` #### Campos para Rastreio de Duplicados | Campo | Conteúdo | Exemplo | |-------|----------|---------| | reference | Número do recibo | `2810-3712-9577` | | note | Incluir ticket ID | `Anthropic Max (ticket #9648)` | #### Paths de Ficheiros - Anexos ticket: `/uploads/ticket_attachments/{ticketid}/` - Anexos despesa: `/uploads/expenses/{expenseid}/` --- ## Comandos | Comando | Descrição | |---------|-----------| | `/expense create` | Registar nova despesa | | `/expense list` | Listar despesas recentes | | `/expense categories` | Listar categorias disponíveis | | `/expense report [período]` | Relatório de despesas | | `/expense search [termo]` | Pesquisar despesas | --- ## MCP Tools ### Despesas - `get_expenses` - Listar despesas com filtros - `create_expense` - Criar nova despesa - `update_expense` - Actualizar despesa - `delete_expense` - Eliminar despesa - `bill_expense_to_customer` - Facturar ao cliente ### Categorias - `get_expense_categories` - Listar categorias (usar with_stats=true) ### Análise - `expense_analytics` - Métricas e análise --- ## Campos create_expense | Campo | Tipo | Obrigatório | Descrição | |-------|------|-------------|-----------| | category_id | number | ✓ | ID da categoria | | amount | number | ✓ | Valor da despesa | | date | string | ✓ | Data (YYYY-MM-DD) | | note | string | ✓ | Descrição (incluir ticket ID) | | reference | string | ✓ | Número fatura/recibo (para duplicados) | | tax | number | ✓ | **ID da taxa** (1 = IVA 23%) | | client_id | number | - | Cliente associado | | project_id | number | - | Projecto associado | | billable | boolean | - | Facturável (default: false) | | tax2 | number | - | Segunda taxa imposto | | currency | number | ✓ | ID moeda (**EUR = 3**, USD = 2) | | payment_mode | string | - | Metodo pagamento (default: 'online') | | send_invoice_to_customer | number | ✓ | **Obrigatorio na BD** (sem default). Usar sempre **0** | ### Campo expense_name (via SQL) > **IMPORTANTE:** Após criar despesa, actualizar `expense_name` com nome do fornecedor. ```sql UPDATE tblexpenses SET expense_name = '{Fornecedor}' WHERE id = {expense_id}; ``` ### Taxas de Imposto (tbltaxes) | ID | Nome | Taxa | |----|------|------| | 1 | IVA Tx Normal | 23% | ### Moedas (tblcurrencies) | ID | Nome | Símbolo | Default | |----|------|---------|---------| | 2 | USD | $ | Nao | | 3 | EUR | € | Sim | > **CRITICO:** EUR = **3**, USD = **2**. Usar currency=1 causa despesas invisiveis nos relatorios. Para despesas em USD manter valor original com currency=2 (nao converter). --- ## Anti-Patterns (NUNCA fazer) ### Categorias 1. **NUNCA** criar categoria sem listar existentes primeiro 2. **NUNCA** assumir que categoria não existe 3. **NUNCA** criar categoria com nome similar a existente ### Despesas 4. Criar despesa sem category_id 5. Usar nome da categoria em vez do ID 6. Não validar data (formato YYYY-MM-DD) 7. Criar despesa sem note/descrição 8. **NUNCA** criar despesa sem verificar duplicados primeiro 9. **NUNCA** processar ticket sem verificar se já foi lançado ### Valores de PDFs 10. **NUNCA** assumir que ficheiros com nomes semelhantes têm o mesmo valor 11. **SEMPRE** ler CADA PDF individualmente para extrair o valor real 12. Exemplo real: `emiteDoc.pdf` a `emiteDoc (12).pdf` tinham valores de 25,80€ a 100,53€ - todos diferentes ### Tickets 13. Processar ticket sem verificar anexos PDF 14. Assumir que ticket com recibo = despesa não lançada --- ## Exemplos ### Exemplo 1: Registar despesa com categoria existente ``` User: Registar despesa de 50€ de telecomunicações → get_expense_categories() → Encontra ID 1 = Telecomunicações → create_expense(category_id=1, amount=50, date="2026-02-05", note="Telecomunicações") → "Despesa #X criada: 50€ em Telecomunicações" ``` ### Exemplo 2: Despesa com categoria a verificar ``` User: Registar despesa de formação 200€ → get_expense_categories() → NÃO encontra "Formação" → "Não encontrei categoria 'Formação'. Usar 'Outras' (ID 17) ou criar nova categoria?" → User: Usar Outras → create_expense(category_id=17, amount=200, date="2026-02-05", note="Formação profissional") ``` ### Exemplo 3: Despesa facturável a cliente ``` User: Registar 150€ de domínio para cliente TechStart → get_expense_categories() → ID 13 = Registo de Domínios → search_customers("TechStart") → client_id=42 → create_expense(category_id=13, amount=150, date="2026-02-05", note="Domínio techstart.pt", client_id=42, billable=true) → "Despesa facturável criada para TechStart" ``` --- ## Checklist Pré-Operação - [ ] Listar categorias existentes - [ ] Validar categoria correcta - [ ] Confirmar dados com utilizador - [ ] Usar formato data YYYY-MM-DD - [ ] Incluir nota descritiva --- ## Base de Dados - **Tabela categorias:** `tblexpenses_categories` - **Tabela despesas:** `tblexpenses` - **Campo categoria:** `category` (ID da categoria) --- ## Changelog ### v2.0.0 (2026-02-12) - Sistema Robusto 2026 - **Gate PDF obrigatorio:** Despesa sem documento nao e registada (bypass explicito para AT/Salario) - **Upload SFTP:** Substituido `cp` local por `mcp__ssh-unified__sftp_upload` (Regra #41) - **Arquivo mensal:** PDF organizado em `Contabilidade/YYYY/NN-NomeMes/` automaticamente - **Campo `send_invoice_to_customer`:** Documentado como obrigatorio na BD (valor 0) - **Correccao currency:** EUR = **3** (nao 2), USD = **2**. Corrigido em toda a skill - **Categorias alinhadas com consolidacao:** 8 categorias activas com mapeamento correcto - Cursor/Canva/Descript/GitHub/Softaculous/CWP → 28 (Licencas Software) - Hetzner → 4 (Alojamento web) - ElasticEmail/Google → 30 (Servicos Cloud) - PTisp/Namecheap → 37 (Dominios) - YouTube/BdThemes → 14 (Subscricoes) - Make/Gamma → 6 (Servicos Externos) - **Verificacao duplicados reforçada:** Integrada no passo 5 do protocolo - **MCP ssh-unified adicionado** a dependencias - **Procedimento:** `06-Operacoes/Procedimentos/PROC-Skill-Expense.md` ### v1.5.0 (2026-02-05) - Adicionado mapeamento **Cursor (Anysphere)** → categoria 38 (Serviços IA e APIs) - Email: `cursor.com` - PDF patterns: `Cursor`, `Anysphere`, `cursor.com` ### v1.4.0 (2026-02-05) - **CORRECÇÃO CRÍTICA:** ID moeda EUR = **2** (não 1) - Despesas com currency=1 não aparecem em relatórios - Corrigidas 22 despesas existentes via `UPDATE tblexpenses SET currency = 2 WHERE currency = 1` - Documentada tabela tblcurrencies com IDs correctos ### v1.3.0 (2026-02-05) - **Conversão USD → EUR obrigatória** (taxa ~0.92, $19.99 → €18.39) - **Processamento em lote** quando utilizador pede "criar todas" - Tickets a saltar documentados (payment receipts, duplicados, sem PDF) - Localização credenciais BD: `app-config.php` - Regras 4 e 5 adicionadas ao protocolo de tickets - 9 despesas processadas em sessão de teste: €857.73 total ### v1.2.0 (2026-02-05) - Campo `expense_name` obrigatório com nome do fornecedor (via SQL UPDATE) - Campo `tax` é ID da taxa de imposto (1 = IVA 23%), não percentagem - Coluna BD é `reference_no`, não `reference` - Script Python para extracção de PDFs: `/home/ealmeida/scripts/extract_invoice_data.py` - 3 métodos de extracção PDF documentados (script, pdfplumber, pdftotext) - Workflow actualizado: processar UM A UM com confirmação - Removido: adicionar respostas aos tickets (apenas criar despesa + anexos) - SQL commands actualizados com expense_name ### v1.1.0 (2026-02-05) - Processamento de despesas a partir de tickets de contabilidade - Mapeamento automático email domain → categoria - Workflow completo com verificação de anexos PDF - Cópia automática de anexos ticket → despesa - Registo de ficheiros em tblfiles ### v1.0.0 (2026-02-05) - Versão inicial - Regra crítica de verificação de categorias - Lista de categorias de referência - Fluxos para criar/consultar despesas - Integração com MCP desk-crm-v3 - Anti-patterns documentados - Criada após limpeza de 9 categorias duplicadas --- **Criado:** 2026-02-05 **Actualizado:** 2026-02-12 **Motivo:** Sistema robusto 2026 - PDF obrigatorio, SFTP, arquivo mensal, correccoes consolidacao