Cenários

Legenda

pendente pago cancelado Asaas pacote

## ✦ addendum Implementado em 20/05 (C10) — divergências e melhorias vs mockup

O mockup original deste doc foi escrito antes da implementação real. Durante a bateria de testes C10 (status change avulsa), surgiram bugs, melhorias UX e travas que foram implementadas mas não estão refletidas nas seções abaixo. Este addendum captura essas mudanças. Cenários C1-C9 continuam fiéis ao mockup; C10 deve ser lido com este addendum em mente.

#### O que ficou diferente / melhor que o mockup original

1. Multa cancela original + cria novo
Antes do fix: `_applyStatusDecisions` INSERIA o record da multa MAS deixava o original pending → cobrança dupla (R$ 200 + R$ 30 = R$ 230). Fix em `useMelissaAgenda.js:1450-1505`: aplicar multa agora cancela o `ctx.pendingRecord` com nota de auditoria em `notes` ("[YYYY-MM-DD] Cancelada — substituída por multa de no-show"). Description do novo record carrega data da sessão pra paciente identificar na fatura: `"Multa por falta · sessão dd/mm/aa"`. ✅ Match com o mock C10/b.
2. Hint contextual explicando regra `min_hours_notice` novo
No bloco "Aplicar multa?" do `AgendaStatusChangeConfirmDialog`, embaixo do checkbox aparece texto explicando por que veio (des)marcado por padrão:

- > janela: "Cancelou 18.5h antes da sessão. Regra: multa apenas quando cancelamento ocorre com menos de 2h de antecedência → sem multa por padrão."
- < janela: "Cancelou 45min antes da sessão (menos que os 2h da regra) → multa aplicada por padrão."
- Após início: "Cancelou 0.5h após o início da sessão (menos que os 2h da regra) → multa aplicada por padrão."

Terapeuta vê a razão da pré-seleção e pode inverter conscientemente.
3. Botão "Agendada" no popover novo
O grupo "Marcar sessão como:" agora tem 5 botões (antes 4): Agendada (pi-calendar, variante `--info` cyan) | Realizada | Falta | Reagendar | Cancelar. Permite reset de status (realizado/faltou/cancelado → agendado) direto do popover sem precisar abrir o AgendaEventDialog completo. Único caminho de saída do estado encerrado (ver item 5).
4. Label do popover muda em sessão encerrada UX
Antes mostrava "A cobrar R$ 150" + botão "Gerar fatura" mesmo em sessão cancelada — sugeria que dava pra cobrar uma sessão que não aconteceu. Agora:

- `status='cancelado'` + sem record ativo → "Sessão cancelada · sem cobrança ativa"
- `status='faltou'` + sem record ativo → "Sessão não realizada · sem cobrança ativa"
- Multa pending continua mostrando "A receber R$ X (pendente)" normalmente

Bug paralelo fixado: `paymentLabel` agora usa `paymentAmount` também pra `'pending'` (antes só pra `'paid'`; multa de R$ 30 mostrava R$ 150 do `ev.price` original).
5. Lock em sessão encerrada (cancelado/faltou) trava
Sessão com status `cancelado` ou `faltou` bloqueia ações que abrem porta pra dados inconsistentes:

- Botão "Editar sessão" some do popover
- Botão "Gerar cobrança" some do `AgendaEventoFinanceiroPanel` (dentro do AgendaEventDialog) — antes dava pra emitir fatura nova mesmo em sessão cancelada
- Botões Realizada / Falta / Reagendar / Cancelar ficam `disabled` com tooltip "Sessão encerrada — use Agendada pra reativar antes"
- SÓ Agendada continua funcional (caminho explícito de recuperação caso tenha sido marcado por engano)
- Badge $ amber some do card no FullCalendar (sessão encerrada + record cancelled → no badge)

