Files
agenciapsilmno/Obsidian/Brain/wiki/agenda-compromisso-fluxo.md
T
Leonardo e95ed9b585 agenda: Fase 5 (status change/edit cobrada) + indicadores visuais + UX convenio
DB
- drop agenda_excecoes (substituida por financial_exceptions + lock-edit
  baseado em financial_records)
- financial_records.payment_link (Asaas + link compartilhavel)
- financial_exceptions.consume_on_miss (rotular nao-show consome ou nao)
- billing_contracts.charging_style (upfront/saldo/per_session)

Payment refactor
- paymentSettlement -> paymentMethod (string) + markPaidNow (bool).
  Handler aplica payment_method sempre; status='paid'+paid_at apenas
  quando markPaidNow=true && method != 'link'. Asaas (link) sempre
  liquida via webhook, nunca nasce paid.
- create_financial_record_for_session com pos-RPC patch pra payment_method
  e (opcional) status='paid' quando user marca "ja recebi".

Indicadores visuais (3 canais distintos por estado)
- Paid: barra esquerda emerald-500 4px na agenda (MelissaAgenda),
  pi-check-circle no popover/Resumo.
- Pending: badge \$ amber canto direito (mantido); linha amber no popover/
  Resumo "A receber R\$ X (cobranca pendente)".
- Neutro: sem badge nem barra (compromisso pessoal, bloqueio, ou
  ocorrencia virtual de pacote upfront/saldo).
- Bulk-load de paymentState em _reloadRange etapa 4 (1 query unica em
  financial_records mapeada por agenda_evento_id).
- AgendaEventDialog Resumo lateral ganha linha entre pi-clock e
  pi-map-marker via novo sessionPaymentRecord (sem guard de
  occurrenceMode, contrario ao occFinancialRecord que continua so pra
  Rail/Clinica). 5 estados: paid+paid_at, overdue+venceu, pending+vence,
  sem cobranca c/ valor, sem cobranca s/ valor.

UX de convenio
- InsurancePlanServiceQuickCreateDialog novo: cadastra procedimento
  POR CIMA do AgendaEventDialog sem sair da agenda. Auto-seleciona
  novo procedimento so quando nada estava selecionado antes.
- Caixa cinza "Cadastrar procedimento" sempre visivel quando convenio
  selecionado, com copy variavel (0 procedimentos: chamada urgente;
  1+: "se quiser adicionar mais").
- "+ Novo convenio" toolbar em ConfiguracoesConveniosPage (botao
  estava faltando, empty state mandava clicar em botao inexistente).
- Hint contextual abaixo do card Sessao/Honorarios: convenio = "N da
  guia eh opcional", gratuito = "sem cobranca", particular = sem hint.
  Label "N da Guia" tambem ganhou "(opcional)" no service-picker dialog.

Bug fixes
- pickDbFields whitelist faltando 'modalidade' (useMelissaAgenda.js:74)
  — sessoes avulsas eram salvas como presencial independente da
  escolha visual. Adicionado.
