# ClientFlow v3 — Clientes, documentos e envios

Esta versão separa responsabilidades para evitar que a página da oportunidade fique demasiado pesada.

## Princípio de UI

- **Clientes**: dados fiscais, NIF, morada, contactos e ligação Jasmin.
- **Oportunidades**: estado da potencial compra, produtos, documentos associados e envios.
- **Documentos**: orçamentos e faturas em `commercial_documents`.
- **Envios**: registos Packlink em `shipments`.

## Páginas principais

### `/customers`

Lista clientes locais normalizados, com pesquisa por nome, NIF, email, telefone ou `jasmin_customer_party_key`.

Permite criar uma ficha mínima de cliente com nome e NIF.

### `/customers/{id}`

Ficha completa de cliente:

- nome fiscal;
- NIF;
- email;
- telefone;
- morada fiscal;
- código postal;
- cidade;
- país;
- Jasmin `customerPartyKey`;
- Jasmin ID;
- oportunidades associadas;
- documentos Jasmin;
- envios Packlink.

### `/opportunities/{id}`

A oportunidade mantém apenas um resumo compacto do cliente e um seletor para associar uma ficha local.

Botões operacionais:

- **Criar orçamento**: cria sempre um novo orçamento Jasmin.
- **Converter em fatura**: converte o orçamento ativo/mais recente em fatura.

## Modelo de dados

Novas tabelas/colunas usadas nesta versão:

- `customers`
- `commercial_documents`
- `commercial_document_lines`
- `shipments`
- `opportunities.local_customer_id`

O campo antigo `opportunities.customer_id` é mantido para compatibilidade com dados legados/Chatwoot. A ligação nova e normalizada usa `opportunities.local_customer_id`.

## Fluxo Jasmin validado

1. Cliente associado à oportunidade.
2. `find_or_create_customer_for_opportunity()` usa primeiro o cliente local.
3. NIF é enviado para Jasmin sem prefixo `PT`.
4. Se o cliente não existir no Jasmin, cria cliente com `partyKey = CF{NIF}`.
5. Cria orçamento `ORC / ORC2026`.
6. Para faturar, chama `POST /billing/invoices/fromQuotation/{quotationId}` com body `{}`.

## Regras importantes

- Não enviar `electronicMail` nem `telephone` vazios para Jasmin.
- OData Jasmin deve usar `$top <= 100`.
- Packlink usa API key no header `Authorization`, sem `Bearer`.
- Para cotação Packlink em Portugal, normalizar CP para 4 dígitos.

## Estratégia de refactor

Esta v3 ainda não reescreve o `admin_dashboard.py` totalmente. A prioridade foi criar a camada de domínio e a navegação operacional correta. O próximo passo recomendado é extrair progressivamente:

- `app/admin_ui/pages/customers.py`
- `app/admin_ui/pages/opportunities.py`
- `app/admin_ui/pages/integrations.py`
- `app/admin_ui/components.py`