6. `_reloadRange()` após status change fix
`onUpdateSeriesEvent` não chamava `_reloadRange()` após `_applyStatusDecisions` — badge $ e label "A receber" ficavam stale até trocar de view ou F5. Fix: reload no fim do flow. Bug paralelo: `_reloadRange` não estava destruturado em `_buildHandlers(deps)` (era passado em deps mas não desempacotado) → toast "ReferenceError: _reloadRange is not defined" ao tentar reload. Ambos corrigidos.
7. Bug dormente em `useAgendaFinanceiro.js` fix
`calcChargeAmount` comparava `charge_mode === 'fixed'`, mas o schema usa `'fixed_fee'`. Off-by-key silencioso que caía no fallback. Path não exercitado na Melissa (que usa `_applyStatusDecisions`, não `handleStatusChange`), mas iria quebrar se algum dia fosse. Fix: `'fixed_fee'`.

#### Pendências mapeadas durante C10 — implementar pós-C13

⚠ Reverse transitions com multa órfã
Caso: terapeuta marca "Faltou" com multa R$ 30 → percebe que foi engano → clica "Agendada" pra reativar → status volta pra agendado MAS multa R$ 30 fica pending órfã. Hoje precisa cancelar manualmente em `/financeiro`. Solução planejada: confirm dialog ao reverter de cancelado/faltou pra agendado com record/multa pending → oferecer auto-cancelar a multa também (radio sim/não). Memória salva em `project_agenda_reverse_transitions.md`.
⚠ Popover Melissa = snapshot do clique
`eventoSelecionado.value` é setado uma vez em `abrirEvento(ev)` — quando `_paymentStateMap` updata depois (ex: bulk-load assíncrono pós F5 leva 1-3s), o popover NÃO re-renderiza com state novo. Caso típico: F5 + clique rapidíssimo no card → popover mostra "A cobrar R$ 150" (state='none' default) porque snapshot pegou map vazio. Fix planejado: guardar `ev.id` em vez de `ev`, popover deriva via computed `eventos.value.find(...)`. Memória em `project_melissa_popover_snapshot.md`.
⚠ A2 do João Almeida com markPaid não persistiu
Durante teste C10/A2, usuário marcou Realizada + "Sim, registrar pagamento" + Maquininha. Toast verde, card mudou visual, mas DB mostra `financial_records.status='pending'` em vez de `'paid'`. A investigar pós-C13 — pode ser que o reset/realizada de novo tenha sobrescrito, ou o markPaid não tenha entrado no caminho de UPDATE.

## Indicadores visuais de pagamento

Como o sistema mostra "essa sessão ainda não foi paga" sem que você precise abrir o financeiro pra checar.

#### Na agenda — 3 estados

Pendente · badge $ amber (canto direito)
$
Ana Souza (14h-14:50h)
agendado presencial
Pago · barra verde (borda esquerda)
Joyce (15h-15:50h)
agendado presencial
Sem cobrança · neutro
Maria (16h-16:50h)
agendado presencial

- Pendente: sessão com paciente, cobrança gerada mas não paga (ou nenhuma cobrança ainda).
- Pago: record marcado como `paid` em `/financeiro`. Pra ver de relance quais sessões já estão quitadas.
- Neutro: sem cobrança (compromisso pessoal, bloqueio) ou ocorrência virtual de pacote upfront/saldo — controlados pelo contrato.
- Os 3 canais não competem: esquerda = pago, direita = pendente, nada = neutro.

#### No popover da sessão — linha "A receber"

14:00 – 14:50 · 50min
A receber R$ 200,00 (cobrança pendente)
Presencial
Agendado

- Linha em destaque amber logo abaixo do horário, com ícone $.
- Texto adapta-se ao caso:
  - "A cobrar R$ X" — quando ainda não há cobrança gerada (sessão "Não cobrar").
  - "A receber R$ X (cobrança pendente)" — quando o record existe mas não foi pago.
  - "Cobrança ainda não gerada" — quando a sessão não tem valor definido.
- Some quando a sessão fica paga.

## 1 Bloqueio · sem paciente, sem cobrança

Almoço, feriado, férias ou compromisso pessoal. Aparece cinza no calendário e não gera lançamento no financeiro.

