Files
agenciapsilmno/HANDOFF.md
T
Leonardo fad1f4ebd4 agenda: C8 OK + Usar/Revogar pacote saldo + UI de contract + ajustes UX
Cenário 8 (Pacote SALDO — Otávio Souza Ferreira 12 × R$ 50)
- Testado e passou. DB: 1 rule, 0 events, 1 contract (saldo), 0 records.
  Visual: 12 virtuais limpas no calendário.

UI de pacote (saldo + upfront)
- _ruleContractMap em useMelissaAgenda: bulk-load popula contract info
  (id, style, totalSessions, sessionsUsed, packagePrice) por
  recurrence_id. Query recurrence_rules.patient_id como fonte
  autoritativa — cobre saldo sem materializadas (sem isso, ruleToPatient
  via records vinha vazio pra saldo)
- normalize injeta `contract` no evento via ruleContractMap
- MelissaEventoPanel: nova linha colorida (violeta saldo, verde upfront)
  com "Pacote saldo · N/M usadas" ou "Pacote · N/M realizadas"
- AgendaEventDialog: info card mt-4 com header+body+hint explicando
  modelo, gateado por occFinancialLoading (spinner durante carga
  pra evitar piscar entre Usar/Revogar)

Handlers Usar/Revogar atômicos
- onUsarSessao em MelissaLayout: materializa virtual (preserva
  determined_commitment_id da regra) → status=realizado +
  billing_contract_id → create_financial_record_for_session →
  sessions_used++ → (se atingiu total) contract.status=completed
- onRevogarSessao: cancela record + sessions_used-- + reativa contract
  se estava completed + status=agendado. Bloqueia se record paid
  (precisa estorno formal pelo Financeiro)
- Ambos aceitam payload {eventRow, contract} do dialog OU fallback
  pra eventoSelecionado do popover
- Botão "Usar" verde no popover (paymentState=none) substituído por
  "Revogar" vermelho (paymentState=pending). Equivalente "Usar agora"/
  "Revogar uso" no info card do dialog

Fix enum status_evento_agenda
- 'realizada' não existe no enum — DB exige 'realizado' (masculino).
  Corrigido em todas as ocorrências do handler

Fix campo "Título" indevido em sessão
- Sessão sem determined_commitment_id → selectedCommitment=null →
  isSessionEvent=false → mostra campo Título (que é só pra não-sessão)
- Fix: materialize do Usar inclui determined_commitment_id (insert
  path); update path backfilla via query da rule se NULL; Revogar
  também backfilla pra consistência

Fix "Gerar fatura" não cabe em saldo
- Botão "Gerar fatura" do popover hide quando há contractInfo. Em
  saldo, gerar fatura solta criaria cobrança duplicada sem incrementar
  sessions_used. Fluxo correto: "Usar"

Recorrências Aplicadas — UI
- Header stats coloridos: total **azul**, realizadas **verde**,
  faltaram **amber**, canceladas **cinza**, remarcadas **violeta**
- Pills com badge sólido por status (emerald-600 realizado, amber-600
  faltou, stone-500 cancelado, violet-600 remarcado)

Race condition no dialog
- AgendaEventDialog mostrava botões Usar/Revogar baseado em
  occFinancialRecord async; durante ~500ms de load, botão errado
  podia piscar. Fix: spinner "Verificando estado…" enquanto
  occFinancialLoading=true; botões só renderizam após
- Popover não fixado (race window pequena, fechar/reabrir resolve)

3 decisões UX confirmadas antes de codar
- Editar serviço pago → NÃO (cobrança fiscal imutável)
- Alternar Particular/Convênio/Gratuito em série cobrada → NÃO
- Gerar fatura individual em pacote upfront → NÃO (duplicação)

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

