C12 fluxo critico OK no DB (antecipar/revogar/re-antecipar/realizada
detecta paid). 5 bugs corrigidos no caminho: re-antecipar nao reusa
cancelled, popover watch sync com lookup virtual->materializada,
normalizeForMelissa expoe owner_id, etc.
User adiou C12 pra iterar UX depois (pos-Rail/Clinica). Salvo em
memoria project_c12_antecipar_iterar.
C13 prep: lock "edit cobrada" ja implementado na Fase 6 (commit
1feb711). User vai validar visualmente com Joao Almeida ou Andre.
14 commits no dia. Pendencias documentadas. Working tree limpo
exceto HANDOFF/log (este commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
31 KiB
HANDOFF — 2026-05-20 (C10 ✅ + C11 ✅ + C12 ⏳ deferido · testando C13)
Documento de continuidade. Quando voltar, comece lendo esta página até o fim.
🎯 SE A FORÇA CAIR / SESSÃO PERDER CONTEXTO: C10 e C11 fechados. C12 fluxo crítico OK no DB mas UX confusa — adiado pra iterar pós-Rail/Clínica (memória project_c12_antecipar_iterar). Agora testando C13 (edit cobrada — invariante imutabilidade SimplePractice). Implementação JÁ existe (Fase 6 do commit
1feb711— Message com cadeado + AgendaEventoFinanceiroPanel embedded). Só validação visual + persistência.
🟢 14 COMMITS NO DIA. C10 (5/5), C11 (4/4), C12 deferred (DB OK), reverse transition trava implementada, popover watch sync implementado. Pós-C13: replicar Rail (AgendaTerapeutaPage) + Clínica (AgendaClinicaPage)
- iterar C12 UX + doc de ajuda (pendência separada).
C13 — passos de teste (próximo)
Paciente: João Almeida Martins (sessão 20/05 9:00 realizada + paid R$ 40 maquininha) ou André Green 20/05 (paid PIX).
Esperado ao abrir o AgendaEventDialog:
- Message azul com cadeado: "Cobrança de R$ X já emitida..."
- AgendaEventoFinanceiroPanel renderiza embaixo do Message
- Card "Aplicar alterações em" oculto (v-if="!occFinancialRecord")
- Só horário/observações editáveis; valor/serviços/tipo travados
C11 sub-test results
| # | Teste | DB validado |
|---|---|---|
| 11A | Realizada + markPaid PIX | sessions_used 0→1, record paid R$ 40 PIX |
| 11B | Falta + Descontar saldo | sessions_used 1→2, sem multa |
| 11C | Falta + Multa SEM consumir | sessions_used stays 2, multa pending R$ 30 |
| 11D | Cancelado + default_consume_on_miss=true | sessions_used 2→3, sem multa (>2h) |
Bugs descobertos + corrigidos durante C11
- UI "Como cobrar?" com options "Já recebi" misturadas → refatorado pra "Já recebi?" radio Sim/Não + select condicional
billing_contractssem colunaupdated_at→ UPDATE falhava silently em Promise.allSettled (root cause do saldo não incrementar). Trocado pra awaits sequenciais com error handling explícito- Reverse transitions deixavam multa órfã → dialog reverse implementado com radio "cancelar pending" + "devolver saldo" + warning pra paid
- Botão "Gerar cobrança" em sessão encerrada → bloqueado
- Lock total em cancelado/faltou: Editar sessão some, status mudanças disabled exceto Agendada (recovery)
- Label "A cobrar R$ X" em pacote saldo state=none → "Aguardando uso do pacote"
- Badge $ amber em pacote saldo state=none → suprimido
- billing_contract_id não amarrado em alguns flows → link universal antes dos blocos forward
- Reverse saldo decrementar: refresh sessions_used FRESH do DB antes do UPDATE (anti-race)
Pendências mapeadas pós-C13
- Popover snapshot:
eventoSelecionado.value = evé snapshot. Fix: guardar ev.id, derivar via computed Reverse transitions✓ implementado ahead of schedule- Cleanup teste: Otto sessão 5364f631 leftover (não-critical)
C10 sub-test results
| # | Teste | DB validado | Notas |
|---|---|---|---|
| A | Realizada sem markPaid | ✅ status=realizado, record=pending | Bubble do C9 funcionou |
| A2 | Realizada + markPaid maquininha | ✅ status=realizado, record=paid, payment_method=cartao_maquininha, paid_at set | João Almeida |
| B | Faltou + multa R$ 30 (fixed_fee) | ✅ original cancelled + nova multa "Multa por falta · sessão dd/mm/aa" | Otto Rank |
| C | Cancelado >2h antecedência | ✅ original cancelled, sem multa | Otto / Karen |
| C2 | Cancelado tardio (<2h) full charge | ✅ original cancelled + nova "Taxa de cancelamento tardio · sessão dd/mm/aa" | Karen Horney |
Pendências mapeadas durante C10 — pós-C13
- Reverse transitions: faltou/cancelado → agendado deixa multa órfã. Implementar confirm dialog oferecendo auto-cancelar multa.
- Popover snapshot:
eventoSelecionado.value = evé snapshot, não acompanha _paymentStateMap. Fix: guardar ev.id, derivar via computed. - Cleanup teste: Otto sessão 5364f631 às 19:30 UTC tem record pending R$ 40 leftover do teste A original. Apagar quando convenient.
Memórias relevantes:
project_agenda_reverse_transitions.mdproject_melissa_popover_snapshot.md
Code-fix aplicado em 20/05 (pré-C10)
useMelissaAgenda.js:1450-1505—_applyStatusDecisionsagora cancela octx.pendingRecordquando faltou/cancelado (com ou sem multa). Antes inseria a multa mas DEIXAVA o original pending → cobrança dupla (R$ 200 + R$ 30 = R$ 230). Audit trail vai emnotesdo record cancelado, descrição da multa nova carrega data: "Multa por falta · sessão 20/05/26".useAgendaFinanceiro.js:59— fix dormente'fixed'→'fixed_fee'(off-by-key contra schema; path nunca exercitado na Melissa, mas iria quebrar se algum dia fosse).
Financial exceptions seedadas (tenant Bruno Terapeuta / owner Leonardo)
patient_no_show→fixed_fee R$ 30patient_cancellation→full,min_hours_notice=2,default_consume_on_miss=true
🔴 PRÓXIMO PASSO IMEDIATO — Cenário 10 (Status change AVULSA)
Doc HTML diz: testar status change numa sessão avulsa com cobrança pendente,
mudando entre realizado / faltou / cancelado. As consequências financeiras
seguem financial_exceptions (regras configuradas pelo terapeuta sobre o
que acontece com a cobrança nesses casos).
Possíveis pacientes pra teste: usar Joyce, Sándor ou outro com cobrança avulsa pendente já criada.
Esperado (depende das financial_exceptions configuradas no tenant):
- Realizada: status muda; cobrança permanece (caminho default)
- Faltou: pode ter regra → cobrança 100% (paciente paga falta) ou cancela
- Cancelado: pode ter regra → cancelar cobrança ou cobrar parcial
Conferir:
STATUS_TO_EXCEPTIONmapping emuseAgendaFinanceiro.jsgetFinancialExceptionRule(tenantId, exceptionType)retorna a regrahandleStatusChangeorquestra: agenda update + financial adjust
Após C10: C11 (status change pacote saldo — usar a infra do Usar/Revogar) → C12 (antecipar pagamento) → C13 (edit cobrada).
Quando todos passarem, replicar em Rail (AgendaTerapeutaPage.vue) e
Clínica (AgendaClinicaPage.vue).
📦 O que foi feito em 20/05 madrugada (C9 + rowGroup financeiro + bubble cobranca-atualizada)
Cenário 9 ✅ (Per-session — Michael Balint 12 × R$ 150)
Testado e passou. Criou-se 1 rule + 12 agenda_eventos materializadas + 12 financial_records pending. Sem billing_contract. Cada sessão com badge $ amber individual. Sem nenhuma linha de pacote no popover (não tem contract → não aparece). Conforme esperado.
/melissa/financeiro-lancamentos agrupado por paciente
- DataTable com
rowGroupMode='subheader'+groupRowsBy='patient_id' - Default: todos os grupos da página expandidos (watcher popula
expandedGroupscom unique patient_ids quandorecordsGroupedmuda) - Header de grupo: avatar pequeno + nome + badge "N lançamento(s)"
- Click no chevron contrai/expande (auto via PrimeVue
expandableRowGroups) - Sort estável: ordena outer por nome do paciente, preserva inner order (pai → filhos de multas/taxas)
Bubble-up @cobranca-atualizada
Antes: AgendaEventoFinanceiroPanel.@cobranca-atualizada disparava só loadOccFinancialRecord (interno do dialog). O _paymentStateMap da agenda ficava stale → card no FC só atualizava ao trocar de view.
Agora: AgendaEventDialog._onCobrancaAtualizada faz duas coisas:
loadOccFinancialRecord()— refresca estado interno do dialogemit('cobranca-atualizada')— bubble pra MelissaLayout
MelissaLayout escuta nos 2 dialogs (principal + occurrenceMode) e chama onCobrancaAtualizada que dispara M.refetch() + refetchEventosHoje(). Resultado: card na agenda passa pra borda verde imediatamente após marcar pago.
📦 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)
_ruleContractMapem useMelissaAgenda: bulk-load agora popula contract info (id, style, totalSessions, sessionsUsed, packagePrice) porrecurrence_id. Query usarecurrence_rules.patient_idcomo fonte autoritativa (cobre saldo sem materializadas).- Normalize injeta
contractno evento → popover acessa viaev.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)
- Saldo: violeta
- 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):
- Materializa virtual se necessário (preserva
determined_commitment_idda regra) - Status='realizado' + link
billing_contract_id create_financial_record_for_sessionRPC com per-session amount- Incrementa
billing_contracts.sessions_used - Se atingiu total → contract
status='completed' - Toast verde + fecha popover/dialog
onRevogarSessao desfaz tudo:
- Cancela financial_record (status='cancelled')
- Decrementa sessions_used (não fica negativo)
- Reativa contract se estava completed
- Status volta pra 'agendado'
- Bloqueia se record já está
paid(precisa estorno formal pelo Financeiro) - Backfill de
determined_commitment_idse 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_idda 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
occFinancialRecordque 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
ruleIdsInViewde 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 →
gerarCobrancaManualdireto, fecha popover pra impedir double-click - Tooltip: "Gerar fatura agora"
Info de pacote no popover
- Header agora mostra
Sessão · Pacote · N sessões(computedseriesLabellê de_rawdo rule)
Botão "Excluir série inteira"
- Novo emit
delete-seriesemMelissaEventoPanel+ botão ao lado de "Excluir sessão" quando evento temrecurrence_id - Handler
onDeleteSeriesem MelissaLayout faz hard delete:financial_recordspendentes →agenda_eventosmaterializados →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.expandRulesagora pula ocorrência comexception.type === 'cancel_session'(era visível com status cancelado; doc dizia "some da agenda" mas código mantinha)patient_missed/therapist_canceled/holiday_blockpermanecem visíveis como histórico
recurrence_exceptions cancel idempotente
- Cancel de ocorrência (virtual e materializada) usa
upsertcomonConflict: '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.showPaymentRowantes excluía virtuais incondicionalmente. Agora só esconde quandopaymentState === 'none'(saldo/sem pacote continua limpo; upfront propagado mostra).MelissaAgenda.fcEvents: removida exigência de!is_occurrencenoisPaidSessione 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:
- Editar serviço já lançado e pago → NÃO (cobrança fiscal imutável)
- Alternar Particular/Convênio/Gratuito em série com cobrança ativa → NÃO (mesma razão)
- "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— computaisPaidSession(sessão+paciente+não-virtual+paymentState==='paid') e adiciona classema-evt--paidao FC event (combina comma-evt--inactive-patientse ambos).MelissaAgenda.vue:2325-2335— CSS forçaborder-left-color: #10b981 !important(emerald-500, 4px).!importantnecessário porque FC setaborderColorinline. 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-circleverde, label "Pago · R$ X,XX"pending→pi-dollaramber, label "A receber R$ X (cobrança pendente)" (mantido)none→pi-dollaramber, 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 entrepi-clockepi-map-markerem ambas as cópias (mobile inline + desktop floating).- Novo ref
sessionPaymentRecordemuseAgendaEventLifecycle.js:104+(sem guard deoccurrenceMode, contrário aooccFinancialRecordque continua só pra Rail/Clínica). LoaderloadSessionPaymentRecordchamado no mesmo lifecycle. - Computed
paymentSummaryemAgendaEventDialog.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-atualizadadoAgendaEventoFinanceiroPanelagora também disparaloadSessionPaymentRecordpra 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.
- Novo ref
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:
-
Botão "Cadastrar" do procedimento navegava pra
/pages/notfound- Root cause:
goToConveniosConfigemAgendaEventDialog.vueprefixava com/therapistou/admin, mas/configuracoes/*é rota raiz sobAppLayout(sibling, não filho). Em Melissa, convênios mora dentro do próprio layout viasecao: '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).
goToConveniosConfigfoi removida (dead code virou armadilha).
- Root cause:
-
Quick-create de procedimento inline (sem sair da agenda)
- Novo componente
InsurancePlanServiceQuickCreateDialog.vue(modelo doInsurancePlanQuickCreateDialog). 2 campos: nome do procedimento + valor que o convênio paga. Insere eminsurance_plan_servicesproinsurance_plan_idativo. - Wiring em
useAgendaEventLifecycle.js: novoplanServiceQuickDlgOpen+openPlanServiceQuickCreate()+onPlanServiceCreated(service). Após criar, recarregaloadInsurancePlanse 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:". planServiceQuickDlgOpenadicionado aoanyChildDialogOpenpra esconder o Resumo flutuante enquanto o quick-create está aberto.
- Novo componente
-
Botão "+ Novo convênio" faltando em
/melissa/cfg-convenios(e na rota canônica também)- Root cause:
ConfiguracoesConveniosPage.vuetinha o form de "Novo convênio" condicionado aaddingNew === 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.
- Root cause:
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:1826dentro do blocov-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-hintemAgendaEventDialog.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) ✅
- Fix
bloqueioCobrindo is not defined— função estava no escopo deuseMelissaAgendamasonSelectTimemora no_buildHandlers(outro escopo). Passada viadeps. Mesmo padrão que_openStatusDialog. - Soft warn dentro do dialog em vez de toast atrás do overlay — novo ref
dialogBlockOverlapno composable + nova propblockOverlapWarningnoAgendaEventDialog+ Message warn no topo do step 1. Reset nos outros openers (onCreateEvento,onCreateEventoForPatient,onEditEvento). - 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) ✅
- Fonte da hint chargeMode subiu de
0.72rem→0.8125rem(acima detext-xs). - 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). - Doc HTML Cenário 2 atualizado.
Cenário 3 (Avulsa cobrar ao salvar) ✅
- 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=falsese voltar pra 'link'. - Wire: AgendaEventDialog → useAgendaEventActions → useMelissaAgenda (handler avulsa +
_createPackageContract).
- Indicadores visuais de pagamento (novidade da sessão):
- Bulk-load de
financial_recordsem_reloadRangeetapa 4 (1 query única, mapa eventId → 'paid' | 'pending' | 'none'). normalizeForMelissaagora injetapaymentState+priceno 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.
- Bulk-load de
- 🐛 Bug fix
pickDbFieldsfaltandomodalidade— sessões avulsas eram salvas sem modalidade, DB caía no default 'presencial' independente da escolha. Adicionado ao whitelist emuseMelissaAgenda.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 emmemory/project_pickdbfields_whitelist.md. - Doc HTML atualizada amplamente:
- Nova seção topo
★ Indicadores visuais de pagamentocom 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 —").
- Nova seção topo
🧭 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) | ✅ |
| 10 | Status change avulsa (realizado/faltou/cancelado) | 🔴 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)
git status— confirmar working tree intacto- Ler HANDOFF até o fim
- Abrir
src/docs/agenda-compromisso-financeiro-cenarios.htmlno browser pra ver o estado atual do doc viva - Começar pelo Cenário 4 (Joyce, "Já recebi (dar baixa)")
- Cada cenário que passar:
- Atualizar status pra ✅ aqui no HANDOFF
- Se descobrir bug ou texto divergente, corrigir código + doc na hora
- Quando todos os 13 passarem: replicar em Rail e Clínica
- Adicionar
professional_cancellationnoSTATUS_TO_EXCEPTION - Marcar Fase 5 como ✅
- 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.htmljá 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
pickDbFieldsafetou 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.vueadmin ≠BloqueioDialog(4 modos) — casos distintosagenda_excecoesfoi dropada em 13/05financial_records.typeundefined semtypeno BASE_SELECT — fix 14/05 cedofinancial_records.descriptionundefined semdescriptionno BASE_SELECT — fix 14/05 noitehandleStatusChangeemuseAgendaFinanceiro.jsestá ÓRFÃO — não reativar_openStatusDialog+bloqueioCobrindo+dialogBlockOverlapdeclarados nouseMelissaAgendamas usados em_buildHandlers— passados viadeps. NÃO ESQUECER ao replicar em Rail/Clínicabilling_contracts.charging_styledistingue upfront/saldo/per_session- Ocorrência virtual tem
id="rec::<rule>::<date>"— detectar viatypeof === 'string' && startsWith('rec::')antes de query Supabase chargeModedefault 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
pickDbFieldswhitelist —useMelissaAgenda.js:74descarta campos não listados silenciosamente. Sintoma: campo escolhido na UI mas DB tem valor default. Memória:memory/project_pickdbfields_whitelist.md paymentSettlementfoi renomeado em 16/05 — agorapaymentMethod(string) +markPaidNow(bool). Handler aplicapayment_methodsempre,status='paid'só quando markPaidNow=true && method!='link'- Bulk-load de paymentState em
_reloadRangeetapa 4 — 1 query única emfinancial_recordsmapeada poragenda_evento_id. AnotapaymentStateno 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 dearg.paymentSettlement)
Bugs evitar repetir:
- Sempre adicionar campo novo ao
pickDbFields.allowedquando adicionar coluna em agenda_eventos - Sempre adicionar campo novo ao
BASE_SELECTquando query custom - Detectar
is_occurrenceourec::antes de query por UUID - Refs/funções do composable principal NÃO ficam acessíveis em
_buildHandlers— passar viadeps - Toast dentro de dialog modal fica atrás do overlay — usar Message