Skip to content

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ária

Modelo de Dados

Orçamento (Quote)

typescript
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)

typescript
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)

typescript
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)

typescript
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)

typescript
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

http
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

http
POST /quotes/:id/convert

Response 201:
{
  "invoiceId": "uuid",
  "invoiceNumber": "CLI-2026-00201"
}

Regras de conversão:

  • Orçamento precisa estar em status APPROVED ou SENT
  • Itens são copiados para a fatura sem alteração de preço
  • O orçamento passa para CONVERTED e recebe o convertedInvoiceId
  • Se o orçamento estiver expirado, é necessário reativá-lo (Admin)

Estados do Orçamento

StatusDescrição
DRAFTRascunho — editável, não enviado
SENTEnviado ao paciente para aprovação
APPROVEDPaciente aprovou — pode ser convertido
REJECTEDPaciente recusou
EXPIREDPassou da data validUntil sem aprovação
CONVERTEDFatura criada a partir deste orçamento

Produtos

Endpoints

MétodoEndpointPapel mínimoDescrição
GET/productsReceptionistListar produtos ativos
POST/productsAdminCadastrar produto
PUT/products/:idAdminAtualizar produto
DELETE/products/:idAdminInativar produto
GET/products/low-stockAdminProdutos abaixo do estoque mínimo

Integração com Estoque

Quando um produto é adicionado a uma fatura paga:

  1. O sistema decrementa stockQuantity do produto
  2. Se stockQuantity ≤ minStockAlert → notificação para Admin
  3. 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:

http
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:

  1. Abrir o agendamento concluído
  2. Clicar em "Gerar Fatura"
  3. Confirmar o procedimento e a entrada de preço desejada
  4. A fatura é criada com status PENDING vinculada ao agendamento e ao paciente

Registro de Pagamento

http
POST /invoices/:id/payments

{
  "method": "PIX",
  "amount": 250.00,
  "installments": 1
}

Lógica pós-pagamento:

  1. Soma pagamentos existentes + novo
  2. Se soma = total → status PAID
  3. Se soma < total → status PARTIAL
  4. Se method é gateway → roteado ao provider ativo via IPaymentGateway
  5. Se PAID → enfileira EmitNfseJob no 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ática

Conciliaçã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

FonteDisponibilidadeDescrição
Webhook GatewayMVPProvider ativo do tenant envia confirmações em tempo real
Import OFXPós-MVPUpload manual de extrato bancário
Open FinanceRoadmap V2Conexã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ória

Estados de Conciliação

StatusDescrição
UNMATCHEDTransação importada, nenhum pagamento encontrado
MATCHEDConciliado automaticamente (confiança ≥ 95%)
DIVERGENTMatch encontrado mas com diferença de valor ou data
IGNOREDIgnorado manualmente pelo operador (ex: estorno interno)

Reconciliação Manual

Quando o sistema não consegue fazer o match automático:

http
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

http
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

http
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âmetroTipoDescrição
fromdateData inicial (obrigatório)
todateData final (obrigatório)
groupBystringprocedure, product, professional, method
professionalIduuidFiltrar por profissional
categoryIduuidFiltrar por categoria de procedimento
unitIduuidFiltrar 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étricaDescrição
Ticket geralMédia de todas as faturas do período
Ticket por profissionalMédia por atendimento de cada profissional
Ticket por categoriaMédia de procedimentos de cada categoria
Evolução do ticketComparação com período anterior

Faturamento — Endpoints da API

MétodoEndpointPapel mínimoDescrição
GET/invoicesReceptionistListar faturas
POST/invoicesReceptionistCriar fatura
GET/invoices/:idReceptionistDetalhe da fatura
PUT/invoices/:idAdminEditar fatura (somente DRAFT)
POST/invoices/:id/paymentsReceptionistRegistrar pagamento
GET/quotesReceptionistListar orçamentos
POST/quotesReceptionistCriar orçamento
POST/quotes/:id/convertReceptionistConverter em fatura
GET/productsReceptionistListar produtos
POST/productsAdminCadastrar produto
GET/proceduresReceptionistListar procedimentos
POST/proceduresAdminCadastrar procedimento
GET/bank-transactionsAdminListar transações bancárias
POST/bank-transactions/:id/reconcileAdminConciliar manualmente
GET/reports/salesAdminRelatório de vendas
GET/reports/cashflowAdminFluxo de caixa
GET/reports/reconciliationAdminRelató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

csharp
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étodoFluxoConciliação
PIXQR Code gerado pelo provider; status via webhookAutomática (Regra 1)
Cartão de CréditoLink de pagamento seguro; parcelamento conforme providerAutomática (Regra 1)
Cartão de DébitoProcessamento imediatoAutomática (Regra 1)
BoletoPDF gerado e enviado ao pacienteAutomática após confirmação

Integração NFE.io (NFS-e)

Após pagamento confirmado:

  1. Hangfire enfileira EmitNfseJob
  2. Job monta payload com dados da fatura e do paciente
  3. Chama API da NFE.io
  4. Armazena nfse_number e nfse_url na fatura
  5. PDF disponível para download

Política de retry: 5 tentativas com backoff exponencial (1s, 5s, 25s, 125s, 625s).


Segurança Financeira

  • tenantId e patientId sã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çãoOwnerAdminReceptionistProfessionalViewer
Ver faturas / orçamentos
Criar fatura / orçamento
Converter orçamento em fatura
Registrar pagamento
Aplicar descontoAté limite%
Gerenciar produtos
Gerenciar procedimentos
Conciliar manualmente
Ver relatório de vendas
Emitir NFS-e

Desenvolvido com ❤️ pela equipe FastGivr.