360 lines
25 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.
# HANDOFF — 2026-05-19 madrugada (C1-C8 ✅ + UI saldo + Usar/Revogar, próximo C9)
Documento de continuidade. **Quando voltar, comece lendo esta página até o fim.**
> **🎯 SE A FORÇA CAIR / SESSÃO PERDER CONTEXTO:** estamos na rodada de
> testes manuais dos 13 cenários do doc viva
> `src/docs/agenda-compromisso-financeiro-cenarios.html`. **C1-C8 ✅**.
> Próximo: **Cenário 9** (Michael Balint · per_session · 12 × R$ 150 —
> materializa todas 12 sessões + cria 12 records pendentes upfront).
> **🟢 WORKING TREE LIMPO** após commit/push de 19/05 madrugada. UI de
> pacote saldo completa (info card violeta + botão Usar/Revogar atômico
> que materializa+realiza+cobra ou desfaz). Backfill de
> determined_commitment_id em revogar+usar (evita campo "Título" indevido
> em sessões legadas).
---
## 🔴 PRÓXIMO PASSO IMEDIATO — Cenário 9 (Per-session)
| Campo | Valor |
|---|---|
| Paciente | **Michael Balint** |
| Frequência | **Semanal · 12 ocorrências** |
| Serviço | Sessão R$ 150 |
| Cobrança ao salvar | **1 por sessão** (chargeMode=per_session) |
**Esperado:**
- 1 row em `recurrence_rules`
- **12 rows em `agenda_eventos`** (TODAS materializadas — diferença chave vs C6-C8)
- 0 rows em `billing_contracts` (per_session NÃO usa contrato)
- **12 rows em `financial_records`** (1 por sessão, todos pending, amount=150 cada)
- Agenda: 12 sessões com **badge $ amber** em todas (cada uma tem seu record próprio)
- Popover de cada uma: linha amber "A receber R$ 150,00 (cobrança pendente)"
Diferença chave vs cenários anteriores:
- C6 (none): 1 row materializada + 0 records
- C7 (upfront): 1 row + 1 record (R$ totalPacote)
- C8 (saldo): 0 rows + 0 records (modelo Cliniko, geram on-the-fly via Usar)
- **C9 (per_session): N rows + N records pré-criados**
Após C9: C10-C13 (status change + edit cobrada).
Quando todos passarem, replicar em **Rail** (`AgendaTerapeutaPage.vue`) e **Clínica** (`AgendaClinicaPage.vue`).
---
## 📦 O que foi feito em 19/05 madrugada (C8 + Usar/Revogar saldo + UI de pacote)
### Cenário 8 ✅ (Pacote SALDO — Otávio Souza Ferreira 12 × R$ 50)
Testado e validado. Contract criado com `charging_style='saldo'`, 0 events materializadas, 0 records. Modelo Cliniko: sessões materializam on-demand via Usar.
### UI do pacote (saldo + upfront)
- **`_ruleContractMap`** em useMelissaAgenda: bulk-load agora popula contract info (id, style, totalSessions, sessionsUsed, packagePrice) por `recurrence_id`. Query usa `recurrence_rules.patient_id` como fonte autoritativa (cobre saldo sem materializadas).
- **Normalize** injeta `contract` no evento → popover acessa via `ev.contract`.
- **Popover** (`MelissaEventoPanel`): nova linha colorida abaixo do payment:
- Saldo: violeta `"Pacote saldo · N/M usadas"` + botão verde **"Usar"** (paymentState=none) OU vermelho **"Revogar"** (paymentState=pending)
- Upfront: verde `"Pacote · N/M realizadas"` (sem botão; cobrança já tratada)
- **AgendaEventDialog**: info card mt-4 (saldo violeta / upfront emerald) com header (pacote+contador), body (total/per-session/restam), botão "Usar agora" ou "Revogar uso", hint explicando o modelo. Gateado por `occFinancialLoading` (spinner durante carga) pra evitar piscar entre estados.
### Handlers Usar/Revogar atômicos
**`onUsarSessao`** em MelissaLayout (aceita payload do popover OU do dialog):
1. Materializa virtual se necessário (preserva `determined_commitment_id` da regra)
2. Status='realizado' + link `billing_contract_id`
3. `create_financial_record_for_session` RPC com per-session amount
4. Incrementa `billing_contracts.sessions_used`
5. Se atingiu total → contract `status='completed'`
6. Toast verde + fecha popover/dialog
**`onRevogarSessao`** desfaz tudo:
1. Cancela financial_record (status='cancelled')
2. Decrementa sessions_used (não fica negativo)
3. Reativa contract se estava completed
4. Status volta pra 'agendado'
5. Bloqueia se record já está `paid` (precisa estorno formal pelo Financeiro)
6. **Backfill** de `determined_commitment_id` se NULL (fix de legado)
### Fix: enum status_evento_agenda
Era `'realizada'` no insert/update, DB exige `'realizado'` (masculino). Corrigido em todas as ocorrências.
### Fix: campo "Título" indevido no dialog
Sessão sem `determined_commitment_id``selectedCommitment=null``isSessionEvent=false` → mostra campo Título (que é só pra não-sessão). Fix:
- Materialize do Usar inclui `determined_commitment_id` da regra
- Update path do Usar (sessão real após revogar) backfilla via query da rule
- Revogar também backfilla — garante consistência mesmo sem novo Usar
- SQL massivo de backfill disponível no HANDOFF pra limpar rows legadas
### Fix: "Gerar fatura" não cabe em sessão de saldo
Hide do botão "Gerar fatura" no popover quando há `contractInfo`. Geraria cobrança solta sem incrementar saldo → duplicação. Fluxo correto: usar "Usar".
### Recorrências Aplicadas: cores + badges
- Header stats: total **azul**, realizadas **verde**, faltaram **amber**, canceladas **cinza**, remarcadas **violeta**
- Pills: badge sólido por status (Realizado=emerald-600, Faltou=amber-600, Cancelado=stone-500, Remarcado=violet-600)
### Race condition no dialog
- AgendaEventDialog mostrava botões "Usar"/"Revogar" baseado em `occFinancialRecord` que carrega async
- Durante load (~500ms), botão errado podia aparecer → snap pro correto depois
- Fix: spinner "Verificando estado…" enquanto `occFinancialLoading=true`; botões só renderizam após
- Popover decidiu manter como está (race window pequena, fechar/reabrir resolve)
---
## 📦 O que foi feito em 19/05 noite (C7 + lock-edit + propagação cross-week)
### Cenário 7 ✅ (Pacote UPFRONT — Ana Souza Ferreira)
Testado e validado. Usuária criou Ana, R$ 200/sessão × 4 = R$ 800, marcou como pago em dinheiro pelo Financeiro. Visualização correta em mês AND em semana navegando pelas 4 semanas.
### Fase 6 (lock-edit cobrada) ativada em Melissa
Antes: `loadOccFinancialRecord` tinha guard `if (!props.occurrenceMode) return;` — só carregava em Rail/Clínica (edição de ocorrência). Em Melissa, `sessionPaymentRecord` paralelo alimentava só o Resumo lateral, sem trigger de lock.
Agora unificado: `occFinancialRecord` carrega em ambos modos:
- Card Sessão / Honorários ganha **Tag** (em vez de Select billingType) quando há cobrança
- Body do card mostra **Message "Cobrança de R$ X já emitida"** + cadeado
- Tipo de cobrança (Particular/Convênio/Gratuito) bloqueado
- Edição de serviços/preço bloqueada
### Propagação cross-week de pacote upfront pago/pendente
**Bug descoberto durante C7:** ao navegar pra semanas futuras (onde só virtual da Ana 2/3/4 aparecia, sem real event paid na view), o `_rulePaymentMap` era zerado pelo else branch do bulk-load → virtuais perdiam estado paid.
Fix em `useMelissaAgenda.js _reloadRange`:
- Maps (paymentStateMap, amountMap, rulePaymentMap) inicializados SEMPRE no início
- Propagação agora roda **independente de realIds.length** (ie, mesmo em semanas só-com-virtuais)
- Coleta `ruleIdsInView` de TODOS eventos da view (reais + virtuais com recurrence_id)
- Cross-week query: pra cada rule em view, busca QUALQUER evento sibling (inclusive em outras semanas) + seus records paid/pending → determina estado do contrato
- Propaga estado pra eventos reais (via map) + virtuais (via rulePaymentMap acessado pelo normalize)
### Atalho "Gerar fatura" no popover
- Pill amber pequeno ao lado de "A cobrar R$ X" no popover (`paymentVariant === 'none' && !is_occurrence`)
- Click → `gerarCobrancaManual` direto, fecha popover pra impedir double-click
- Tooltip: "Gerar fatura agora"
### Info de pacote no popover
- Header agora mostra `Sessão · Pacote · N sessões` (computed `seriesLabel` lê de `_raw` do rule)
### Botão "Excluir série inteira"
- Novo emit `delete-series` em `MelissaEventoPanel` + botão ao lado de "Excluir sessão" quando evento tem `recurrence_id`
- Handler `onDeleteSeries` em MelissaLayout faz hard delete: `financial_records` pendentes → `agenda_eventos` materializados → `recurrence_rules` (CASCADE leva exceptions + rule_services)
- Bloqueia se algum record tem `status='paid'` (estornar primeiro)
### RPC `create_financial_record_for_session` ignora cancelled
**Migration 20260519000001:** idempotência da RPC passou a filtrar `AND status != 'cancelled'` além de `deleted_at IS NULL`. Antes: cancelar cobrança sem querer → todo "Gerar fatura" subsequente retornava o cancelado em vez de criar nova. Toast verde mentindo.
Memória durável em `memory/project_rpc_idempotency_cancelled.md`.
### `cancel_session` exception some da agenda
- `useRecurrence.expandRules` agora pula ocorrência com `exception.type === 'cancel_session'` (era visível com status cancelado; doc dizia "some da agenda" mas código mantinha)
- `patient_missed` / `therapist_canceled` / `holiday_block` permanecem visíveis como histórico
### `recurrence_exceptions` cancel idempotente
- Cancel de ocorrência (virtual e materializada) usa `upsert` com `onConflict: 'recurrence_id,original_date'` — não quebra mais com unique violation quando há exception zumbi de tentativa anterior.
### Visualização paid/pending de upfront em virtuais
- `MelissaEventoPanel.showPaymentRow` antes excluía virtuais incondicionalmente. Agora só esconde quando `paymentState === 'none'` (saldo/sem pacote continua limpo; upfront propagado mostra).
- `MelissaAgenda.fcEvents`: removida exigência de `!is_occurrence` no `isPaidSession` e no badge $ pendente. Virtuais herdadas via propagação mostram borda verde/badge amber.
### `onVerLancamentos` cobre virtual de upfront
- Antes: virtual sempre toast "Sem lançamentos". Agora: busca records via siblings da série pra encontrar o do pacote. Saldo/sem pacote continua com toast.
### Confirmação 3 decisões UX (não codar)
Antes de C7, user perguntou e concordou:
1. Editar serviço já lançado e pago → **NÃO** (cobrança fiscal imutável)
2. Alternar Particular/Convênio/Gratuito em série com cobrança ativa → **NÃO** (mesma razão)
3. "Gerar fatura" extra em sessão coberta por contrato upfront → **NÃO** (duplicaria cobrança)
Tudo isso o lock-edit (Fase 6 ativada acima) cobre.
---
## 📦 O que foi feito em 18/05
### Cenário 4 (Joyce · "Já recebi") ✅
- Testado e passou: toast "Cobrança paga R$ 180,00 recebido via PIX", record nasceu `paid + payment_method=pix + paid_at=now()`.
### Novo indicador: barra esquerda verde para sessão paga
- Brainstorm de 6 opções; user escolheu #6 (3 canais visuais distintos por estado).
- `MelissaAgenda.vue:395-419` — computa `isPaidSession` (sessão+paciente+não-virtual+`paymentState==='paid'`) e adiciona classe `ma-evt--paid` ao FC event (combina com `ma-evt--inactive-patient` se ambos).
- `MelissaAgenda.vue:2325-2335` — CSS força `border-left-color: #10b981 !important` (emerald-500, 4px). `!important` necessário porque FC seta `borderColor` inline. Trata também list view (`.fc-list-event-dot`).
- Doc HTML atualizado: legenda "Indicadores visuais" agora descreve **3 estados** (pendente / pago / neutro) com 3 mocks empilhados; estado-alvo do C4 reescrito mencionando a barra verde.
- Decisão salva em `memory/project_agenda_payment_indicators.md`.
### Linha "Cobrança" no popover + Resumo do dialog
- **Popover `MelissaEventoPanel`** — antes só mostrava amber "A receber R$ X" pra pendente. Agora cobre os 3 estados, com cor + ícone por variante:
- `paid``pi-check-circle` verde, label **"Pago · R$ X,XX"**
- `pending``pi-dollar` amber, label **"A receber R$ X (cobrança pendente)"** (mantido)
- `none``pi-dollar` amber, label **"A cobrar R$ X"** ou **"Cobrança ainda não gerada"** (mantido)
- CSS reescrito em 3 modificadores `.evento-row--pay-{paid|pending|none}` (com dark mode).
- **Resumo lateral do `AgendaEventDialog`** — nova linha entre `pi-clock` e `pi-map-marker` em ambas as cópias (mobile inline + desktop floating).
- Novo ref `sessionPaymentRecord` em `useAgendaEventLifecycle.js:104+` (sem guard de `occurrenceMode`, contrário ao `occFinancialRecord` que continua só pra Rail/Clínica). Loader `loadSessionPaymentRecord` chamado no mesmo lifecycle.
- Computed `paymentSummary` em `AgendaEventDialog.vue:951+` retorna `{icon, cls, label}` pra 5 casos: paid (verde + paid_at), overdue (vermelho + due_date), pending (amber + due_date), sem cobrança c/ valor (neutro), sem cobrança s/ valor (neutro).
- `@cobranca-atualizada` do `AgendaEventoFinanceiroPanel` agora também dispara `loadSessionPaymentRecord` pra a linha refrescar.
- **Importante:** `occFinancialRecord` (que aciona lock-edit) NÃO foi tocado de propósito — esse é território da Fase 6/C13 (Edit cobrada). Manter dois refs separados evita ativar lock prematuro em Melissa.
### Preparação do C5 (Sándor + Unimed Nacional) — UX de convênio refinado (3 issues)
User tentou rodar C5 e bateu em 3 problemas seguidos. Cada um virou um fix:
1. **Botão "Cadastrar" do procedimento navegava pra `/pages/notfound`**
- Root cause: `goToConveniosConfig` em `AgendaEventDialog.vue` prefixava com `/therapist` ou `/admin`, mas `/configuracoes/*` é rota **raiz** sob `AppLayout` (sibling, não filho). Em Melissa, convênios mora dentro do próprio layout via `secao: 'cfg-convenios'` (sem URL própria).
- Fix descartado: o user não queria sair da agenda. Em vez disso, criamos um quick-create inline (ver #2). `goToConveniosConfig` foi removida (dead code virou armadilha).
2. **Quick-create de procedimento inline (sem sair da agenda)**
- Novo componente `InsurancePlanServiceQuickCreateDialog.vue` (modelo do `InsurancePlanQuickCreateDialog`). 2 campos: nome do procedimento + valor que o convênio paga. Insere em `insurance_plan_services` pro `insurance_plan_id` ativo.
- Wiring em `useAgendaEventLifecycle.js`: novo `planServiceQuickDlgOpen` + `openPlanServiceQuickCreate()` + `onPlanServiceCreated(service)`. Após criar, recarrega `loadInsurancePlans` e **auto-seleciona** o novo procedimento **só quando nada estava selecionado antes** (preserva escolha quando user já tinha selecionado X e está só cadastrando Y pra próxima).
- UI refatorada (`AgendaEventDialog.vue:3110+`): a caixa cinza com botão "Cadastrar" agora aparece **sempre** que um convênio está selecionado. Quando 0 procedimentos: **"Este convênio ainda não tem procedimentos cadastrados."** Quando 1+: **"Se quiser adicionar mais procedimentos a este convênio:"**.
- `planServiceQuickDlgOpen` adicionado ao `anyChildDialogOpen` pra esconder o Resumo flutuante enquanto o quick-create está aberto.
3. **Botão "+ Novo convênio" faltando em `/melissa/cfg-convenios` (e na rota canônica também)**
- Root cause: `ConfiguracoesConveniosPage.vue` tinha o form de "Novo convênio" condicionado a `addingNew === true`, mas **nenhum botão setava esse flag**. Empty state mandava "Clique em 'Novo convênio'" sem botão pra clicar.
- Fix: toolbar simples no topo do template `<template v-else>` com `<Button label="Novo convênio" icon="pi pi-plus" @click="addingNew = true">`. Empty state corrigida pra apontar pro botão certo.
### Hint contextual abaixo do card Sessão / Honorários
- User pediu mensagem clarificando que "Nº da guia" é opcional em convênio.
- **Tentativa 1 (errou o lugar):** coloquei o hint em `AgendaEventDialog.vue:1826` dentro do bloco `v-if="occurrenceMode"` (só edita ocorrência em Rail/Clínica). User não viu.
- **Tentativa 2 (correta):** adicionado em `AgendaEventDialog.vue:2305+` (fluxo principal Melissa, fora do occurrenceMode). Mantive a tentativa 1 também — não atrapalha, só ativa em outro contexto.
- Texto: convênio = **"Nº da guia é opcional — você pode salvar a sessão e preencher depois, quando o convênio responder."** Gratuito = **"Sessão gratuita — nenhum lançamento será gerado no Financeiro."** Particular = sem hint (não há ambiguidade).
- Condição: `isSessionEvent && !occFinancialRecord && billingType === 'convenio'|'gratuito'`. Esconde quando há cobrança paga/pendente (lock-edit) — Message do panel já cobre.
- CSS: `.aed-billing-hint` em `AgendaEventDialog.vue:3558+` — barra esquerda primary, fundo neutro leve, fonte 0.78rem.
- Label do "Nº da Guia" no service-picker dialog também ganhou **(opcional)**.
---
## 📦 O que foi feito antes (16/05 noite/madrugada)
### Cenário 1 (Bloqueio) ✅
1. **Fix `bloqueioCobrindo is not defined`** — função estava no escopo de `useMelissaAgenda` mas `onSelectTime` mora no `_buildHandlers` (outro escopo). Passada via `deps`. Mesmo padrão que `_openStatusDialog`.
2. **Soft warn dentro do dialog** em vez de toast atrás do overlay — novo ref `dialogBlockOverlap` no composable + nova prop `blockOverlapWarning` no `AgendaEventDialog` + Message warn no topo do step 1. Reset nos outros openers (`onCreateEvento`, `onCreateEventoForPatient`, `onEditEvento`).
3. **Doc HTML Cenário 1 expandido** em 1a (criar bloqueio) + 1b (agendar sobre bloqueio), com mock visual da Message + comparação com agendador público (que veta).
### Cenário 2 (Avulsa sem cobrança) ✅
4. **Fonte da hint chargeMode** subiu de `0.72rem``0.8125rem` (acima de `text-xs`).
5. **Card Frequência avulsa** refeito — antes era empty state convidando configurar; agora renderiza com `.aed-pay-summary` (mesma estrutura do estado configurado: "Tipo: Avulsa · Sessão única, sem repetição" + botão Editar).
6. Doc HTML Cenário 2 atualizado.
### Cenário 3 (Avulsa cobrar ao salvar) ✅
7. **Refactor payment: `paymentSettlement` → `paymentMethod` + `markPaidNow`**
- UI antiga misturava método e status num único Select ("Já recebi — PIX").
- Agora 2 controles: Select forma (Enviar link / PIX / Dinheiro / Depósito / Cartão maquininha — SEM prefixo "Já recebi —") + SelectButton status (Cobrança pendente / Já recebi (dar baixa)).
- SelectButton só aparece quando método ≠ link (Asaas só liquida via webhook).
- Watcher força `markPaidNow=false` se voltar pra 'link'.
- Wire: AgendaEventDialog → useAgendaEventActions → useMelissaAgenda (handler avulsa + `_createPackageContract`).
8. **Indicadores visuais de pagamento** (novidade da sessão):
- Bulk-load de `financial_records` em `_reloadRange` etapa 4 (1 query única, mapa eventId → 'paid' | 'pending' | 'none').
- `normalizeForMelissa` agora injeta `paymentState` + `price` no evento.
- **Badge $ no canto** dos eventos da agenda — círculo amber 16px no canto superior direito. Só pra sessão + paciente + não-virtual + paymentState !== 'paid'.
- **Linha "A receber"** no popover (`MelissaEventoPanel`) — texto adaptativo: "A receber R$ X (cobrança pendente)" se pending, "A cobrar R$ X" se none, "Cobrança ainda não gerada" se sem valor.
9. **🐛 Bug fix `pickDbFields` faltando `modalidade`** — sessões avulsas eram salvas sem modalidade, DB caía no default 'presencial' independente da escolha. Adicionado ao whitelist em `useMelissaAgenda.js:74`. **TODAS as sessões avulsas criadas no Melissa antes desse fix estão como 'presencial' no DB** — pode precisar rodar UPDATE manual no banco se quiser corrigir histórico. Gotcha salvo em `memory/project_pickdbfields_whitelist.md`.
10. **Doc HTML atualizada amplamente**:
- Nova seção topo `★ Indicadores visuais de pagamento` com mocks (badge $ + linha popover) e link em violeta no TOC.
- Caixa violeta "Indicadores visuais" em cada cenário relevante (C2-C9).
- C4 ganhou caixa verde "estado-alvo" (sem badge, sem linha — pago).
- Receita do C3 e C4 atualizadas com os 3 controles (Cobrança ao salvar / Forma de pagamento / Status do pagamento) e opções limpas (sem prefixo "Já recebi —").
---
## 🧭 Onde estamos no plano de 9 fases
| Fase | Status |
|---|---|
| **1** Compromisso SEM paciente | ✅ |
| **2** Compromisso COM paciente | ✅ testado (C1-C3 done) |
| **3** Recorrência + replicar occurrenceMode Rail/Clínica | ⏳ |
| **4** Modo disparo cobrança híbrido | ⚠️ parcial |
| **5** Status change → confirm dialog | 🔄 Melissa codado + indicadores visuais done; falta testar (C10-C12) + replicar Rail/Clínica |
| **6** Edit cobrada | ✅ |
| **7** Pagamento separado | ⏳ |
| **8** Refund/credit note | ⏳ |
| **9** Plano Inicial | 📋 |
---
## 📋 Roteiro de testes restantes (`src/docs/agenda-compromisso-financeiro-cenarios.html`)
| # | Cenário | Status |
|---|---|---|
| 1 | Bloqueio (criar + agendar sobre) | ✅ |
| 2 | Avulsa sem cobrança | ✅ |
| 3 | Avulsa cobrar ao salvar | ✅ |
| 4 | Avulsa "já recebi" no salvar | ✅ |
| 5 | Avulsa convênio (Sándor + Unimed) | ✅ |
| 6 | Recorrente sem pacote (Maria Magali / Anna Freud) | ✅ |
| 7 | Pacote upfront (Ana Souza Ferreira 4 × R$ 200) | ✅ |
| 8 | Pacote saldo (Otávio 12 × R$ 50) | ✅ |
| **9** | **1 por sessão (Michael Balint 12 × R$ 150)** | 🔴 **PRÓXIMO** |
| 10 | Status change avulsa (realizado/faltou/cancelado) | ⏳ |
| 11 | Status change pacote saldo | ⏳ |
| 12 | Antecipar pagamento (Carl Jung) | ⏳ |
| 13 | Edit cobrada | ⏳ (parcialmente — lock ativo em Melissa pós-19/05 noite) |
---
## 📋 Como retomar amanhã (cego)
1. `git status` — confirmar working tree intacto
2. **Ler HANDOFF até o fim**
3. Abrir `src/docs/agenda-compromisso-financeiro-cenarios.html` no browser pra ver o estado atual do doc viva
4. **Começar pelo Cenário 4** (Joyce, "Já recebi (dar baixa)")
5. Cada cenário que passar:
- Atualizar status pra ✅ aqui no HANDOFF
- Se descobrir bug ou texto divergente, corrigir código + doc na hora
6. Quando todos os 13 passarem: replicar em **Rail** e **Clínica**
7. Adicionar `professional_cancellation` no `STATUS_TO_EXCEPTION`
8. Marcar Fase 5 como ✅
9. Decidir Fase 4 (modo disparo cobrança híbrido) OU Fase 3 (replicar occurrenceMode)
---
## 🚨 Pendência IMPORTANTE — não esquecer
**Pós-Fase 9** (quando concluirmos TODAS as fases 1-9):
- User vai passar prompt específico pra criar **documentação completa da parte de ajuda** do sistema
- Está em `memory/project_pendencia_doc_ajuda.md`
- O doc `agenda-compromisso-financeiro-cenarios.html` já está sendo escrito de forma que vira a doc final pra usuário (cada teste validado vira parte da doc)
**Histórico modalidade='presencial' no DB:**
- Bug do `pickDbFields` afetou TODAS as sessões avulsas criadas no Melissa até 16/05/2026
- Se quiser corrigir histórico, rodar UPDATE manual identificando sessões cuja modalidade visual era online (não há como saber retroativamente — perdido)
- Going forward o fix já cobre
---
## ⚠️ Gotchas duráveis (atualizados)
- **`MelissaBloqueios.vue` admin ≠ `BloqueioDialog` (4 modos)** — casos distintos
- **`agenda_excecoes` foi dropada** em 13/05
- **`financial_records.type` undefined sem `type` no BASE_SELECT** — fix 14/05 cedo
- **`financial_records.description` undefined sem `description` no BASE_SELECT** — fix 14/05 noite
- **`handleStatusChange` em `useAgendaFinanceiro.js` está ÓRFÃO** — não reativar
- **`_openStatusDialog` + `bloqueioCobrindo` + `dialogBlockOverlap`** declarados no `useMelissaAgenda` mas usados em `_buildHandlers` — passados via `deps`. **NÃO ESQUECER ao replicar em Rail/Clínica**
- **`billing_contracts.charging_style`** distingue upfront/saldo/per_session
- **Ocorrência virtual tem `id="rec::<rule>::<date>"`** — detectar via `typeof === 'string' && startsWith('rec::')` antes de query Supabase
- **`chargeMode` default dinâmico:** `'session'` em avulsa, `'none'` em recorrente
- **Toast atrás do overlay do dialog** — usar Message no topo do dialog em vez de toast quando contexto for dentro de dialog modal
- **Cuidado com `pickDbFields` whitelist** — `useMelissaAgenda.js:74` descarta campos não listados silenciosamente. Sintoma: campo escolhido na UI mas DB tem valor default. Memória: `memory/project_pickdbfields_whitelist.md`
- **`paymentSettlement` foi renomeado** em 16/05 — agora `paymentMethod` (string) + `markPaidNow` (bool). Handler aplica `payment_method` sempre, `status='paid'` só quando markPaidNow=true && method!='link'
- **Bulk-load de paymentState em `_reloadRange` etapa 4** — 1 query única em `financial_records` mapeada por `agenda_evento_id`. Anota `paymentState` no normalize. Badge na agenda + linha popover lêem daqui
---
## 🧠 Decisões persistidas (memory/)
**Indicadores visuais (16/05):**
- Badge $ no canto: só sessão + paciente + não-virtual + !paid
- Linha popover: 3 textos (a receber pending / a cobrar none / cobrança não gerada)
- Bulk-load 1x por _reloadRange, não query por evento
- Ocorrências virtuais sempre paymentState='none' (cobertas por contrato)
**Payment refactor (16/05):**
- Separar método (forma) de status (já pago?) — controles independentes na UI
- Método 'link' (Asaas) força markPaidNow=false (gateway externo)
- Wire format: `arg.paymentMethod` + `arg.markPaidNow` (no lugar de `arg.paymentSettlement`)
**Bugs evitar repetir:**
- Sempre adicionar campo novo ao `pickDbFields.allowed` quando adicionar coluna em agenda_eventos
- Sempre adicionar campo novo ao `BASE_SELECT` quando query custom
- Detectar `is_occurrence` ou `rec::` antes de query por UUID
- Refs/funções do composable principal NÃO ficam acessíveis em `_buildHandlers` — passar via `deps`
- Toast dentro de dialog modal fica atrás do overlay — usar Message