# HANDOFF — 2026-05-19 noite (C1-C7 ✅, próximo C8 — pacote saldo) 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-C7 ✅**. > Próximo: **Cenário 8** (Carl Jung · pacote SALDO · 4 × R$ 40 — modelo > Cliniko: contrato sem cobrança imediata, cada sessão gera record ao > ser realizada). > **🟢 WORKING TREE LIMPO** após commit/push de 19/05 noite. Fase 6 > (lock-edit cobrada) ativada em Melissa também. Lock + popover atalho > "Gerar fatura" + propagação cross-week de pacote upfront tudo > funcionando. --- ## 🔴 PRÓXIMO PASSO IMEDIATO — Cenário 8 (Pacote SALDO) | Campo | Valor | |---|---| | Paciente | **Carl Jung** | | Frequência | **Semanal · 4 ocorrências** | | Serviço | Sessão (R$ 40 cada) | | Cobrança ao salvar | **Pacote único** + estilo **"Saldo (Cliniko)"** | | Total contrato | **R$ 160** (4 × 40) | **Esperado:** - 1 row em `recurrence_rules` - **0 rows em `agenda_eventos`** materializadas inicialmente (saldo NÃO materializa 1ª) - 1 row em `billing_contracts` (type=package, charging_style=**saldo**, total_sessions=4, package_price=160) - **0 rows em `financial_records`** (sem cobrança imediata — modelo Cliniko) - Agenda: 4 ocorrências virtuais **TODAS LIMPAS** (sem badge $, sem barra verde) — saldo intencionalmente não propaga estado, cada sessão gera cobrança individual quando vira realidade Diferença chave vs upfront: - Upfront: 1 cobrança única (paga ou pendente) cobre todas as 4 sessões - Saldo: contrato sem cobrança; cada sessão materializada GERA sua própria cobrança ao virar realidade (status realizado/faltou via flow C10-C12) Após C8: C9 (per_session) → 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 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 `