- goToConveniosConfig removida — fazia router.push("/therapist/
  configuracoes/convenios") mas /configuracoes/* eh rota raiz, nao
  filha. Substituida pelo quick-create inline (#1).
- bloqueioCobrindo + dialogBlockOverlap passados via deps em
  _buildHandlers (refs do useMelissaAgenda nao sao acessiveis no
  escopo de _buildHandlers).

Fase 5 (status change + edit cobrada)
- AgendaStatusChangeConfirmDialog: confirm dialog quando user muda
  status pra realizada/faltou/cancelado, com opcoes de markPaid ou
  gerar cobranca conforme o caso.
- useAgendaBloqueios novo composable: extrai logica de bloqueios
  cinza (background events) do MelissaAgenda.

Doc viva
- src/docs/agenda-compromisso-financeiro-cenarios.html: 13 cenarios
  de teste manual. C1-C4 ja validados. Cada teste validado vira parte
  da doc final pra area de ajuda (pos-Fase 9).

Wiki/handoff
- agenda-compromisso-fluxo e agenda-billing-pesquisa-mercado (decisoes
  arquiteturais sobre billing).
- HANDOFF.md atualizado.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 08:31:18 -03:00

217 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Plano de auditoria fase-a-fase — fluxo de compromisso da agenda
date: 2026-05-13
status: em-andamento
related: [[agenda-billing-pesquisa-mercado]], [[recorrencia-agenda]]
---
## Contexto
Auditoria do ciclo completo de compromisso da agenda, fase-a-fase, validando cada etapa contra a [[agenda-billing-pesquisa-mercado|pesquisa de mercado]] (Cliniko / SimplePractice / TherapyNotes). Cada fase tem 3 entregas: **auditar o que existe**, **decidir o gap**, **codar**.
## Decisões já tomadas (5 das 8 perguntas)
| # | Decisão |
|---|---|
| 1 | Disparo de cobrança: **híbrido configurável** (manual / status-driven / note-signed) |
| 4 | No-show: **semi-automático via dialog de confirmação** ao mudar status |
| 5 | Edit de cobrada: **bloqueia** (já implementado) |
| 7 | Refund: **credit note nova** (alinhado NFS-e) |
| 8 | Pagamento: **entidade separada** de financial_records |
Pendentes: #2 (lead/Inquiry), #3 (pacote upfront), #6 (janela de cancelamento — provavelmente já resolvido por `min_hours_notice` em `financial_exceptions`).
---
## Plano de 8 fases
Ordem por dependência ("o que destrava o quê") e por estado atual.
### ✅ Fase 1 — Compromisso SEM paciente (bloqueio/feriado/exceção) — **CONCLUÍDA 2026-05-13**
**Auditoria fez:**
-`agenda_excecoes` é tabela órfã (0 referências em src/) — apesar de schema, policies, trigger e enums existentes
-`agenda_bloqueios` é a entidade canônica usada pelos 3 layouts
-`BloqueioDialog` (4 modos: horário/período/dia/feriados) é compartilhado por Melissa Agenda (via `MelissaLayout.vue:2186`), Rail e Clínica
-`MelissaBloqueios.vue` tem form inline próprio pra **admin/edit** (caso de uso legítimo distinto do dialog de 4 modos)
- ✅ Bloqueios não eram renderizados no FullCalendar — apenas impediam criação. UX inconsistente vs pausas/feriados que aparecem como background events
- ⚠️ Tipos customizáveis de bloqueio: descartado no MVP (sem cliente real)
- ⚠️ Robustez de `marcarSessoesParaRemarcar`: adiado pra Fase 5 (status change)
**Aplicado:**
1. Migration `20260513000001_drop_agenda_excecoes.sql` — dropa tabela + 2 enums + trigger; policies caem com CASCADE
2. `agendaMappers.js`: nova função `buildBloqueioBackgroundEvents(bloqueios, rangeStart, rangeEnd)` — renderiza bloqueios como background events cinza (`#6b728033`), suporta dia-inteiro, com hora, e recorrente semanal
3. Novo composable `useAgendaBloqueios.js` — load por owner único OU array (multi-owner pra Clínica), `buildEventsForRange` reutilizável
4. Wire em `useMelissaAgenda` + `MelissaAgenda.vue` — bloqueios concatenados ao `fcEvents`
5. Wire em `AgendaTerapeutaPage` — bloqueios concatenados ao `calendarEvents`
6. Wire em `AgendaClinicaPage` — bloqueios consolidados de todos os ownerIds
7. Refs stale removidas de `database-novo/docs/schema_map.md` e `database-novo/db.config.json`
**Verificação:**
- ESLint nos arquivos modificados: 0 errors novos (11 pré-existentes em código não-tocado)
- Vitest `agendaMappers.spec.js`: 40/40 tests passed
- ⚠️ **Falta rodar a migration no banco local** (pendente de execução manual; arquivo SQL pronto)
- ⚠️ **Falta validar visualmente** nos 3 layouts (Melissa/Rail/Clínica) — verificar que bloqueios aparecem em cinza após criar pelo BloqueioDialog
---
### 🟢 Fase 2 — Compromisso COM paciente
**Estado:** dialog refatorado em 11/05 (cards 40px, picker DataTable, 50/50 layout, 3 estados Sessão/Honorários, conceito Pacote, resumo flutuante). Working tree.
**Auditar:**
- Fluxo de cadastro mínimo de paciente in-line (já existe via `PatientCadastroDialog` quick mode?)
- Decidir #2 (Inquiry/lead separado ou só quick-create)
- Modalidade presencial/online consistente
**Gap potencial:**
- Quick-create exige só nome ou mais campos? (Cliniko: só nome; TherapyNotes: só last name)
- Decisão #2 (Inquiry/lead) — adiar pra v2 provável
**Codar:** ajustes pequenos, principalmente UX. Provavelmente quase nada novo.
---
### 🟢 Fase 3 — Recorrência
**Estado:** modelo "1 real + N-1 virtual" + `occurrenceMode` no 2º dialog estabilizado em 12/05. Ver [[recorrencia-agenda]].
**Auditar:**
- `occurrenceMode` já replicado em Melissa; falta Rail (`AgendaTerapeutaPage` L1630 + L3080) e Clínica (`AgendaClinicaPage` L1119 + L2398)
- Decisão #3 (pacote upfront via account credit) — adiar provável
**Codar:** replicar `occurrenceMode` em Rail/Clínica. Talvez add de pacote upfront (Cliniko model) numa fase futura.
---
### 🟠 Fase 4 — Cobrança: modo de disparo configurável (DECISÃO #1)
**Estado:** Fase 1 atual ("Gerar cobrança ao salvar") existe como checkbox em criação avulsa+particular. Não tem setting de modo.
**Auditar:**
- Onde vive a config? Card novo em `/configuracoes/excecoes-financeiras` ou página irmã `/configuracoes/cobranca-defaults`?
- Granularidade: por tenant (clínica), por owner (terapeuta), ou ambos com herança?
**Gap:**
- Tabela/coluna nova pra `charge_trigger_mode` enum (`manual` / `status_driven` / `note_signed`)
- UI de config
- Job overnight pra modo `status_driven` (Supabase edge function + cron)
- Trigger no signature de nota pra `note_signed` (depende de modulo de notas; nao temos)
- Checkbox atual da agenda passa a fazer sentido **só em modo manual** (ou vira override universal?)
**Codar:**
1. Migration: setting de modo (tenant_billing_settings ou colunas em agenda_configuracoes)
2. UI de config
3. Job pra modo status_driven (avaliar se entra na v1 ou v2)
4. Refator do checkbox atual pra respeitar o modo
---
### 🟠 Fase 5 — Status change → cobrança com confirm dialog (DECISÃO #4)
**Estado:** lógica automática roda em `useAgendaFinanceiro.handleStatusChange`. Consulta regra em `financial_exceptions`, cria/ajusta/cancela `financial_record` SEM perguntar.
**Auditar:**
- Quais status disparam: hoje só `faltou` e `cancelado` (mapping `STATUS_TO_EXCEPTION`)
- `professional_cancellation` na tabela mas não no mapping
- Onde `handleStatusChange` é chamado (quais entradas de status change disparam)
**Gap:**
- Confirm dialog ao mudar status pra `faltou` / `cancelado`: *"Aplicar cobrança de R$X conforme regra? [Sim / Não / Editar valor]"*
- Adicionar `professional_cancellation` ao mapping (status atual da agenda inclui? checar)
- Decidir: dialog aparece **sempre** ou só quando `charge_mode !== 'none'`
**Codar:**
1. Dialog componente novo (`AgendaStatusChargeConfirmDialog.vue`)
2. Interceptar `handleStatusChange` antes da aplicação automática
3. Adicionar `professional_cancellation` no mapping
4. Toast diferenciado pra "aplicado/recusado/editado"
---
### 🟢 Fase 6 — Edit de cobrada (DECISÃO #5 — JÁ IMPLEMENTADO)
**Estado:** `propagateToSerie` filtra por `financial_records` em status imutável. UI lock em `AgendaEventDialog` via `occFinancialRecord`. Working tree.
**Auditar:** validar contra cenários reais (testar série com 4 sessões, 2 cobradas, 2 abertas; editar template; verificar que cobranças não mudam).
**Codar:** zero (talvez add de aviso UX se faltar clareza).
---
### 🔴 Fase 7 — Pagamento como entidade separada (DECISÃO #8)
**Estado:** hoje `financial_records.paid_at` marca pagamento (acoplado). Não tem entidade `payments` independente.
**Auditar:**
- Como financial_records.paid_at é usado hoje (queries de receita, dashboards, conciliação)
- Webhook PSP existente? (provável que PIX e cartão sejam manuais hoje)
**Gap:**
- Migration: tabela `payments` (id, amount, method, paid_at, source, allocated_to_record_id NULL-able)
- Alocação manual de pagamento "solto" a um financial_record
- Pagamento parcial (1 payment cobre N records ou 1 record recebe N payments?)
- Repo + composable + UI
**Codar:** fase pesada — provavelmente sub-dividir.
---
### 🔴 Fase 8 — Reembolso / credit note (DECISÃO #7)
**Estado:** hoje só tem `financial_records.status='cancelled'`. Não preserva original como doc fiscal.
**Auditar:** processo fiscal atual (já emite NFS-e? quando? como cancela?)
**Gap:**
- Migration: tabela `credit_notes` (id, original_record_id, amount, reason, issued_at)
- Constraint: credit note tem valor ≤ |original|
- UI no Financeiro pra "Reembolsar"
- Integração com NFS-e (pode ser separada)
**Codar:** fase pesada — provavelmente sub-dividir.
---
### 🟣 Fase 9 — Plano Inicial (entrevista + N sessões regulares)
**Estado:** apenas conceito; nada codado.
**Pedido do user (2026-05-14):** clínica cobra **1 entrevista inicial** (valor X) + **4 sessões regulares** (valor Y cada). É o "plano de entrada" pra novos pacientes. User faz isso manualmente hoje na clínica dele.
**Conceito:**
- Config nas settings da agenda do tenant:
- Toggle "Habilitar plano inicial"
- Valor entrevista (R$)
- Qtd de sessões regulares (default 4)
- Valor por sessão regular (R$)
- (Opcional) Texto/descrição que aparece no fluxo
- Quando user cria 1ª sessão de **paciente novo** (sem histórico):
- Sistema oferece: "Aplicar plano inicial? Entrevista R$ X + 4× R$ Y = total R$ Z"
- Ao aceitar, materializa 5 sessões com `price` diferenciado: 1ª = X, demais = Y
- Pode ser tratado como 1 série recorrente "especial" com 1ª ocorrência destacada
- OU como 2 entidades distintas (1 avulsa entrevista + 1 série de 4)
**Decisões pendentes:**
- Estrutura: série única com 1ª diferenciada OU avulsa + série separada?
- Onde fica a config: `agenda_configuracoes` (jsonb adicional?) ou tabela nova `intake_plans`?
- "Paciente novo" = sem sessões anteriores? Ou marcador manual no cadastro?
- Plano único do tenant ou múltiplos planos (avaliação clínica, avaliação neuropsi, etc)?
**Cabe na Fase 4 (cobrança)?** Não — Fase 4 é só modo de disparo; aqui é estrutura de pacote pré-configurado. Fica como Fase 9 separada.
---
## Ordem sugerida de execução
| Ordem | Fase | Razão |
|---|---|---|
| 1ª | **Fase 1** | Curta, validação, define se tem cleanup de tabelas necessário |
| 2ª | **Fase 5** | Destrava UX urgente (confirm dialog evita cobrar errado) |
| 3ª | **Fase 4** | Híbrido configurável — destrava racional do checkbox atual |
| 4ª | **Fase 2** | Quase 100% pronta, validar e finalizar |
| 5ª | **Fase 3** | Replicar `occurrenceMode` em Rail/Clínica |
| 6ª | **Fase 6** | Já feito; só testar |
| 7ª | **Fase 7** | Refator estrutural pesado — entra depois das fases UX |
| 8ª | **Fase 8** | Depende fiscal NFS-e — pode ir pra v2 |
| 9ª | **Fase 9** | Plano Inicial (entrevista + 4 sessões) — pedido do user, conceito pronto, codar pós-7 |
## Como cada fase termina
1. Página da fase na wiki é atualizada com o resultado
2. Commit dedicado com prefixo `agenda(fase-N): ...`
3. Update no [[index]] da wiki
4. Entrada no `log.md`