Módulo Financeiro
Documentação técnica completa do módulo financeiro — faturas, orçamentos, produtos, procedimentos, relatórios de vendas, ticket médio, conciliação automática e integrações bancárias.
Visão Geral
Orçamento (DRAFT)
↓ aprovação
Fatura (PENDING)
↓ pagamento parcial
Parcialmente Paga (PARTIAL)
↓ pagamento total
Paga (PAID)
↓ automático
NFS-e Emitida → Conciliação BancáriaModelo de Dados
Orçamento (Quote)
interface QuoteDTO {
id: string;
organizationId: string;
patientId: string;
number: string; // ex: "ORC-2026-00045"
status: 'DRAFT' | 'SENT' | 'APPROVED' | 'REJECTED' | 'EXPIRED' | 'CONVERTED';
validUntil: string; // data de validade
items: QuoteItemDTO[];
subtotal: number;
discount: number;
total: number;
notes?: string;
convertedInvoiceId?: string; // preenchido após conversão
createdAt: string;
}
interface QuoteItemDTO {
id: string;
type: 'PROCEDURE' | 'PRODUCT' | 'SERVICE';
referenceId?: string; // procedureId ou productId
description: string;
quantity: number;
unitPrice: number;
discount: number;
total: number;
}Fatura (Invoice)
interface InvoiceDTO {
id: string;
organizationId: string;
patientId: string;
appointmentId?: string;
quoteId?: string; // origem do orçamento
number: string; // ex: "CLI-2026-00123"
status: 'DRAFT' | 'PENDING' | 'PAID' | 'PARTIAL' | 'CANCELLED' | 'REFUNDED';
items: InvoiceItemDTO[];
subtotal: number;
discount: number;
total: number;
dueDate?: string;
paidAt?: string;
notes?: string;
nfseNumber?: string;
nfseUrl?: string;
payments: PaymentDTO[];
createdAt: string;
}
interface InvoiceItemDTO {
id: string;
type: 'PROCEDURE' | 'PRODUCT' | 'SERVICE';
procedureId?: string;
productId?: string;
description: string;
quantity: number;
unitPrice: number;
discount: number;
total: number;
}
interface PaymentDTO {
id: string;
invoiceId: string;
method: 'CASH' | 'CREDIT_CARD' | 'DEBIT_CARD' | 'PIX' | 'BANK_SLIP' | 'INSURANCE' | 'CREDIT';
amount: number;
gatewayId?: string;
gatewayStatus?: string;
installments: number;
paidAt?: string;
createdAt: string;
}Produto (Product)
interface ProductDTO {
id: string;
organizationId: string;
name: string;
sku?: string;
category: string;
unitPrice: number;
costPrice?: number;
stockQuantity: number;
minStockAlert: number;
unit: 'UN' | 'ML' | 'G' | 'KG' | 'L' | 'CX';
active: boolean;
}Procedimento (Procedure)
interface ProcedureDTO {
id: string;
organizationId: string;
name: string;
category: string;
description?: string;
defaultPrice: number;
durationMinutes: number;
requiresRoom: boolean;
active: boolean;
pricingTable: ProcedurePricingDTO[];
}
interface ProcedurePricingDTO {
id: string;
label: string; // ex: "Particular", "Convênio X"
price: number;
active: boolean;
}Conciliação (BankReconciliation)
interface BankTransactionDTO {
id: string;
organizationId: string;
bankAccountId: string;
externalId: string; // ID do banco / OFX
date: string;
amount: number; // positivo = crédito, negativo = débito
description: string;
type: 'CREDIT' | 'DEBIT';
status: 'UNMATCHED' | 'MATCHED' | 'IGNORED' | 'DIVERGENT';
matchedPaymentId?: string;
matchedAt?: string;
reconciledBy?: string; // userId (manual) | 'SYSTEM' (automático)
}Orçamentos
Criação
POST /quotes
{
"patientId": "uuid",
"validUntil": "2026-06-30",
"items": [
{
"type": "PROCEDURE",
"referenceId": "proc-uuid",
"description": "Botox facial",
"quantity": 1,
"unitPrice": 850.00,
"discount": 0
}
],
"notes": "Pacote completo"
}Conversão em Fatura
POST /quotes/:id/convert
Response 201:
{
"invoiceId": "uuid",
"invoiceNumber": "CLI-2026-00201"
}Regras de conversão:
- Orçamento precisa estar em status
APPROVEDouSENT - Itens são copiados para a fatura sem alteração de preço
- O orçamento passa para
CONVERTEDe recebe oconvertedInvoiceId - Se o orçamento estiver expirado, é necessário reativá-lo (Admin)
Estados do Orçamento
| Status | Descrição |
|---|---|
DRAFT | Rascunho — editável, não enviado |
SENT | Enviado ao paciente para aprovação |
APPROVED | Paciente aprovou — pode ser convertido |
REJECTED | Paciente recusou |
EXPIRED | Passou da data validUntil sem aprovação |
CONVERTED | Fatura criada a partir deste orçamento |
Produtos
Endpoints
| Método | Endpoint | Papel mínimo | Descrição |
|---|---|---|---|
GET | /products | Receptionist | Listar produtos ativos |
POST | /products | Admin | Cadastrar produto |
PUT | /products/:id | Admin | Atualizar produto |
DELETE | /products/:id | Admin | Inativar produto |
GET | /products/low-stock | Admin | Produtos abaixo do estoque mínimo |
Integração com Estoque
Quando um produto é adicionado a uma fatura paga:
- O sistema decrementa
stockQuantitydo produto - Se
stockQuantity ≤ minStockAlert→ notificação para Admin - O movimento é registrado no log de estoque com referência à fatura
Procedimentos
Tabela de Preços
Cada procedimento pode ter múltiplas entradas na tabela de preços:
POST /procedures/:id/pricing
{
"label": "Convênio ABC",
"price": 320.00
}No momento de criar uma fatura ou orçamento, o operador seleciona qual entrada da tabela usar. O preço selecionado é copiado para o item e não se altera se a tabela for atualizada posteriormente.
Duração e Agendamento
O campo durationMinutes é usado pelo módulo de agendamento para bloquear a agenda do profissional automaticamente.
Faturamento
Geração de Fatura (manual, pós-atendimento)
Quando um agendamento é marcado como CONCLUÍDO, nenhuma fatura é criada automaticamente. O operador deve criá-la manualmente:
- Abrir o agendamento concluído
- Clicar em "Gerar Fatura"
- Confirmar o procedimento e a entrada de preço desejada
- A fatura é criada com status
PENDINGvinculada ao agendamento e ao paciente
Registro de Pagamento
POST /invoices/:id/payments
{
"method": "PIX",
"amount": 250.00,
"installments": 1
}Lógica pós-pagamento:
- Soma pagamentos existentes + novo
- Se soma
= total→ statusPAID - Se soma
< total→ statusPARTIAL - Se
methodé gateway → roteado ao provider ativo viaIPaymentGateway - Se
PAID→ enfileiraEmitNfseJobno Hangfire
Webhook do Gateway Ativo
O endpoint é polimórfico: o sistema identifica o provider configurado pelo tenant e aplica a validação e o parsing corretos.
POST /webhooks/payment
1. Resolve o provider ativo do tenant (via tenant settings)
2. Valida assinatura HMAC-SHA256 com o secret do provider
3. Verifica idempotency key (evita processamento duplicado)
4. Normaliza o evento para PaymentConfirmedEvent (provider-agnostic)
5. Atualiza status da fatura para PAID
6. Enfileira EmitNfseJob
7. Enfileira ReconcilePaymentJob → conciliação automáticaConciliação Bancária Automática
Visão Geral
O engine de conciliação cruza automaticamente as transações bancárias importadas com os pagamentos registrados no sistema.
Fontes de Dados Bancários
| Fonte | Disponibilidade | Descrição |
|---|---|---|
| Webhook Gateway | MVP | Provider ativo do tenant envia confirmações em tempo real |
| Import OFX | Pós-MVP | Upload manual de extrato bancário |
| Open Finance | Roadmap V2 | Conexão direta via API do banco |
Engine de Matching Automático
O job ReconcilePaymentJob executa as seguintes regras em ordem de prioridade:
Regra 1 — Gateway ID (confiança: 100%)
Transação contém o gatewayId do pagamento
→ MATCHED automaticamente
Regra 2 — Valor exato + data próxima (confiança: 95%)
amount == payment.amount
|date - payment.paidAt| ≤ 3 dias
→ MATCHED automaticamente
Regra 3 — Valor exato + período (confiança: 80%)
amount == payment.amount
|date - payment.paidAt| ≤ 7 dias
→ MATCHED com flag "revisar"
Regra 4 — Valor aproximado (±2%) + data próxima (confiança: 60%)
|amount - payment.amount| / payment.amount ≤ 0.02
|date - payment.paidAt| ≤ 3 dias
→ DIVERGENTE → revisão manual obrigatóriaEstados de Conciliação
| Status | Descrição |
|---|---|
UNMATCHED | Transação importada, nenhum pagamento encontrado |
MATCHED | Conciliado automaticamente (confiança ≥ 95%) |
DIVERGENT | Match encontrado mas com diferença de valor ou data |
IGNORED | Ignorado manualmente pelo operador (ex: estorno interno) |
Reconciliação Manual
Quando o sistema não consegue fazer o match automático:
POST /bank-transactions/:txId/reconcile
{
"paymentId": "uuid",
"notes": "Confirmado com extrato PDF do banco"
}O campo reconciledBy é preenchido com o userId do operador.
Relatório de Conciliação
GET /reports/reconciliation?from=2026-05-01&to=2026-05-31
Response 200:
{
"period": { "from": "2026-05-01", "to": "2026-05-31" },
"totalTransactions": 312,
"matched": 298,
"divergent": 8,
"unmatched": 6,
"matchRate": "95.5%",
"totalCredited": 48500.00,
"totalReconciled": 46200.00,
"pendingAmount": 2300.00
}Relatório de Vendas e Ticket Médio
Relatório de Vendas
GET /reports/sales?from=2026-05-01&to=2026-05-31&groupBy=procedure
Response 200:
{
"period": { "from": "2026-05-01", "to": "2026-05-31" },
"totalRevenue": 58400.00,
"totalInvoices": 142,
"averageTicket": 411.27,
"byProcedure": [
{ "name": "Botox facial", "quantity": 38, "revenue": 32300.00, "share": "55.3%" }
],
"byProduct": [
{ "name": "Sérum vitamina C", "quantity": 15, "revenue": 2250.00, "share": "3.9%" }
],
"byProfessional": [
{ "name": "Dra. Silva", "invoices": 62, "revenue": 27800.00 }
],
"byPaymentMethod": {
"PIX": 28000.00,
"CREDIT_CARD": 22000.00,
"CASH": 8400.00
},
"dailyEvolution": [
{ "date": "2026-05-01", "revenue": 2100.00, "invoices": 5 }
]
}Parâmetros disponíveis:
| Parâmetro | Tipo | Descrição |
|---|---|---|
from | date | Data inicial (obrigatório) |
to | date | Data final (obrigatório) |
groupBy | string | procedure, product, professional, method |
professionalId | uuid | Filtrar por profissional |
categoryId | uuid | Filtrar por categoria de procedimento |
unitId | uuid | Filtrar por unidade |
Ticket Médio
O ticket médio é calculado como:
averageTicket = totalRevenue / totalInvoices (status PAID ou PARTIAL)Métricas disponíveis no relatório:
| Métrica | Descrição |
|---|---|
| Ticket geral | Média de todas as faturas do período |
| Ticket por profissional | Média por atendimento de cada profissional |
| Ticket por categoria | Média de procedimentos de cada categoria |
| Evolução do ticket | Comparação com período anterior |
Faturamento — Endpoints da API
| Método | Endpoint | Papel mínimo | Descrição |
|---|---|---|---|
GET | /invoices | Receptionist | Listar faturas |
POST | /invoices | Receptionist | Criar fatura |
GET | /invoices/:id | Receptionist | Detalhe da fatura |
PUT | /invoices/:id | Admin | Editar fatura (somente DRAFT) |
POST | /invoices/:id/payments | Receptionist | Registrar pagamento |
GET | /quotes | Receptionist | Listar orçamentos |
POST | /quotes | Receptionist | Criar orçamento |
POST | /quotes/:id/convert | Receptionist | Converter em fatura |
GET | /products | Receptionist | Listar produtos |
POST | /products | Admin | Cadastrar produto |
GET | /procedures | Receptionist | Listar procedimentos |
POST | /procedures | Admin | Cadastrar procedimento |
GET | /bank-transactions | Admin | Listar transações bancárias |
POST | /bank-transactions/:id/reconcile | Admin | Conciliar manualmente |
GET | /reports/sales | Admin | Relatório de vendas |
GET | /reports/cashflow | Admin | Fluxo de caixa |
GET | /reports/reconciliation | Admin | Relatório de conciliação |
POST | /webhooks/payment | — (HMAC, provider-agnostic) | Webhook do gateway ativo |
Integração de Gateway de Pagamento
O sistema utiliza uma abstração polimórfica (IPaymentGateway) para isolar o código de negócio de qualquer provider específico. O gateway ativo é determinado pela integração configurada pelo tenant.
Seleção do Provider
IPaymentGateway gateway = _gatewayFactory.Create(tenant.PreferredGateway);
var result = await gateway.ProcessPaymentAsync(paymentRequest);O GatewayFactory resolve o provider correto em tempo de execução com base nas configurações do tenant. Novos providers são adicionados implementando IPaymentGateway sem alterar o fluxo de faturamento.
Configuração
Em Configurações → Integrações → Gateway de Pagamento, selecione o provider desejado e preencha as credenciais correspondentes (API Key, Webhook Secret etc.). O formulário se adapta dinamicamente ao provider escolhido.
Métodos Suportados
| Método | Fluxo | Conciliação |
|---|---|---|
| PIX | QR Code gerado pelo provider; status via webhook | Automática (Regra 1) |
| Cartão de Crédito | Link de pagamento seguro; parcelamento conforme provider | Automática (Regra 1) |
| Cartão de Débito | Processamento imediato | Automática (Regra 1) |
| Boleto | PDF gerado e enviado ao paciente | Automática após confirmação |
Integração NFE.io (NFS-e)
Após pagamento confirmado:
- Hangfire enfileira
EmitNfseJob - Job monta payload com dados da fatura e do paciente
- Chama API da NFE.io
- Armazena
nfse_numberenfse_urlna fatura - PDF disponível para download
Política de retry: 5 tentativas com backoff exponencial (1s, 5s, 25s, 125s, 625s).
Segurança Financeira
tenantIdepatientIdsão imutáveis após criação da fatura- Descontos acima do limite configurado requerem papel Admin ou superior
- Todos os ajustes financeiros são registrados no audit log
- Webhooks validados por assinatura HMAC-SHA256 (chave por tenant)
- Processamento idempotente via
idempotency_key - Transações de conciliação têm trilha de auditoria completa (quem, quando, motivo)
Permissões
| Ação | Owner | Admin | Receptionist | Professional | Viewer |
|---|---|---|---|---|---|
| Ver faturas / orçamentos | ✅ | ✅ | ✅ | ❌ | ✅ |
| Criar fatura / orçamento | ✅ | ✅ | ✅ | ❌ | ❌ |
| Converter orçamento em fatura | ✅ | ✅ | ✅ | ❌ | ❌ |
| Registrar pagamento | ✅ | ✅ | ✅ | ❌ | ❌ |
| Aplicar desconto | ✅ | ✅ | Até limite% | ❌ | ❌ |
| Gerenciar produtos | ✅ | ✅ | ❌ | ❌ | ❌ |
| Gerenciar procedimentos | ✅ | ✅ | ❌ | ❌ | ❌ |
| Conciliar manualmente | ✅ | ✅ | ❌ | ❌ | ❌ |
| Ver relatório de vendas | ✅ | ✅ | ❌ | ❌ | ✅ |
| Emitir NFS-e | ✅ | ✅ | ❌ | ❌ | ❌ |