docs: centraliza e reescreve a documentação em documentação/
- reúne todos os .md de ScoreOdonto numa pasta única (fonte única, dentro do repo versionado pelo Gitea) - arquitetura.md: reescrito p/ o estado REAL (3 VPS, banco compartilhado dev/prod, sem staging/listener/registry fantasmas) - deploy.md: reescrito p/ o estado-ALVO (Gitea CI/CD: push->build->registry->deploy por tag) - DEPLOY-PRODUCAO.md: runbook operacional executável (fluxo manual real de hoje) - VERDADE-HOJE.md: auditoria (codigo/banco/containers/deploy/versao/rollback) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
# Backlog V1 — Financeiro (ScoreOdonto)
|
||||
|
||||
> Derivado do `AUDITORIA-FUNCIONAL-SCOREODONTO.md` (Deep-dive 1). Apenas planejamento — nada implementado.
|
||||
> Padrões do projeto a seguir em toda história: `tenantGuard` + escopo `clinica_id`, soft delete onde fizer sentido,
|
||||
> `registrarAudit` nas mutações, helpers `buildInsert/buildUpdate`, frontend `useToast`/`useConfirm`,
|
||||
> rebuild da imagem DEV (frontend nginx + backend) — ver `feedback_deploy_workflow`.
|
||||
> Estimativa: P (≤0,5 dia) · M (1–2 dias) · G (3+ dias).
|
||||
|
||||
Estado atual relevante: tabela `financeiro(id, clinica_id, pacientenome[texto], descricao, valor, datavencimento, status[Pago/Pendente/Atrasado], formapagamento, tipo[RECEITA/DESPESA])`, 0 linhas. Único elo: `LancarProcedimentoModal → saveFinanceiro`.
|
||||
|
||||
---
|
||||
|
||||
## ÉPICO 1 — Ligar o Financeiro (FKs de origem) — ✅ IMPLEMENTADO (DEV)
|
||||
**Por quê:** sem `profissional_id`/`paciente_id` não há comissão, repasse, nem relatório por paciente/profissional. É o pré-requisito de tudo.
|
||||
|
||||
> **Status (DEV):** H1.1 ✅ (FKs + índices + soft delete + cron "Atrasado" — também cobre H2.2/H2.4) · H1.2 ✅ (`FinanceiroView` envia `paciente_id`) · H1.3 ✅ (`LancarProcedimentoModal` envia `paciente_id`/`procedimento_id`/`origem='procedimento'`). **Extra entregue:** prontuário de procedimentos realizados (`procedimentos_realizados` + fotos antes/depois) com regra de **sem comissão** em reabertura pelo mesmo profissional e em "refazer na garantia".
|
||||
|
||||
### H1.1 — Adicionar chaves de ligação ao lançamento — **M**
|
||||
- **Como** sistema, **quero** que cada lançamento referencie paciente/profissional/origem **para** rastrear e relatar.
|
||||
- **Migração:** `ALTER TABLE financeiro ADD COLUMN IF NOT EXISTS paciente_id TEXT, profissional_id TEXT, procedimento_id TEXT, agendamento_id TEXT, contrato_id TEXT, origem TEXT DEFAULT 'manual', pago_em TIMESTAMPTZ;` + índices `(clinica_id, status, datavencimento)` e `(clinica_id, profissional_id)`.
|
||||
- **Backend:** `POST/PUT /api/financeiro` aceitam os novos campos (já é `buildInsert`); manter `clinica_id` do `tenantGuard`.
|
||||
- **Critérios:** lançamento salva e retorna os campos; antigos (sem FK) continuam válidos; `node --check` + E2E (criar com `profissional_id` e ler).
|
||||
- **Risco:** `pacientenome` legado permanece (não migrar dado, base vazia em DEV).
|
||||
|
||||
### H1.2 — Vincular paciente real (não só nome) no lançamento — **M**
|
||||
- **Como** recepção, **quero** selecionar o paciente cadastrado **para** o financeiro aparecer no histórico do paciente.
|
||||
- **Frontend:** no modal de novo lançamento (`FinanceiroView`), trocar campo de nome livre por **busca de paciente** (reusar `getPacientes` + componente do `AppointmentModal`); preencher `paciente_id` + `pacientenome` (snapshot).
|
||||
- **Critérios:** ao gravar, `paciente_id` preenchido; busca por nome/CPF/telefone funciona; manter compatível com lançamento sem paciente (despesa).
|
||||
|
||||
### H1.3 — Procedimento concluído gera lançamento com FKs — **M**
|
||||
- **Como** sistema, **quero** que ao concluir um procedimento na agenda o lançamento já venha com `paciente_id/profissional_id/procedimento_id/agendamento_id`.
|
||||
- **Backend/Frontend:** `LancarProcedimentoModal` (que já chama `saveFinanceiro`) passa as FKs; valor sugerido = `procedimentos.valorparticular`.
|
||||
- **Critérios:** lançamento criado a partir de procedimento tem `origem='procedimento'` + as 4 FKs; valor pré-preenchido.
|
||||
|
||||
---
|
||||
|
||||
## ÉPICO 2 — Contas a Receber / a Pagar
|
||||
**Por quê:** o Financeiro hoje é uma lista plana; falta gestão de vencimentos, baixa e despesas estruturadas.
|
||||
|
||||
### H2.1 — Baixa de pagamento com data e meio — **P** — ✅ IMPLEMENTADO (DEV)
|
||||
> Modal `BaixaPagamentoModal` (data + forma) no `FinanceiroView` substitui o "marcar Pago" direto; `useConfirm` p/ valores ≥ R$ 1.000. `PUT /api/financeiro/:id` carimba `pago_em` (se ausente) e registra `registrarAudit` (`baixa_pagamento`, idempotente). **Bug latente corrigido:** `enforceUppercase` (service) mandava `PAGO`/`PIX`, violando os CHECK do banco (`Pago`/`Pix`); backend agora normaliza `status`/`formapagamento`/`tipo` no POST e no PUT (`normalizeFinanceiro`). Era a causa de a tabela `financeiro` ficar sempre vazia.
|
||||
- **Como** financeiro, **quero** confirmar pagamento registrando `pago_em` e a forma **para** ter caixa real.
|
||||
- **Backend:** `PUT /api/financeiro/:id` aceita `status='Pago'` + `pago_em=NOW()` (ou data informada). (PUT já é tenant-scoped.)
|
||||
- **Frontend:** botão "Confirmar pagamento" abre mini-form (data + forma) em vez de marcar direto; `useConfirm` para valores altos.
|
||||
- **Critérios:** ao confirmar, grava `pago_em` e `formapagamento`; KPI Recebido considera `pago_em`.
|
||||
|
||||
### H2.2 — Job de "Atrasado" automático — **P**
|
||||
- **Como** sistema, **quero** marcar lançamentos vencidos como Atrasado **para** o KPI ser fiel.
|
||||
- **Backend:** cron (boot + 6h, padrão `processarVigenciasContratos`): `UPDATE financeiro SET status='Atrasado' WHERE status='Pendente' AND datavencimento < CURRENT_DATE AND deleted_at IS NULL`.
|
||||
- **Pré-req:** adicionar `deleted_at` ao `financeiro` (soft delete) — hoje DELETE é físico (`H2.4`).
|
||||
- **Critérios:** lançamento vencido vira Atrasado sem ação manual.
|
||||
|
||||
### H2.3 — Despesas com fornecedor e centro de custo — **M** — ✅ IMPLEMENTADO (DEV)
|
||||
> Migração `fornecedor/centro_custo/recorrente`. `NovoLancamentoModal` ganhou toggle **Receita/Despesa**: em DESPESA o paciente fica opcional e aparecem fornecedor + centro de custo (datalist com presets) + recorrente. Normalização de enums já cobre o casing. **H3.3 entregue junto:** `porCategoria` (despesas por centro de custo) no `GET /api/financeiro/relatorio` + bloco no `ReportsView`.
|
||||
- **Como** financeiro, **quero** classificar despesas (fornecedor, categoria/centro de custo, recorrente) **para** análise de saída.
|
||||
- **Migração:** `ALTER TABLE financeiro ADD COLUMN IF NOT EXISTS fornecedor TEXT, centro_custo TEXT, recorrente BOOLEAN DEFAULT false;`
|
||||
- **Frontend:** quando `tipo=DESPESA`, exibir fornecedor + centro de custo + recorrente.
|
||||
- **Critérios:** despesa salva com classificação; filtro por categoria no relatório (Épico 3).
|
||||
|
||||
### H2.4 — Soft delete + auditoria do lançamento — **P**
|
||||
- **Migração:** `ALTER TABLE financeiro ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;`
|
||||
- **Backend:** `DELETE /api/financeiro/:id` → `UPDATE ... SET deleted_at=NOW()`; GET filtra `deleted_at IS NULL`; `registrarAudit`.
|
||||
- **Critérios:** excluir não apaga fisicamente; some das listas; audit registra autor.
|
||||
|
||||
---
|
||||
|
||||
## ÉPICO 3 — Relatórios financeiros essenciais
|
||||
**Por quê:** hoje só existe `/api/reports/productivity`.
|
||||
|
||||
> **Status (DEV):** H3.1 ✅ + H3.2 ✅ — endpoint único `GET /api/financeiro/relatorio?inicio&fim&pacienteId&profissionalId` (tenantGuard, escopo `clinica_id`, soft delete) retornando `resumo` (recebido/a_receber/atrasado/despesa), `porMes`, `porForma` e `lancamentos`. UI em `ReportsView` (componente `RelatorioFinanceiro`): KPIs + barras por mês + por forma de pagamento + tabela + export CSV, com filtros de profissional (select) e paciente (busca). H3.3 (despesas por categoria/centro de custo) depende de H2.3 — pendente.
|
||||
|
||||
### H3.1 — A receber/recebido por período — **M**
|
||||
- **Backend:** `GET /api/financeiro/relatorio?inicio=&fim=` (tenantGuard) → totais por status, por dia/mês, por forma de pagamento.
|
||||
- **Frontend:** seção em `ReportsView` (ou aba no Financeiro) com gráfico simples (reaproveitar padrão do Dashboard).
|
||||
- **Critérios:** filtros de período; soma confere com lançamentos.
|
||||
|
||||
### H3.2 — Financeiro por paciente e por profissional — **M**
|
||||
- **Backend:** `GET /api/financeiro/relatorio?pacienteId=` e `?profissionalId=` (usa as FKs do Épico 1).
|
||||
- **Critérios:** lista lançamentos do paciente/profissional no período; base para o repasse (V2).
|
||||
|
||||
### H3.3 — Despesas por categoria/centro de custo — **P** — ✅ IMPLEMENTADO (DEV, junto com H2.3)
|
||||
- **Backend:** agregação por `centro_custo` no relatório (`porCategoria`). ✓
|
||||
- **Critérios:** total por categoria no período. ✓ (bloco "Despesas por centro de custo" no `ReportsView`)
|
||||
|
||||
---
|
||||
|
||||
## ÉPICO 4 — Orçamento → Contrato → Financeiro
|
||||
**Por quê:** os 3 módulos existem isolados; ligá-los fecha o ciclo comercial.
|
||||
|
||||
> **Status (DEV):** H4.1 ✅ — orçamento **Assinado** gera contas a receber automaticamente (entrada + N parcelas mensais a partir de `data_inicio`, `origem='orcamento'`, FKs paciente/profissional/`contrato_id`), idempotente via `orcamentos.financeiro_gerado`; helper `gerarFinanceiroOrcamento`; toast no `OrcamentoModal`.
|
||||
> **H4.2 ✅** — contrato (motor) com **cobrança recorrente**: campos `contratos.{cobranca_automatica,valor_recorrente,periodicidade(mensal|anual),dia_cobranca}` + `financeiro.competencia`. Helper `gerarCobrancaContratoCompetencia` (idempotente por contrato_id+competência, origem='contrato', dia clamp 28) e `gerarCobrancasRecorrentes` rodando no cron `processarVigenciasContratos` (boot+6h) sobre contratos **vigentes**. Endpoint manual `POST /api/contratos/instancias/:id/gerar-cobranca`. UI: config no `GeradorModal` + botão "Gerar cobrança do mês" nos contratos vigentes (`ContratosView`).
|
||||
> **Renovação automática ✅** — no mesmo cron, contratos `renovacao='automatica'` com `data_fim` vencido estendem a vigência por nova duração igual à anterior (`data_inicio=data_fim+1`, `data_fim += (data_fim−data_inicio)+1`; fallback +12 meses sem início) e reabilitam o aviso de vencimento.
|
||||
|
||||
### H4.1 — Orçamento aprovado gera contas a receber — **M** — ✅
|
||||
- **Como** recepção, **quero** que ao aprovar um orçamento as parcelas entrem no Financeiro **para** não relançar manualmente.
|
||||
- **Backend:** ao mudar status do orçamento para "Aprovado/Assinado", criar N lançamentos `RECEITA` (`origem='orcamento'`, `paciente_id`, vencimentos por parcela) a partir de `valor_total/entrada/parcelas`.
|
||||
- **Critérios:** aprovar orçamento de 3x cria 3 lançamentos pendentes com vencimentos; idempotente (não duplica).
|
||||
|
||||
### H4.2 — Contrato (motor) com cobrança recorrente — **M** (ponte p/ V2)
|
||||
- **Como** sistema, **quero** que um contrato vigente com vigência/parcelas gere lançamentos **para** cobrança recorrente.
|
||||
- **Backend:** estender o cron de vigência (`processarVigenciasContratos`) para emitir lançamentos conforme `valores`/periodicidade do contrato (`contrato_id` na FK).
|
||||
- **Critérios:** contrato mensal gera lançamento por competência; não duplica.
|
||||
|
||||
---
|
||||
|
||||
## Sequência sugerida (entrega incremental)
|
||||
1. **H1.1 → H2.4 → H2.2** (FKs + soft delete + atrasado) — base sólida e segura.
|
||||
2. **H1.2 → H1.3** (paciente real + procedimento→lançamento) — liga clínica↔financeiro.
|
||||
3. **H2.1 → H2.3** (baixa + despesas) — caixa fiel.
|
||||
4. **H3.1 → H3.2 → H3.3** (relatórios) — visão gerencial.
|
||||
5. **H4.1 → H4.2** (orçamento/contrato→financeiro) — fecha o ciclo e prepara o V2 (comissão/repasse).
|
||||
|
||||
## Definição de pronto (toda história)
|
||||
`node --check` (backend) + build OK (frontend) · endpoints com `tenantGuard` + escopo `clinica_id` · mutações com `registrarAudit` · E2E mínimo (criar/ler/escopo cross-tenant 403) · rebuild + recreate DEV · validado em `dev.scoreodonto.com`.
|
||||
|
||||
## Riscos / observações
|
||||
- Não migrar dados legados (base de DEV vazia); em PROD, planejar backfill de `paciente_id` por `pacientenome` (fuzzy) — fora do V1.
|
||||
- `formapagamento` é texto livre hoje; padronizar enum (PIX/Cartão/Boleto/Dinheiro) facilita relatórios e o V2 (gateways).
|
||||
- Comissão/Repasse é **V2** — depende do Épico 1 (FK `profissional_id`) entregue aqui.
|
||||
|
||||
---
|
||||
|
||||
## V2 — Comissão / Repasse — ✅ IMPLEMENTADO (DEV)
|
||||
Decisões: base **selecionável** (recebido=caixa via status Pago/pago_em · produção=competência via vencimento); percentual **por procedimento** com **regra padrão** de fallback (`comissao_regras.procedimento_id NULL`); **fechamento persistido** (snapshot imutável `repasses` + `registrarAudit`).
|
||||
- **Tabelas:** `comissao_regras (clinica_id, procedimento_id NULL=padrão, percentual)` (unique por clínica+proc); `repasses (snapshot por profissional: período, base, base_valor, comissao_valor, detalhes JSONB, status, criado_por)`.
|
||||
- **Cálculo** (`computeComissoes`): RECEITA, `sem_comissao=false`, `profissional_id NOT NULL`; pct = regra do procedimento → senão padrão → senão 0; agrupa por profissional com detalhe por lançamento. Garantia/reabertura do mesmo profissional (sem_comissao) já saem de fora.
|
||||
- **Endpoints:** `GET/PUT/DELETE /api/comissoes/regras`, `GET /api/comissoes/relatorio`, `POST /api/repasses/fechar`, `GET /api/repasses[/:id]`, `POST /api/repasses/:id/cancelar`.
|
||||
- **Frontend:** `ComissoesView` (menu COMISSÕES, owner-only) com abas Relatório/Fechamento (toggle recebido/produção, expandível por profissional, botão Fechar), Regras de % (padrão + por procedimento) e Histórico (lista de fechamentos + cancelar).
|
||||
- **Gaps (✅ implementados DEV):** (1) **override de % por profissional** (`comissao_regras.profissional_id`; prioridade prof > procedimento > padrão; unique inclui profissional); (2) **custo de laboratório** por procedimento (`comissao_regras.custo_lab`) deduzido do valor antes da comissão (base líquida); (3) **marcar repasse como pago** (`POST /api/repasses/:id/pagar`) que gera **DESPESA** no financeiro (origem='repasse', centro_custo='COMISSÕES', sem_comissao, Pago) e grava `repasses.pago_em/financeiro_id`; repasse pago não pode ser cancelado.
|
||||
- **Estorno ao cancelar (✅):** cancelar repasse **pago** agora soft-deleta a DESPESA gerada (`financeiro.deleted_at`) e zera `repasses.financeiro_id`; auditoria registra `despesa_estornada`. UI: botão cancelar disponível p/ status fechado e pago (confirm avisa do estorno).
|
||||
- **GTO → Financeiro (✅ — gap descoberto):** `PUT /api/gto-builder/:id/finalizar` agora gera **RECEITA** (convênio a receber, vencimento +30d, origem='gto', FKs paciente/profissional) — idempotente via `gto_builders.financeiro_id`. **A tela `LancarGTO` não chamava os endpoints** (`handleFinalize` era stub); agora persiste builder+itens+finaliza (service `createGtoBuilder/addGtoItem/finalizarGto`). Assim a produção por convênio entra no financeiro e na comissão (profissional=dentistaid).
|
||||
- **Convênios padronizados (✅):** GTO passou a usar **convênios reais** da tabela `planos` (menu PLANOS/CONVÊNIOS) via dropdown no `LancarGTO` (carrega `getPlanos`); `credenciadaid` agora guarda o **id do plano** (obrigatório). O lançamento GTO grava `financeiro.convenio_id` e a descrição traz o nome do convênio (`GTO <id> — <nome>`).
|
||||
- **Relatório por convênio (✅):** `GET /api/financeiro/relatorio` agora retorna `porConvenio` (RECEITA agrupada por `convenio_id`, join `planos`, com total/recebido/a_receber/qtd; sem convênio = "Particular"); bloco "Receitas por convênio" no `ReportsView`. Respeita os filtros de período/paciente/profissional.
|
||||
- **Comprovante do repasse (✅):** `repasses.comprovante_{filename,mimetype,nome}` + `POST /api/repasses/:id/comprovante` (multer memória, imagem/PDF, MEDIA_ROOT/repasses/<id>/) e `GET /api/repasses/comprovante/:id/raw` (URL assinada `signMedia`/`verifyMedia`); listagem retorna `comprovante_url`. UI no Histórico (`ComissoesView`): anexar (clip) / ver (PDF) nos repasses pagos.
|
||||
- **Competência por conclusão (✅):** `financeiro.data_competencia` (DATE) gravada na origem — procedimento realizado usa `data_conclusao` (campo "Concluído em" no prontuário; default hoje), GTO usa a data de finalização. `computeComissoes` base **produção** passou a usar `COALESCE(data_competencia, datavencimento)` (fallback p/ vencimento em lançamentos antigos). Recebido segue por `pago_em`.
|
||||
- **Gaps remanescentes:** glosa por item de GTO; recurso/protocolo de glosa com prazos.
|
||||
|
||||
---
|
||||
|
||||
## GLOSAS DE CONVÊNIO — ✅ IMPLEMENTADO (DEV)
|
||||
**Por quê:** convênio nega (glosa) parte do recebível; precisa marcar/gerenciar e recorrer.
|
||||
- **Modelo:** `financeiro.valor_glosa` (acumulado) + tabela `glosas (financeiro_id, valor, motivo, status[glosado|recuperado], autor)`. Líquido a receber = `valor − valor_glosa`.
|
||||
- **Endpoints:** `GET /api/glosas[?status=]`, `GET /api/glosas/recebiveis` (RECEITA de convênio/GTO não totalmente glosada), `POST /api/glosas` (valida não exceder o restante; incrementa `valor_glosa`), `POST /api/glosas/:id/recuperar` (recurso ganho → devolve valor), `DELETE /api/glosas/:id` (correção). Todos tenantGuard + `registrarAudit`.
|
||||
- **Frontend:** `GlosasView` (menu GLOSAS, visível p/ donos e funcionário) — aba "Recebíveis/Marcar glosa" (busca + modal valor+motivo) e aba "Glosas" (filtro glosado/recuperado, recuperar/excluir).
|
||||
- **Glosa no relatório (✅):** `GET /api/financeiro/relatorio` → `resumo.glosa` + `resumo.a_receber_liquido` (= a_receber+atrasado − glosa) e coluna `glosa` em `porConvenio`; UI mostra KPI Glosa (com líquido) e coluna no relatório por convênio.
|
||||
- **Glosa por item + recurso (✅):** `glosas.{gto_item_id,item_descricao,protocolo,prazo_recurso}`; status agora `glosado|recurso|recuperado`. `GET /api/glosas/itens/:financeiroId` lista os `gto_items` da guia (via `gto_builders.financeiro_id`) com flag `ja_glosado`; POST aceita item/protocolo/prazo e bloqueia item já glosado (409). `POST /api/glosas/:id/recorrer` (protocolo+prazo, mantém valor_glosa); recuperar/excluir tratam status recurso. UI: seletor de item no `GlosaModal` + protocolo/prazo, filtro "Em recurso", ação Recorrer e exibição de item/protocolo/prazo.
|
||||
|
||||
---
|
||||
|
||||
## Varredura final do módulo financeiro (DEV)
|
||||
- **Isolamento multi-tenant:** todos os endpoints novos (financeiro/relatorio, comissoes/*, repasses/*, glosas/*, contratos/.../gerar-cobranca) usam `tenantGuard`; teste cross-tenant retornou **403** em todos (exceto os `*/raw` de mídia, protegidos por URL assinada). Mutações por `:id` validam `clinica_id` antes de alterar.
|
||||
- **Índices adicionados:** `idx_financeiro_contrato_comp (contrato_id,competencia)`, `idx_financeiro_convenio`, `idx_glosas_financeiro`, `idx_glosas_item`, `idx_gto_builders_financeiro` (além dos já existentes `idx_financeiro_clinica/_prof`, `idx_glosas_clinica`, `idx_repasses_clinica`, `uq_comissao_regra2`).
|
||||
- **Limitações conhecidas (futuro):** (1) glosa não reduz automaticamente a base de comissão "recebido" se um recebível glosado for marcado Pago com valor cheio — ao dar baixa em recebível de convênio com glosa, reduzir o valor pago; (2) orçamento `financeiro_gerado=true` evita duplicar parcelas, mas não regenera se o valor mudar após assinado (falta ação "regenerar").
|
||||
|
||||
## Contrato para dentista que atende convênios (✅)
|
||||
Novo modelo global `cmod_odo_14` — **"Atendimento a Convênios / Repasse (Dentista Credenciado)"** (tipo `dentista-clinica`), com cláusulas de: objeto (convênios atendidos), guias/GTO e faturamento, repasse (% sobre produção, base recebido/produção, prazo), custo de laboratório, glosas (responsável + dedução do repasse + registro de protocolo/prazo), obrigações, ausência de vínculo, vigência/rescisão, LGPD e foro. Novas variáveis em `VARIAVEIS_CONTRATO`: `{{CONVENIOS}}`, `{{PERCENTUAL_REPASSE}}`, `{{BASE_REPASSE}}`, `{{PRAZO_REPASSE}}`, `{{RESPONSAVEL_GLOSA}}`, `{{CUSTO_LABORATORIO}}` — aparecem automaticamente no Gerador de contrato (data-driven). Liga-se ao financeiro: repasse (base recebido/produção), custo de lab e glosas implementados no módulo.
|
||||
Reference in New Issue
Block a user