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

11 KiB
Raw Blame History


Contexto

Auditoria do ciclo completo de compromisso da agenda, fase-a-fase, validando cada etapa contra a agenda-billing-pesquisa-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
Fase 1 Curta, validação, define se tem cleanup de tabelas necessário
Fase 5 Destrava UX urgente (confirm dialog evita cobrar errado)
Fase 4 Híbrido configurável — destrava racional do checkbox atual
Fase 2 Quase 100% pronta, validar e finalizar
Fase 3 Replicar occurrenceMode em Rail/Clínica
Fase 6 Já feito; só testar
Fase 7 Refator estrutural pesado — entra depois das fases UX
Fase 8 Depende fiscal NFS-e — pode ir pra v2
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