Como testar:

1. Abra `BloqueioDialog` pelo menu da agenda, escolha modo Período e crie um bloqueio de 15/05 a 17/05 com motivo "Férias curtas".
2. Confira que o intervalo aparece em cinza nas 3 agendas (Melissa / Rail / Clínica) e que nada foi gerado em `/financeiro`.
3. Clique num horário dentro do bloqueio (ex: 16/05 às 10h). O `AgendaEventDialog` abre normalmente, com um aviso amarelo no topo avisando que aquele slot está bloqueado. Você pode prosseguir e criar o compromisso assim mesmo — o aviso é só pra evitar acidente.

1a · Criar o bloqueio

#### BloqueioDialog · "Período"

Modo
Período
15/05/2026 → 17/05/2026
Motivo
Férias curtas
Renderizado como faixa cinza nas 3 agendas (Melissa / Rail / Clínica). Não afeta o financeiro.

#### Financeiro · Lançamentos

Nada gerado. Bloqueio não é cobrável.
1b · Agendar sobre um bloqueio (aviso, não impede)

#### AgendaEventDialog · escolha do tipo

Este horário está dentro do bloqueio "Férias curtas". O compromisso pode ser criado mesmo assim.
Selecione o tipo de compromisso para começar.
Sessão
Reunião
Outro
O aviso aparece dentro do dialog (não como toast solto) e some sozinho ao reabrir o dialog em outro horário.

#### O que acontece

Você decide. Diferente do agendador público (que veta agendamento em bloqueio), aqui o sistema só te avisa. É comum querer encaixar uma sessão sobre um almoço, ou agendar uma reunião em pleno feriado.

- Se prosseguir, o compromisso é criado normalmente e aparece por cima da faixa cinza do bloqueio.
- Se quiser cancelar, basta fechar o dialog — nenhum dado é gravado.
- O aviso usa o título do bloqueio (ex: "Férias curtas") pra você lembrar o motivo do bloqueio antes de decidir.

No agendador público (link de marcação que o paciente acessa) o comportamento é diferente: se o dia inteiro estiver bloqueado, sessões ficam vetadas e aparece a mensagem "Este dia está bloqueado. Compromissos do tipo Sessão não podem ser agendados." — só você (terapeuta) pode passar por cima.

## 2 Avulsa · sem cobrança chargeMode = none

Sessão única, terapeuta decide depois se cobra (modo manual / fechamento mensal).

Como testar: Criar sessão pra Ana Souza Ferreira em 15/05 14:00, modalidade presencial, tipo avulsa, deixar "Não cobrar agora" selecionado no card Cobrança ao salvar. Salvar. Verificar evento na agenda (status pendente) e zero record em `/financeiro`.
Indicadores visuais: Como a sessão ainda não foi cobrada, o evento na agenda mostra um círculo amber com cifrão ($) no canto superior direito. Ao clicar no evento, o popover mostra uma linha amber com o ícone $: "A cobrar R$ X,XX" (ou "Cobrança ainda não gerada" se a sessão não tiver valor). Assim você lembra de gerar a cobrança depois no `/financeiro`.

#### AgendaEventDialog · Ana Souza Ferreira

Início
15/05 14:00
Duração
50 min
Modalidade
Presencial
Tipo
Avulsa
Cobrança ao salvar

Você pode gerar a cobrança depois pelo `/financeiro` ou pelo painel da sessão.

#### Financeiro · Ana

Nenhum lançamento gerado. Sessão fica como pendente na agenda só.

## 3 Avulsa · cobrar ao salvar particular

Gera lançamento pendente no ato. Default da agenda em modo manual.

Como testar: Criar sessão pra Henrique, serviço Psicoterapia R$ 200, modalidade online. No card "Cobrança ao salvar", escolher "Gerar cobrança". Forma de pagamento: selecionar PIX. Status do pagamento: deixar em "Cobrança pendente" (default). Salvar. Verificar 1 record em `/financeiro` com status pendente, `payment_method = pix` e `paid_at = null`.
Indicadores visuais: A cobrança foi gerada mas o paciente ainda não pagou. O evento na agenda mostra o círculo amber com $ no canto. No popover, linha amber: "A receber R$ 200,00 (cobrança pendente)". O badge some automaticamente assim que o record for marcado como pago no `/financeiro`.

#### AgendaEventDialog · Henrique

Sessão
Psicoterapia · 50 min · R$ 200,00
Modalidade
Online
Cobrança ao salvar
Forma de pagamento
Status do pagamento

Só aparece quando a forma de pagamento não é o link Asaas (que é sempre liquidado pelo gateway).

#### Financeiro · Henrique

Clique em Salvar sessão

## 4 Avulsa · "já recebi" no momento da criação

Quando o paciente paga PIX/dinheiro na hora — record já nasce pago.

Como testar: Criar sessão pra Joyce, valor R$ 180. No card "Cobrança ao salvar", escolher "Gerar cobrança". Forma de pagamento: PIX. Status do pagamento: "Já recebi (dar baixa)". Salvar. Verificar 1 record em `/financeiro` com status pago, `payment_method = pix` e `paid_at = hoje`.
Indicadores visuais: O pagamento já foi confirmado, então o evento ganha uma barra verde fina na borda esquerda (substitui a cor padrão do commitment naquele lado). Sem badge $ no canto e o popover não mostra linha de cobrança. É o estado-alvo: dá pra olhar a agenda e identificar de relance quais sessões já foram pagas.

#### Joyce · Sessão R$ 180

Cobrança ao salvar
Forma de pagamento
Status do pagamento

Quando escolhe "Já recebi", o record nasce com `paid_at = agora` + `status = paid`.

#### Financeiro · Joyce

Clique em Salvar sessão

## 5 Avulsa · convênio

Valor pago pelo plano de saúde, não pelo paciente. Lançamento vinculado ao plano.

Como testar: Criar sessão pra Sándor com convênio Unimed Nacional (criar via `InsurancePlanQuickCreateDialog` se não existir), valor R$ 95. Salvar. Verificar record com `insurance_plan_id` preenchido e pill "convênio" visível.
Indicadores visuais: Como o convênio ainda não fechou o mês, o record fica pendente — o evento na agenda mostra badge $ e o popover mostra "A receber R$ 95,00 (cobrança pendente)". Quando você marcar o record do convênio como pago após o fechamento mensal, os indicadores somem.

#### Sándor · sessão por convênio

Convênio
Unimed Nacional
Valor (clínica recebe)
R$ 95,00

Cobrança vai pra fechamento mensal do convênio — não cobra o paciente.

#### Financeiro · Sándor

Sessão 15/05 14:00
via convênio Unimed
convênio
R$ 95,00
Vai pra fatura mensal do convênio (não cobra paciente individualmente).

## 6 Recorrente · sem pacote chargeMode = none

Série semanal, mas cobrança decidida sessão a sessão (status driven ou manual depois).

Como testar: Criar série recorrente pra Anna Freud, frequência semanal, 4 ocorrências, modo de cobrança "Não cobrar". Salvar. Verificar no banco: 1 row em `agenda_eventos` + 1 row em `recurrence_rules`; outras 3 ocorrências expandidas em runtime. Zero em `/financeiro`.
Indicadores visuais: Apenas a 1ª ocorrência (materializada de verdade) mostra o badge $ e a linha "A cobrar". As outras 3 (virtuais, expandidas em runtime) ficam limpas — vão ganhar o badge automaticamente quando virarem ocorrências reais (ao marcar status ou editar). Isso evita poluir o calendário com $ em séries longas.

#### Anna Freud · semanal · 4 sessões

Frequência
Semanal · qua 14:00
Ocorrências
4 (1 real + 3 virtuais)
Modo de cobrança
Modelo "1 real + N-1 virtual"

Só a 1ª ocorrência vira row em `agenda_eventos`. As demais são `rec::ruleId::date` expandidas em runtime.

#### Financeiro · Anna Freud

Zero lançamentos. Cobra sessão por sessão quando marcar como Realizada.

## 7 Recorrente · pacote único UPFRONT

1 cobrança do valor total no salvar. Cada sessão posterior é "consumo" do já pago.

Como testar: Criar série pra Donald Winnicott, 4 sessões semanais R$ 200 = R$ 800 total, modo "Pacote único", estilo "Upfront", forma "Enviar link Asaas". Salvar. Verificar 1 record R$ 800 pending em `/financeiro` + 1 billing_contract com `charging_style=upfront`, `sessions_used=0`.
Indicadores visuais: A 1ª ocorrência (que carrega o record de R$ 800 do pacote) mostra o badge $ enquanto o boleto Asaas não for pago — popover diz "A receber R$ 800,00 (cobrança pendente)". As outras 3 ocorrências (virtuais) ficam limpas, mesmo que o paciente não tenha pago ainda — porque o controle é a nível de pacote, não sessão. Quando o boleto for marcado como pago, o badge some da 1ª sessão.

#### Donald Winnicott · 4× R$ 200 = R$ 800

Ocorrências
4 semanais
Total
R$ 800,00
Como cobrar?
Estilo do pacote
Forma de pagamento

#### Financeiro · Donald

Clique em Salvar série

## 8 Recorrente · pacote único SALDO

Sem record financeiro inicial. Cria `billing_contract` com sessions_total. Records nascem 1 a 1 quando sessão é realizada.

Como testar: Criar série pra Carl Jung, 4 sessões R$ 40 = R$ 160 total, modo "Pacote único", estilo "Saldo". Salvar. Verificar ZERO record em `/financeiro` + 1 billing_contract com `charging_style=saldo`, `sessions_total=4`, `sessions_used=0`.
Indicadores visuais: Como não existe record financeiro inicial e o controle é por saldo do contrato, todas as 4 ocorrências ficam limpas no calendário (sem badge $). À medida que cada sessão for realizada, nasce 1 record por vez — e essa sessão recebe o badge $ até o record ser pago. Use "Antecipar pagamento" no popover quando o paciente quiser pagar uma sessão antes dela acontecer.

#### Carl Jung · 4× R$ 40 = R$ 160

Sessions total
4
Valor por sessão
R$ 40,00
Estilo do pacote

Cobra 1 record por sessão realizada. Pacote dá direito ao preço promocional + controle de saldo.

#### Financeiro · Carl Jung

Clique em Salvar série

## 9 Recorrente · 1 cobrança por sessão sem pacote

Cobra individualmente cada ocorrência. Diferença pro Cenário 6: aqui já materializa records pendentes pra cada ocorrência futura.

Como testar: Criar série pra Michael Balint, 12 sessões semanais R$ 150, modo "1 por sessão". Salvar. Verificar 12 records pending em `/financeiro` (1 por ocorrência) com `agenda_evento_id` apontando pra cada uma.
Indicadores visuais: Como cada ocorrência foi materializada com seu próprio record pendente, todas as 12 sessões mostram o badge $ no calendário e a linha "A receber R$ 150,00 (cobrança pendente)" no popover. À medida que o paciente pagar cada uma, o badge dessa sessão some.

#### Michael Balint · semanal

Frequência
Semanal · 12 sessões
Valor / sessão
R$ 150,00
Modo

#### Financeiro · Michael

Clique em Salvar série

## 10 Status change · avulsa pendente

Sessão criada com chargeMode=session, agora chegou a hora. Dialog confirm aparece em realizado/faltou/cancelado.

Como testar: Usar a sessão pendente do Henrique (cenário 3) ou criar uma nova pra Maria Magali R$ 200 cobrar ao salvar. (a) Mudar status → Realizada: dialog deve aparecer, confirmar, record continua pendente. (b) Reset (volta pra pendente). Mudar → Faltou: dialog aparece com multa editável R$ 100, confirmar, record original cancelado + novo de multa criado. (c) Reset. Mudar → Cancelado: dialog confirma cancelamento sem multa, record fica cancelado.

#### Maria Magali · 15/05 16:00 · pendente

Status atual
pendente
Valor sessão
R$ 200,00
Mudar status para:

Cada um abre o `AgendaStatusChangeConfirmDialog` com variante diferente.

#### Financeiro · Maria

Sessão 15/05 16:00
avulsa · particular
pendente
R$ 200,00

## 11 Status change · ocorrência de pacote saldo

Carl Jung tem pacote 4/4. Marca cada sessão como realizada → cria record + decrementa saldo. Faltou → vai pra config `default_consume_on_miss`.

Como testar: Usando o pacote do Carl Jung (cenário 8): (a) Marcar 1ª sessão como Realizada: dialog confirm → criar record R$ 40 pending + saldo cai pra 3/4. (b) Marcar 2ª como Faltou: dialog com 3 opções (consumir saldo / aplicar multa / nada). Testar a opção consumir saldo → saldo cai pra 2/4 sem cobrança nova. (c) Marcar 3ª como Faltou com aplicar multa R$ 20 → saldo continua 2/4, novo record R$ 20.

#### Carl Jung · pacote saldo

Saldo: 4 / 4
R$ 40/sessão
Sessão de hoje (15/05 18:00) — virtual `rec::...::2026-05-15`
pendente
Marcar como:
O que acontece no banco?

- Materializa a ocorrência virtual: INSERT em `agenda_eventos` com `recurrence_id + recurrence_date`
- UPDATE `billing_contracts.sessions_used += 1`
- INSERT em `financial_records` R$ 40 status=pending (ou paid se já houver record do antecipar)

#### Financeiro · Carl Jung

Pacote 4× R$ 40
billing_contract · saldo 4/4
contrato
R$ 160,00
Ainda sem records de sessão. Cada Realizada cria 1 record + decrementa saldo.

## 12 Antecipar pagamento · pacote saldo novo · 14/05

Paciente pediu pra pagar a próxima sessão antes dela acontecer. Cria record pago, MAS não decrementa saldo (só quando marcar realizada depois).

Como testar: No pacote do Carl Jung, clicar numa ocorrência virtual futura → popover abre → seção Financeiro mostra 2 botões: "Lançamentos" + "Antecipar pagamento". Clicar "Antecipar pagamento", escolher PIX (default), confirmar. (a) Verificar 1 novo record R$ 40 paid em `/financeiro`, MAS saldo do contrato continua intacto. (b) Depois marcar a mesma sessão como Realizada: dialog NÃO deve perguntar "gerar cobrança" (detecta record paid), só atualiza status + decrementa saldo.

#### Popover da ocorrência virtual

Seção Financeiro do popover:

Diferença pra Realizada: não decrementa `sessions_used`. Quando user marcar Realizada depois, handler detecta record já paid → só atualiza status.

#### Financeiro · Carl Jung

Pacote 4× R$ 40
billing_contract · saldo 4/4
contrato
R$ 160,00

## 13 Edit de sessão já cobrada imutável

Invariante "cobrança emitida é imutável pelo dialog da agenda" (Decisão #5, padrão SimplePractice).

Como testar: Pegar a sessão paga da Joyce (cenário 4) e abrir o `AgendaEventDialog`. Verificar: (a) aviso amarelo no topo "esta sessão tem cobrança paga"; (b) campos de valor / modalidade / tipo travados (disabled); (c) apenas horário e observações editáveis; (d) tentar mudar valor não persiste.

#### AgendaEventDialog · Sessão paga

Esta sessão tem cobrança paga. Campos de valor / modalidade / tipo estão travados. Pra ajustar valor, use credit note no /financeiro.
Valor
R$ 200,00
Modalidade
Presencial

Apenas horário / observações podem ser editados.

#### Financeiro · paciente

Sessão 10/05 14:00
avulsa · particular · paid_at 10/05
pago
R$ 200,00
Record imutável. Reembolso parcial → credit note. Cancelamento → record novo negativo.

### Confirmar