Registra cronologia da leva noturna 20/05 evening -> 21/05 01:06:
Fase 0+0.5 sweep foundation, M1 Home/Components, M2 Pacientes batch,
M3+M4+M5+M6 foundation em batch, M5 quick wins, Fase 2 Graphify
hotspots, Asaas Gateway Tier 1 Fase A, Compliance CFP #5/#8/#9.
8 entradas no log.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DESIGN_ASAAS_GATEWAY.md documenta arquitetura. Schema novo: 2
migrations (tables + RLS) cobrindo asaas_customers + asaas_payments
+ asaas_webhook_events. Client service asaasGatewayService.js no
features/financeiro. 3 Edge Function stubs (create-payment-record,
cancel-payment, sync-payment) — webhook financial_records eh Fase B.
Bloqueadores Fase B (implementacao real): user precisa criar conta
Asaas, gerar API keys, configurar webhook, setar ENV vars no
Supabase. Decisao modelo de negocio (A/B/C) tambem pendente.
Stops marcados claramente no DESIGN.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modulo 6 da Fase 1. noticesSelects.js extrai os 2 selects do
noticeService (GLOBAL_NOTICE_SELECT, NOTICE_DISMISSAL_SELECT) +
noticeService passa a usa-los (zero select inline). Conversations
ganha foundation: 3 services (_tenantGuards, conversationsSelects,
conversationsRepository). Channel factory (WhatsApp/SMS/Email) e
composables ficam pra sessao dedicada — escopo M6 era so destravar
o supabase.from() inline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modulo 5 da Fase 1 + quick wins fechados. features/tenantship/ com
2 services + 2 composables (members + invites). MembersPage.vue
nova em views/pages/admin/ + rota /admin/members em routes.clinic.
Migration 20260520000005 cria RPC accept_tenant_invite (SECURITY
DEFINER + lock FOR UPDATE) — tenantInvitesRepository.acceptInvite
agora chama RPC real (nao mais stub). SaasTenantFeaturesPage
refatorada pra usar novo tenantFeatureAdminService. SetupWizardPage
2648 linhas deferido pra sessao dedicada.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modulo 4 da Fase 1. 9 arquivos novos em features/financeiro/:
4 services (_tenantGuards, financialSelects, financialRecords
Repository, financialExceptionsRepository, billingContractsRepository)
+ 4 composables (useFinancialRecords, useFinancialExceptions,
useBillingContracts, useBillingOrchestrator). Old composables ainda
em paralelo — Fase C (cutover) bloqueada pelas decisoes #2/#3/#6
de billing (memoria agenda_billing_decisoes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modulo 2 da Fase 1 de padronizacao em batch unico. patientsSelects.js
nova com 11 constantes de select. patientsRepository.js estendido com
~15 funcoes novas (markIntakeConverted, list/get/update por
contexto, etc). 8 composables refatorados em paralelo (usePatients,
useDetail, Financial, Sessions, Messages, Documents, Recurrences,
SupportContacts) — zero supabase.from() em qualquer composable de
patients. _lastPatientId movido pra DENTRO das functions nos 3
composables que tinham. CadastrosRecebidosPage + MelissaCadastros
Recebidos pegam carona dos selects. Aguarda teste batch consolidado.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modulo 1 da Fase 1 de padronizacao. Novos features/medicos (services
+ composable useMedicos) e features/insurance (idem). 3 cadastros
rapidos (medicos, convenios, ComponentCadastroRapido + Insurance
PlanQuickCreateDialog) migrados pra usar os composables novos —
zero supabase.from() em UI components. TEST_ACCOUNTS extraido pra
src/config/devTestAccounts.js. Topbar ganhou switcher de layout
+ atalhos M1 via novo useTopbarDevMenuExtras. M1.6 MelissaLayout
90 imports deferida pra sessao dedicada (memoria padronizacao_sweep).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Relatorio standalone HTML (com print CSS otimizado pra PDF export):
- 10 paginas estruturadas
- Sumario executivo + metricas + pontos fortes
- 10 codes smells / dividas tecnicas detalhadas
- 8 issues de UX
- 7 riscos arquiteturais
- 15 recomendacoes priorizadas (P0-P3) com esforco e impacto
- Roadmap proposto em 3 horizontes
- Apendices: 14 bugs do dia, pendencias, commits, status dos cenarios
Visao senior eng: arquitetura solida em conceito, divida tecnica
em execucao. Top 5 achados:
1. 3 hotspots >2.8k LOC cada (AgendaEventDialog 6k, MelissaLayout 4.3k)
2. Logica de status change triplicada (Melissa/Rail/Clinica)
3. billing_contracts.updated_at gotcha
4. Snapshot stale popover (mitigado mas estrutural)
5. Audit trail acumulando ruido
Recomendacao chave: extrair status change orchestrator pra composable
shared ANTES da replicacao Rail/Clinica. Senao replica os mesmos
14 bugs vezes 2.
Para PDF: abrir relatorios/RELATORIO-AGENDA-2026-05-20.html no
browser e Ctrl+P -> Salvar como PDF.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Bug do "Usar sumiu apos revogar antecipacao": o watch sincronizava
eventoSelecionado por id, mas quando virtual era materializada
(antecipar/Usar/Realizada flow) o id mudava de rec::rule::date
pra uuid real. Watch nao achava match -> popover ficava preso na
versao virtual stale -> botoes refletiam estado antigo.
Fix: lookup em 2 etapas:
1) match por id (caso comum)
2) match por recurrence_id+recurrence_date quando nao acha (caso
virtual->materializada). Pega a versao real correspondente
aquela data.
Estado final do teste C12 do user: status=realizado, saldo 3/4,
1 pending + 5 cancelled (audit trail de varios ciclos antecipar/
revogar). Funcionalmente OK; com o fix, retestes ficam mais limpos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug introduzido pelo watch sync do popover (commit b5e00a7).
Apos o sync com eventos computed, eventoSelecionado.value ficava
com apenas os campos do normalizeForMelissa return. owner_id,
tenant_id, terapeuta_id, billing_contract_id NAO estavam expostos
no normalize -> sumiam apos refresh. onAnteciparPagamento entao
mandava owner_id=null pro RPC create_financial_record_for_session
-> "null value in column owner_id violates not-null constraint".
Fix:
- normalizeForMelissa agora expoe owner_id, tenant_id,
terapeuta_id, billing_contract_id explicitos no return
- onAnteciparPagamento ganhou fallback robusto: ev.owner_id ||
ev._raw?.owner_id || M.ownerId.value, com throw explicito se
nada disponivel (em vez de mandar null pro RPC)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dois bugs descobertos no ciclo C12 antecipar -> revogar -> reantecipar:
1) confirmAnteciparPagamento reusava record cancelled:
Quando user revogava (vira cancelled) e antecipava de novo,
o existRec query pegava o cancelled e UPDATE-ava pra paid no
MESMO record id. Resultado: notes mantinham historico
"Cancelada via reversao" + "Antecipacao revogada" + record
reativo como paid, confuso pra audit trail. Fix: filtrar
.neq('status', 'cancelled') na busca de existRec — agora a
re-antecipacao via RPC cria record fresh.
2) Popover snapshot stale (pendencia documentada em
project_melissa_popover_snapshot, antecipada pra agora):
eventoSelecionado.value era snapshot do clique e nao acompanhava
updates do _paymentStateMap pos M.refetch. User antecipava, o
record paid era criado, mas o popover continuava com paymentState
antigo -> botao continuava "Antecipar pagamento" em vez de
alternar pra "Revogar pagamento". Fix: watch em M.eventos sincroniza
eventoSelecionado com a versao fresh quando id bate. flush:'post'
pra rodar apos o computed reagir.
Como o popover agora atualiza in-place, removido fecharEvento() de
confirmAnteciparPagamento e onRevogarAntecipacao — o user pode ver
o botao alternar live em vez de precisar reabrir o popover.
Cleanup do estado do Andre: deletado record orfa 3a4c79e0 pra reset
do teste C12.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UX gap descoberto durante teste C12: apos antecipar pagamento,
nao havia caminho via popover pra desfazer caso user tenha
errado (paciente nao pagou, errou o valor, etc).
Implementacao:
- Botao "Antecipar pagamento" agora alterna pra "Revogar
pagamento" (vermelho, --danger) quando isAntecipacaoAtiva
(status=agendado + paymentState=paid)
- Handler onRevogarAntecipacao em MelissaLayout: ConfirmDialog
vermelho + cancela record paid + nota de auditoria em notes
("[YYYY-MM-DD] Antecipacao revogada em ...") + refetch
- Apos revogar, botao volta pra "Antecipar pagamento" — user
pode antecipar de novo com valor/metodo corretos
Limites: so disponivel em status='agendado'. Apos Realizada o
paid representa pagamento real da sessao realizada, nao
antecipacao — estorno deve ir pelo /financeiro.
Sobre "Usar" desaparecer apos antecipar (questao do user): comportamento
correto. "Usar" cria record+consome saldo — duplicaria com paid
existente. Apos antecipar, fluxo correto e clicar Realizada (que
detecta paid pre-existente via fix anterior 00c4168).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Preparacao pra teste C12 (antecipar pagamento). Fluxo:
1. User clica "Antecipar pagamento" em virtual futura -> cria
record paid R$ X sem consumir saldo
2. Depois marca a sessao como Realizada -> dialog deve detectar
o paid + so consumir saldo (NAO criar record novo, evitar
duplicidade)
Sem esse fix, marcar Realizada apos antecipar abriria o dialog
"Gerar cobranca?" com default true, gerando record novo duplicado.
Implementacao:
- _loadStatusChangeContext: carrega ctx.existingPaidRecord (qualquer
paid linkado ao evento, n=1)
- Dialog: nova prop existingPaidRecord + computed showAlreadyPaid
(substitui showCobrancaPacote quando paid existe)
- Template: bloco "Sessao ja paga via antecipacao" com info do
pagamento + preview do consumo de saldo
- _applyStatusDecisions: novo branch 4-pre roda ANTES do generatePackageCharge:
se realizado+pacote saldo+paid existe, roda tasks pendentes (1b
amarra) + incrementa saldo sem criar record. Return cedo.
Backfill: Andre 10/06 voltou pra agendado + saldo 2/4 (estado limpo
pra testar C12 com a sessao 10/06 antecipando).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cenario 11 completo. 5 bugs descobertos+corrigidos durante
a bateria (UI confusa, gotcha billing_contracts.updated_at,
reverse transitions, lock sessao encerrada, label/badge
pacote-aware). Reverse trava antecipada de pos-C13 pra ja
(user hit pra valer).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dois bugs descobertos durante C11/C+D:
1) Faltou+multa SEM consumeSaldo nao amarrava billing_contract_id
no agenda_evento (so amarrava se consumeSaldo=true). Resultado:
sessao 27/05 do Andre faltou+multa-sem-consume ficou sem rastro
do contrato no DB. Reverse posterior nao detectaria saldoConsumed.
Fix: bloco 1b) universal — sempre amarra quando forward (realizado/
faltou/cancelado) + tem contract + eventoId. Cobre todos os
combos (multa-sem-consume, multa-com-consume, generatePackageCharge,
consumeSaldo solo).
2) Reverse decrementar saldo as vezes nao persistia. Suspeita: race
com ctx.billingContract.sessions_used stale do _loadStatusChangeContext
quando flows rapidos sequenciais (Realizada+gerar -> Agendada
imediato). Fix: refetch FRESH do billing_contracts.sessions_used
direto do DB ANTES de calcular newUsed. Mais robusto contra qualquer
race condition. Adicionado console.log pra futura debug.
Removida duplicidade do amarra-billing_contract_id no bloco
consumeSaldo (universal cobre).
Backfill Andre Green: 27/05 amarrado, saldo voltou pra 2/4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug descoberto durante teste C11: sessao 27/05 do Andre Green
(materializada via Falta+reverse, pertence ao pacote saldo)
mostrava "A cobrar R$ 40,00" no popover mesmo sem fatura ativa.
Implicava que dava pra gerar cobranca avulsa solta — conflito
com o flow do pacote (Usar consume saldo).
Fix em paymentLabel: quando state='none' e ev.contract existe,
label muda conforme estilo:
- saldo: "Aguardando uso do pacote"
- upfront: "Coberta pelo pacote (upfront)"
Avulsa sem pacote continua mostrando "A cobrar R$ X".
Simetria em MelissaAgenda.vue badge gate: nao mostra badge $ amber
em sessao state=none com pacote amarrado (hasPacoteTied). Sem
isso, sessao agendada de pacote saldo no calendar ficava com
badge "cobranca pendente" enganoso.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug em cascata descoberto durante C11/B com Andre Green:
- User clicou Falta + Descontar (consumeSaldo) -> sessions_used: 1->2
- billing_contract_id do agenda_evento ficou NULL (omissao no flow)
- User clicou Agendada (reverse) -> detector saldoConsumed em
_loadStatusChangeContext checa evRow.billing_contract_id, que esta
NULL -> saldoConsumed=false -> bloco "Devolver saldo" NAO aparece
no dialog -> saldo NAO devolvido
- Next Falta mostra "Descontar 2 para 3" em vez de "1 para 2"
Fix: bloco consumeSaldo agora tambem amarra billing_contract_id no
agenda_eventos. Replica o padrao que ja existe no generatePackageCharge
e no onUsarSessao. Sem isso, qualquer reverse pos-consumeSaldo nao
detecta o saldo consumido.
Backfill manual do Andre: sessions_used voltou pra 1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User hit pra valer a pendencia documentada (reverter
realizado/faltou/cancelado pra agendado deixa records/saldo
orfaos). Decidido implementar trava AGORA em vez de pos-C13.
Quando user clica "Agendada" no popover/dialog em sessao que
tem artefatos pendentes (cobranca pending, multa, saldo consumido
em pacote saldo), abre o AgendaStatusChangeConfirmDialog com nova
variante "reverse":
1. Lista records pending vinculados (descricao + valor) com radio
[Cancelar (recomendado) | Manter ativa]
2. Warning textual pra records PAID (estorno e manual pelo
Financeiro — sem radio, so info)
3. Saldo consumido (pacote saldo): radio [Devolver 1 sessao | Manter]
No confirm:
- Cancela records pending escolhidos (status='cancelled' + notes
de auditoria)
- Decrementa sessions_used + reativa contract se estava completed
- Desamarra billing_contract_id do evento se devolveu saldo
- Status muda pra agendado (ja foi aplicado pelo _applyStatusUpdateOnly)
Se nao tem artefato algum (sessao agendado -> agendado, ou
realizado sem records): aplica direto sem dialog (existing
behavior via _needsConfirmDialog).
_loadStatusChangeContext agora carrega reverseArtifacts (status
anterior, records ativos, saldoConsumed) quando novoStatus=agendado.
Memoria project_agenda_reverse_transitions atualizada — pendencia
fechada antes da hora.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ROOT CAUSE DESCOBERTO durante C11/A com Andre Green:
billing_contracts NAO tem coluna updated_at. UPDATEs em
_applyStatusDecisions passavam updated_at -> Postgres retornava
"column updated_at does not exist" -> Promise.allSettled engolia
como {status: 'rejected'} silencioso -> toast warn generico que
user nao percebia. Resultado: sessions_used nunca incrementava.
Bug existia em DOIS lugares:
1. consumeSaldo block (faltou/cancelado pacote saldo) - afetaria
C11/B, C11/C, C11/D
2. generatePackageCharge block (realizado pacote saldo) - afetou
C11/A
Em ambos: removido updated_at do patch (.update({...})).
ADICIONAL: generatePackageCharge refatorado pra usar AWAITS
SEQUENCIAIS (igual onUsarSessao do MelissaLayout que sempre
funcionou):
- 4a) UPDATE agenda_eventos.billing_contract_id (faltava!)
- 4b) RPC create_financial_record_for_session
- 4c) UPDATE billing_contracts.sessions_used + status=completed
Cada step com try/catch + console.error + toast distinto. Sem mais
falhas escondidas em Promise.allSettled paralelo.
Backfill manual do estado do Andre Green: evento 6e70476f agora
amarrado ao contract 691118da com sessions_used=1.
Memoria nova: project_billing_contracts_no_updated_at.md pra evitar
o gotcha no futuro.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Antes (UX confusa): bloco "Gerar cobranca no pacote?" tinha so um
Select "Como cobrar?" com options mixadas:
- "Enviar link de pagamento (Asaas)"
- "Ja recebi - PIX"
- "Ja recebi - Dinheiro"
- etc
User selecionou "Ja recebi - PIX" pensando que era "cobrar via PIX"
durante teste C11/A com Andre Green. Resultado: fatura virou paid
sem o user ter recebido de verdade. Ambiguidade entre "como cobrar"
(header) e "ja recebi" (options).
Refactor: espelhar o padrao da avulsa (showRegistrarPagto):
1. Sub-question "A sessao ja foi paga?" radio Sim/Nao (default Nao)
2. Se Nao -> Select "Como vai cobrar?" [Apenas registrar pendente |
Enviar link de pagamento (Asaas)]
3. Se Sim -> Select "Como recebeu?" [PIX | Dinheiro | Deposito |
Maquininha] (sem prefixo "Ja recebi" — header ja deixa claro)
Defaults safer: markPaid=false em ambos contextos (avulsa e pacote)
pra evitar marcar paid sem querer. paymentMethod='pending' inicial.
Handler em useMelissaAgenda._applyStatusDecisions: pos-processamento
agora usa decision.markPaid explicito no caso pacote saldo:
- markPaid=true -> record vira paid + payment_method=X
- markPaid=false + paymentMethod='link' -> pending + payment_method='asaas'
- markPaid=false + paymentMethod='pending' -> pending sem metodo
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cenario 10 (status change avulsa) completo:
- A: Realizada sem markPaid (record pending preservado)
- A2: Realizada + markPaid maquininha (paid + paid_at + payment_method)
- B: Faltou + multa fixed R$ 30 (original cancelled + nova multa)
- C: Cancelado >2h (original cancelled, sem multa)
- C2: Cancelado tardio <2h, full charge (original cancelled + nova taxa)
Bugs descobertos + corrigidos durante a bateria: cobranca dupla na
multa (cancela original agora), _reloadRange not defined no escopo
de _buildHandlers, badge $ amber em sessao encerrada, paymentLabel
usando ev.price em vez de paymentAmount pra pending, popover
permitindo emissao de fatura em sessao cancelada.
3 pendencias pos-C13 mapeadas em memoria + addendum HTML do doc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bugs descobertos durante testes C10/A2/B/C com user:
1) _reloadRange not defined: _buildHandlers nao destruturava
_reloadRange do deps (passava mas nao desempacotava). Toast
ReferenceError ao tentar reload pos-status change. Fix em
useMelissaAgenda.js:_buildHandlers.
2) Badge $ amber em sessao cancelada: MelissaAgenda.vue badge gate
ignorava status. Cancelado+state=none (records cancelled
filtrados) ainda recebia badge "cobranca pendente". Fix: gate
sessaoEncerrada (cancelado/faltou) -> sem badge nunca.
3) Botao "Gerar cobranca" em sessao encerrada: AgendaEventoFinanceiro
Panel mostrava botao mesmo em cancelado/faltou -> user podia
emitir fatura nova em sessao que nao aconteceu. Fix: v-if
!isSessaoEncerrada + label muda pra "Sessao cancelada · sem
cobranca ativa".
4) paymentLabel usava ev.price em vez de paymentAmount pra state
'pending': caso multa R$ 30 mostrava R$ 150 (ev.price original).
Fix: usar paymentAmount tambem em pending.
5) Lock total em sessao encerrada (cancelado/faltou):
- "Editar sessao" SOME do popover
- Realizada/Falta/Reagendar/Cancelar disabled com tooltip
- Apenas "Agendada" continua funcional (caminho explicito de
recuperacao). Single path de saida do estado encerrado.
Adicoes UX em AgendaStatusChangeConfirmDialog:
- Hint contextual sobre min_hours_notice explicando POR QUE multa
veio (des)marcada por padrao: "Cancelou 18.5h antes da sessao.
Regra: multa apenas quando cancelamento <2h -> sem multa por
padrao." Terapeuta ve a razao e pode inverter conscientemente.
Adicoes UX em MelissaEventoPanel:
- Botao "Agendada" (variante --info azul cyan) no grupo status
pra reset/recuperacao. CSS .evento-act--info hover + is-current.
Doc:
- Addendum C10 no topo de src/docs/agenda-compromisso-financeiro
-cenarios.html capturando todas as divergencias/melhorias vs
mockup original + 3 pendencias pos-C13 (reverse transitions,
popover snapshot, A2 markPaid stale).
Pendencias salvas em memoria pra puxar pos-C13.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adicoes (durante teste C10/A2):
- Botao "Agendada" no popover (pi-calendar, variante --info azul
cyan) pra permitir reset de status realizado/faltou/cancelado
voltando pra agendado sem precisar abrir o AgendaEventDialog.
Wire-up: emit 'agendar' -> onAgendar -> updateEventoStatus.
- CSS .evento-act--info: hover + is-current com tom cyan
(#38bdf8 do domainColors da agenda). Highlight generico
rgba(255,255,255,0.12) era invisivel em light mode.
Bug fixes durante teste C10/B com Otto Rank:
- MelissaEventoPanel paymentLabel: usar paymentAmount tambem pra
state='pending' (antes so 'paid' usava; pending caia em ev.price
e mostrava R$ 150 original quando o pendente real era R$ 30 da
multa).
- useMelissaAgenda onUpdateSeriesEvent: chamar _reloadRange() apos
_applyStatusDecisions. Sem isso o paymentStateMap+amountMap nao
re-populavam apos status change com multa -> FullCalendar e
popover ficavam stale ate F5/troca de view.
Pendencia salva em memoria: travas em reverse transitions
(faltou->agendado deixa multa orfa). User hit pra valer com Otto
durante teste, R$ 30 limpo manualmente no DB. Implementar pos-C13.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: ao mudar status pra faltou/cancelado com multa configurada
em financial_exceptions, _applyStatusDecisions INSERIA o novo
record da multa MAS deixava o pendingRecord original em pending.
Resultado: cobranca dupla (R$ 200 original + R$ 30 multa = R$ 230).
Fix em useMelissaAgenda.js:1450-1505:
- applyFine agora carrega data da sessao na description ("Multa
por falta - sessao dd/mm/aa") pro paciente identificar na fatura.
- Novo bloco 2b: cancela ctx.pendingRecord quando faltou/cancelado,
com nota de auditoria appendada em notes ("[YYYY-MM-DD] Cancelada
- substituida por multa de no-show" ou similar). Vale tanto pra
caso com multa quanto sem (status mudado sem aplicar multa).
Fix dormente em useAgendaFinanceiro.js:59 ('fixed' -> 'fixed_fee')
- charge_mode no schema eh 'fixed_fee' mas calcChargeAmount usava
'fixed' silenciosamente caia no fallback. Path nao exercitado na
Melissa (usa _applyStatusDecisions, nao handleStatusChange), mas
iria quebrar se algum dia fosse.
Pre-teste C10: financial_exceptions seedadas no DB para tenant
Bruno Terapeuta / owner Leonardo:
- patient_no_show: fixed_fee R$ 30
- patient_cancellation: full, min_hours_notice=2, default_consume_on_miss=true
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cenário 9 (Per-session — Michael Balint 12 × R$ 150)
- Testado e passou. 1 rule + 12 agenda_eventos materializadas + 12
financial_records pending. Sem billing_contract. Badge $ em todas as
12 sessões. Conforme esperado.
/melissa/financeiro-lancamentos: agrupado por paciente
- DataTable com rowGroupMode='subheader' + groupRowsBy='patient_id'
- Header de grupo com avatar + nome + badge "N lançamento(s)"
- expandableRowGroups + v-model:expandedRowGroups; watcher popula
todos os grupos da página atual como expandidos (sempre que
recordsGrouped muda — refletindo paginação/filtros)
- Sort outer por nome do paciente, preserva inner order
(pai → filhos de multas/taxas via mesmo agenda_evento_id)
Bubble-up @cobranca-atualizada → M.refetch
- Antes: ao marcar como pago no dialog, o card no FC ficava stale
até trocar de view. AgendaEventoFinanceiroPanel emitia
cobranca-atualizada mas só o loadOccFinancialRecord do dialog
escutava; o _paymentStateMap da agenda nao re-rodava.
- Fix: AgendaEventDialog ganhou _onCobrancaAtualizada que dispara
loadOccFinancialRecord() E emit('cobranca-atualizada') pra cima.
MelissaLayout escuta nos 2 dialogs e chama M.refetch() +
refetchEventosHoje(). Card passa pra borda verde na hora.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Cenário 7 (Pacote UPFRONT — Ana Souza Ferreira 4×R$ 200 = R$ 800)
- Testado e passou. User criou Ana, pagou os R$ 800 em dinheiro pelo
Financeiro. Borda verde + popover "Pago R$ 800" funcionando.
Fase 6 (lock-edit cobrada) ativada em Melissa
- Removido guard `if (!props.occurrenceMode) return;` em
loadOccFinancialRecord (useAgendaEventLifecycle.js:217+). Agora ele
carrega em ambos modos (Rail/Clínica E Melissa)
- loadOccFinancialRecord SINTETIZA record paid/pending pra siblings de
contrato upfront ativo — assim TODAS as ocorrências da série mostram
"Cobrança paga R$ 800 do pacote" no AgendaEventDialog
- AgendaEventDialog card Sessão/Honorários (flow Melissa) ganhou lock
template: Tag em vez de Select billingType quando occFinancialRecord
existe; Message com cadeado "Cobrança de R$ X já emitida"
- AgendaEventoFinanceiroPanel só renderiza dentro do lock quando record
é REAL (não sintetizado) — evita "Gerar cobrança" indevido em sibling
- paymentSummary do Resumo lateral unificado pra usar occFinancialRecord
(em vez do sessionPaymentRecord paralelo de antes)
Cross-week propagation de pacote upfront
- BUG: ao navegar pra semana só com virtuais (sem reais), bulk-load
caía no else `_rulePaymentMap.value = {}` — virtuais perdiam estado
paid herdado
- FIX em useMelissaAgenda._reloadRange:
* Maps (payment/amount/rule) inicializados SEMPRE no início
* Propagação roda independente de realIds.length (depende só de
ruleIdsInView.size>0, considera reais E virtuais com recurrence_id)
* Query cross-week: pra cada rule em view, busca QUALQUER evento
sibling em qualquer semana + seus records pra determinar estado do
contrato. Encontra o record do pacote mesmo em outra semana
- Saldo NÃO propaga (filter: charging_style='upfront' || NULL); cada
sessão de saldo gera cobrança individual ao realizar
- Memória durável: memory/project_cross_week_propagation.md
Visualização de virtuais cobertas
- MelissaEventoPanel.showPaymentRow: virtuais só escondem quando state
='none'. Com paid/pending herdado, exibem linha colorida
- MelissaAgenda fcEvents: isPaidSession e badge $ pendente removeram
exigência de !is_occurrence. Virtuais herdadas via propagação mostram
borda verde / badge amber
Atalho "Gerar fatura" no popover
- Pill amber pequeno ao lado de "A cobrar R$ X" quando paymentVariant
='none' && !is_occurrence. Click → gerarCobrancaManual direto, fecha
popover pra impedir double-click. Tooltip: "Gerar fatura agora"
- Wire em MelissaLayout via novo emit gerar-cobranca + handler
onGerarCobrancaQuick
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: hard delete em 3 etapas
(financial_records pendentes → agenda_eventos materializados →
recurrence_rules CASCADE leva exceptions). Bloqueia se algum record
paid (estorno via Financeiro primeiro)
cancel_session some da agenda
- useRecurrence.expandRules agora pula occurrence com exception.type=
'cancel_session' (era visível com status cancelado; doc dizia que
some). patient_missed/therapist_canceled/holiday_block permanecem
como histórico
recurrence_exceptions cancel idempotente
- MelissaLayout onDeleteEvento usa upsert com onConflict pra exception
cancel — não quebra mais com unique violation em re-cancel
billing_contract_id na 1ª materializada
- _createPackageContract agora .select() o contrato após insert e seta
billing_contract_id no insert da 1ª agenda_eventos materializada
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
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DB
- migration 20260519000001: create_financial_record_for_session passa a
ignorar status='cancelled' na idempotência (era bug — cancelar e tentar
regerar travava silencioso retornando o cancelado)
Cenário 5 (convênio) — fixes pra save + visualização
- Convênio: amount lia 'price' (null) → agora detecta via insurance_plan_id
e usa insurance_value. payment_method forçado 'convenio' (era 'asaas')
- Popover: ev.price era null em convênio → normalize expõe insurance_value
e paymentLabel faz fallback. Linha mostra "A receber R$ X" corretamente
- /financeiro: branch novo pra payment_method='convenio' → pill violeta
com pi-id-card (antes ficava sem indicador, igual particular)
Cenário 6 (recorrente sem pacote, Maria Magali) — materialização
- chargeMode='none' não materializava a 1ª (todas viravam virtuais, sem
badge $). Agora materializa a 1ª no fluxo de criação recorrente
- Bug intermediário: usei 'paciente_id' (Portuguese) mas agendaRepository
dropa esse campo. Corrigido pra 'patient_id' (English DB column)
Atalho "Gerar fatura" no popover
- Pill amber pequeno ao lado de "A cobrar R$ X" no popover (paymentVariant
='none' + sessão materializada)
- Wire em MelissaLayout via emit gerar-cobranca + handler onGerarCobrancaQuick
(chama gerarCobrancaManual, fecha popover pra impedir double-click)
- Bulk-load do useMelissaAgenda e fetchRecord do AgendaEventoFinanceiroPanel
agora filtram status='cancelled' (resolve badge $ residual + botão sumido)
Header do popover: info de pacote/série
- "Sessão · Pacote · N sessões" ou "Sessão X de Y" abaixo do tipo
(computed seriesLabel lê do _raw da rule)
Título do dialog "Sessão do Pacote · Sessão"
- Quando commitment name é "Sessão" (default), drop pra evitar duplicação
- Outros nomes (Avaliação, etc) permanecem com forma completa
Excluir série inteira (popover)
- Novo botão "Excluir série" no popover quando evento pertence a recorrência
- Hard delete: financial_records pendentes → agenda_eventos materializados
→ recurrence_rules (CASCADE leva exceptions + rule_services)
- Bloqueia se algum record tem status='paid' (estornar primeiro)
cancel_session some da agenda
- useRecurrence.expandRules agora pula occurrence com exception type=
'cancel_session' (era visível com status cancelado; doc dizia "some
da agenda" mas código mantinha. Honra a promessa do diálogo)
- patient_missed / therapist_canceled / holiday_block permanecem visíveis
como histórico
UX outros
- "+ Novo convênio" toolbar em ConfiguracoesConveniosPage (botão faltava
— empty state mandava clicar em botão inexistente)
- InsurancePlanServiceQuickCreateDialog: cadastrar procedimento POR CIMA
do AgendaEventDialog sem sair da agenda. Auto-seleciona quando nada
estava selecionado antes
- Hint contextual abaixo do card Sessão/Honorários: convênio = "Nº guia
opcional"; gratuito = "sem cobrança". Particular sem hint
- recurrence_exceptions cancel agora usa upsert com onConflict
(idempotente, não quebra com unique violation em re-cancel)
- goToConveniosConfig removida (dead code após quick-create inline)
CSS
- .aed-row-50 perdeu margin-bottom (user request)
- .field-card.mb-4 ganhou margin-top: 1rem (scoped a composer wrappers)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
BLOQUEIO REAL Inativo + AVISO Arquivado futura:
- useAgendaEventComposer.canSave agora bloqueia edicao de sessao futura
quando !perms.canReschedule. Antes canReschedule era dead code
(definido em getPatientAgendaPermissions mas nunca consumido). Pra
Inativo: canReschedule=false => Save desabilitado de verdade (antes
o aviso "Remarcacao bloqueada" mentia, save acontecia mesmo).
- Aviso novo (severity=info) em AgendaEventDialog + V2 pra Arquivado +
futura + edit: "Sessao futura editavel; novos agendamentos e
recorrencias bloqueados". Cobria um gap onde nao havia aviso nenhum
pra esse cenario.
RESUMO FLUTUANTE acompanha o Dialog:
- ResizeObserver no .p-dialog.agenda-event-composer mede top + altura
e sincroniza com :style do aside via ref resumoStyle. Antes o aside
tinha top:5vh fixo — dialog baixo (Bloqueio/Atividade) centrava
vertical e o resumo ficava preso la em cima desalinhado.
CARD "Campos Extras (compromisso)":
- Bloco de selectedCommitmentFields extraido da fields-grid pra um
.field-card proprio com header pi-list + titulo + .aed-extras-body
(padding 0.85rem). Hierarquia visual clara: campos do compromisso
ficam isolados dos campos do form principal.
- Bind especial pra f.key==='notes': v-model="form.observacoes" em vez
de form.extra_fields.notes. Mantem compat com a coluna nativa
agenda_eventos.observacoes (consumida por relatorios/prontuario).
- Textareas hardcoded de Observacao removidas do form (fields-grid +
side-card direito) — agora vem como campo extra do commitment Sessao,
via migration 20260511000001 (commit anterior).
OUTROS:
- Card "Pagamento" renomeado pra "Sessao / Honorarios" (cobre os 3
tipos: gratuito, particular, convenio — terminologia mais alinhada
ao vocabulario clinico).
- composer-grid e composer-right ganharam gap:0 — os cards filhos
ja tem mb-4 proprio (Tailwind ~1rem), gap do flex duplicava.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migration 20260511000001 adiciona campo 'notes' (Observacao, textarea,
sort_order=30) como campo extra default no commitment determinado 'Sessao'.
Antes Sessao era a unica excecao entre os nativos — Leitura/Supervisao/
Aula/Analise ja tinham. Padroniza pra que a Observacao da sessao siga o
mesmo mecanismo de extra_fields dos outros, e o frontend remova a textarea
hardcoded do AgendaEventDialog (proximo commit).
Backfill: insere 'notes' em TODOS os commitments Sessao ja existentes
(idempotente). Forward-fix: substitui a funcao seed_determined_commitments
incluindo o bloco de Sessao + 'notes' pra novos tenants.
Schema regenerado via db.cjs schema-export pra refletir o estado pos-
migration. agenciapsi-db-dashboard.html regenerado pelo
generate-dashboard.cjs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nova pagina [[recorrencia-agenda]] cobrindo: modelo "1 real + N-1 virtual"
via useRecurrence, quem expande virtuais (composables corrigidos em 39cf017),
pattern de materializacao ao mudar status (4 caminhos), view listAll de
2 anos no MelissaAgenda, visual de evento inativo, e query SQL pra detectar
rows orfas. index.md ganhou link sob Concepts.
Log entry da sessao 2026-05-11 10:50 cobrindo os 6 commits previos
(8b0e633..39cf017): time picker, services nome unico, paciente arquivado/
inativo, AgendaEventDialog overhaul, view lista Melissa, expansao+
materializacao de recorrencia.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PROBLEMA 1 — recorrencias virtuais nao apareciam em listas de sessao
============================================================
Sistema cria 1 row real em agenda_eventos por recorrencia (a primeira
ocorrencia) + 1 regra em recurrence_rules. As N-1 sessoes seguintes sao
geradas em runtime via useRecurrence.loadAndExpand. AgendaTerapeutaPage e
AgendaClinicaPage ja usavam loadAndExpand, mas composables compartilhados
("Hoje", widget, prontuario, ver todas) so liam agenda_eventos direto —
serie semanal de 4 sessoes aparecia como 1.
Fix em 3 composables cross-layout:
- usePatientSessions.load — range padrao -6mo a +12mo, filtra virtuais
por patient_id apos loadAndExpand. Cobre MelissaPaciente Tab Agenda +
PatientProntuario legacy.
- useMelissaEventos._fetchRange — merge real + virtual no range visivel.
Cobre widget "Hoje" (MelissaLayout), mini-cal, fallback standalone do
MelissaAgenda. Falha do expand cai silencioso pra so-reais.
- useMelissaTodasSessoesPaciente.fetch — mesma logica do paciente, range
-6mo a +12mo. Cobre "Ver todas as sessoes" do MelissaAgenda.
normalizeEvent agora aceita shape de virtual (paciente_nome/patient_name)
alem de joined query (patients.nome_completo). Expoe is_occurrence +
recurrence_id pra consumidores diferenciarem.
PROBLEMA 2 — UPDATE em id virtual quebra com "invalid input syntax for type uuid"
============================================================
Apos #1, ocorrencias virtuais aparecem na UI. Quando o user mudava status
(via botoes do MelissaEventoPanel, watcher do form.status no
AgendaEventDialog, ou botoes diretos no MelissaPaciente Tab Agenda), o
UPDATE caia direto no PostgreSQL com id "rec::ruleId::date" — sintaxe
invalida pra coluna UUID.
Materializacao em 4 caminhos:
- usePatientSessions.updateStatus(sessionOrId, status) — aceita row inteira
agora. Se virtual, busca row real por recurrence_id+date, ou cria nova
copiando campos da virtual (com status aplicado).
- useAgendaEventActions watcher do form.status — emit('updateSeriesEvent',
{ ..., row: form }) em vez de UPDATE direto. Parent materializa.
- MelissaLayout.updateEventoStatus — detecta virtual, delega pro
M.onUpdateSeriesEvent passando row: ev (sem isso, dialogEventRow ficaria
vazio porque user nao abriu o dialog antes — criava row orfa sem
patient_id).
- MelissaPaciente — @updateSeriesEvent do dialog local aponta pro
onSessaoDialogUpdateSeries (wrapper que delega pro composable que sabe
materializar). Antes apontava pro save normal.
useMelissaAgenda.onUpdateSeriesEvent atualizado:
- aceita row opcional do chamador (prioridade > dialogEventRow > vazio).
- guard: aborta com toast se rid (recurrence_id) for null, em vez de
criar row orfa.
- error check no .maybeSingle (antes ignorado — query falhando seguia pro
insert e duplicava sessoes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
View lista: 'listWeek' -> custom 'listAll' (duration { years: 2 },
centrada em hoje via gotoDate(hoje - 1 ano) no setView). Antes a lista
mostrava so 7 dias e ocultava 3 das 4 ocorrencias semanais — agora cobre
passado + presente + futuro numa varredura. Cap MAX_RANGE_DAYS=730 do
loadAndExpand bate exato com 2 anos.
Banner showRecurrenceHint: aparece quando ha virtuais visiveis em
day/week/month (nao mostra em listAll). Texto curto + botao "Ver na
lista" que chama setView('lista').
Sticky day header (.fc-list-day): adicionado position:relative + z-index 3
+ bg opaco. Sem isso, .fc-event passava POR CIMA do header conforme
scroll (stacking context da row de evento ganhava do cushion sem z-index).
View selector: botoes manuais (.ma-cal__view-btn) -> PrimeVue SelectButton.
Visual herdado do tema, menos CSS custom.
Visual evento inativo: classNames=['ma-evt--inactive-patient'] em fcEvents
quando paciente_status === 'Arquivado'|'Inativo'. CSS aplica borda
tracejada + opacidade 0.58 (italico em list view). Mantem a cor do
commitment pra preservar contexto.
FC touch defaults: adiciona spread de FC_TOUCH_DEFAULTS (utility commitada
antes) — paridade touch <-> mouse, tap dispara select na hora em vez de
exigir long-press de 1000ms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picker de paciente: button list -> DataTable (.aed-patient-dt) com
rowClass condicional pra blocked + Tags Arquivado/Inativo + ordenacao
Ativo > Inativo > Arquivado. Pareia com selectPaciente do composable
(commit anterior).
Time picker: header com header-dot colorido + titulo dinamico
"Nova {commitment.name}" + subtitulo "Inicio da sessao e duracao"
(espelha o header do dialog principal). Inicio e Termino lado a lado
(Termino readonly, derivado de fimDateTime). Cards "Horarios disponiveis"
(.aed-card) + chips de duracao rapida (.aed-pill, 30/50/60/90m) + Select
"Outra" pareando com AgendaEventDialogV2. Card de Termino destacado
embaixo da Duracao removido (info ja vai no input ao lado do Inicio).
Mini calendar (.mc-mini) estilo MelissaAgenda mini-cal — grid 6x7, sem
dots/feriados, sync com form.dia ao abrir.
Cadastro completo inline: importa PatientCadastroDialog dentro do dialog
em vez de redirecionar pra rota nova-aba (vazaria do layout Melissa).
Botao pi-id-card no patient-hero abre. Usa prop hideViewListButton
adicionada antes pra esconder "Salvar e ver pacientes".
Popovers de ajuda nos InputGroups do card Pagamento (servico/convenio
help refs separados pra nao colidir).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AgendaEventDialogV2.filteredPatients agora mostra TODOS os pacientes
(antes filtrava status='Ativo' silenciosamente), ordenados Ativo > Inativo
> Arquivado. Items nao-Ativo vem com Tag colorida + disabled + tooltip
explicativo — UX clara: o paciente aparece (user nao "perde" no search)
mas nao da pra agendar.
selectPaciente bloqueia non-Ativo (defesa em camadas: template ja marca
disabled, mas se alguem chamar a funcao programaticamente por cache stale
etc, a regra continua valendo). Copia status pro form pra canSave aplicar
getPatientAgendaPermissions corretamente.
3 specs novas em useAgendaEventPickerBilling.spec cobrem o bloqueio +
copia do status.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
services: useServices.save e ServiceQuickCreateDialog agora validam nome
unico por owner (ilike, case-insensitive; ignora self no update). Antes
era possivel criar dois servicos com nome igual via paths diferentes.
cadastro in-flow: ComponentCadastroRapido e PatientCadastroDialog ganham
prop hideViewListButton. Quando true (uso dentro de outro fluxo, ex:
cadastrar paciente direto no AgendaEventDialog), esconde "Salvar e ver
pacientes" — navegar pra lista abandonaria o evento em edicao.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sem long-press delays customizados, tap em slot vazio precisa de 1000ms
antes de disparar select — diverge totalmente do mouse (clique abre na
hora). Mesmo problema em eventDrop. Move pra utils/fcDefaults.js e
aplica nos 4 calendars (AgendaCalendar, AgendaClinicMosaic,
AgendaTerapeutaPage, MelissaAgenda no proximo commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reescreve HANDOFF.md com:
- Status final: 24 commits no branch, working tree limpa
- Historico completo dos commits (mais recente -> mais antigo)
- Lista de arquivos novos/modificados (composables, utils, paginas)
- Pendentes pra proxima sessao
- 5 decisoes arquiteturais documentadas
- Hotspots de drift no AgendaEventDialog
- Comandos uteis pra retomar
Adiciona entry no log.md descrevendo a iteracao pos-Fase 8 (16 commits
de UX/funcionalidades novas + debugging do AgendaEventDialog reuse).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User: "Botao pra salvar nao esta aparecendo".
CAUSA: o footer com botao Salvar tem v-if="step === 2". O lifecycle
do composer (linha 359 do useAgendaEventLifecycle) decide step inicial
assim:
if (composer.isEdit.value) step.value = 2;
else if (props.presetCommitmentId) {
composer.form.value.commitment_id = preset;
composer.step.value = 2;
} else step.value = 1;
Eu setava determined_commitment_id no eventRow (que populava
form.commitment_id via resetForm), mas NAO passava props.presetCommitmentId.
Resultado: lifecycle ia pra step=1 (escolha de tipo). E como lockType=true
escondia o conteudo do step 1 com v-if, o dialog ficava com Body vazio
+ footer step=2 nao renderizando.
FIX: passar :preset-commitment-id="sessaoDialogEventRow?.determined_commitment_id".
Como ja resolvo o id do commitment "Sessão" no goAgendar, reuso aqui
direto sem ter que duplicar o lookup.
Resultado: dialog abre direto em step=2, footer aparece, botao Salvar
visivel (com :disabled="!canSave" — ainda exige paciente_id +
items/billing valido, comportamento normal).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User detectou bug: jornada/freq/billing continuavam ocultos mesmo apos
o fix do commit anterior, e o resumo lateral nao mostrava o nome do
paciente apesar de aparecer no subtitle do header. Diagnostico correto:
form.paciente_nome estava vazio.
CAUSA: meu watch lockType (commit 73788c7) chamava selectCommitment
APOS o lifecycle watcher do composer rodar resetForm(). Mas resetForm
le do props.eventRow — e eu so passava paciente_id + tipo. Sem
paciente_nome/avatar/status no eventRow, o form ficava com paciente_id
solto e nome vazio. E sem determined_commitment_id, o lifecycle setava
step=1 antes do meu watch tentar consertar via selectCommitment, gerando
race condition (lifecycle await nextTick + resetForm DESFAZIA o trabalho
do watch sync).
FIX em goAgendar() do MelissaPaciente:
1. Acha o commitment "Sessão" (native_key='session') em
melissaAgenda.commitmentOptions e pre-popula determined_commitment_id
no eventRow. resetForm le isso e ja deixa form.commitment_id setado
na inicializacao — isSessionEvent fica true imediatamente, sem
precisar do watch lockType.
2. Pre-popula paciente_nome/avatar/status no eventRow direto dos
computeds (nomeCompleto, avatarUrl, statusPaciente) que ja existem
no MelissaPaciente desde a Fase 3. Composer s o faz fetch async de
nome quando isEdit=true — pra criacao precisa vir no eventRow.
Resultado: dialog abre ja com:
- paciente_id + nome + avatar + status preenchidos no resumo lateral
- commitment_id setado, isSessionEvent=true
- Jornada de trabalho aparece
- Billing radio (particular/convenio/gratuito) aparece
- Frequencia aparece
O watch lockType continua valido como redundancia (caso commitmentOptions
chegue async), mas agora nao e mais o caminho principal.
301 specs passando. ESLint 0 errors da minha mudanca.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User apontou que jornada de trabalho, frequencia e billing (particular/
convenio/gratuito) sumiam quando o dialog abria do prontuario. Causa:
meu watch original do commit 30d09eb so forcava step.value=2 sem
inicializar form.commitment_id. Sem commitment, o computed
isSessionEvent virava false e esses 3 blocos do template (que dependem
de isSessionEvent) ficavam ocultos:
- jornadaDialog: <Message v-if="jornadaDialog && isSessionEvent">
- frequencia: bloco v-if="!hasSerie" tem gates internos de billing/
patient que dependem de isSessionEvent
- billing radio (particular/convenio/gratuito): isSessionEvent
FIX: watch agora chama selectCommitment(sessao) — exatamente o que o
user faria clicando no card "Sessão" no step 1. Isso seta:
- form.commitment_id pro id do native_key='session'
- form.extra_fields = {} populado pelos fields do commitment
- step.value = 2
Adicionei props.commitmentOptions ao watch dep — necessario pq quando
o dialog abre antes do tenant load terminar, commitmentOptions chega
vazio inicialmente. Watch re-roda quando popula.
Idempotente: so chama selectCommitment se ainda nao tem commitment_id
ou se id atual nao bate com sessao.id (re-open com mesmo lockType
nao reinicializa redundantemente).
301 specs do agenda continuam passando. ESLint: 31 errors pre-existentes
(mesmos do commit anterior).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User escolheu caminho A: modificar AgendaEventDialog em vez de copiar.
Mudancas SAO ADITIVAS — comportamento atual dos 5 callsites legacy
(TherapistDashboard, PatientsListPage, MelissaAgenda,
MelissaAgendamentosRecebidos, MelissaLayout) preservado.
VALIDACAO: rodei os 7 spec files do agenda — 301 testes passaram.
Zero regressao.
ADICIONADO em src/features/agenda/components/AgendaEventDialog.vue
- Prop lockType (Boolean, default false): pula step 1 (escolha de tipo)
e vai direto pro form. Watch immediate em [lockType, modelValue]
forca step.value=2 quando lockType=true e dialog abre.
- Prop lockPatient (Boolean, default false): esconde botoes "trocar"/
"limpar" do paciente. Mostra icon de lock com tooltip "Paciente do
prontuario". Cobre o cenario "criar sessao pra paciente fixo" sem
precisar do isEdit que o patientLocked computed exige.
- Slot #headerLeft: substitui o conteudo esquerdo do header (default
era header-dot + headerTitle + previewRange). Permite callsites
customizar com icon+title+subtitle proprios sem mexer no resto do
header (X / actions).
- v-if no Step 1: "step === 1 && !lockType"
- v-if nos buttons trocar/limpar: "!patientLocked && !lockPatient"
- Lock icon: "patientLocked || lockPatient" + tooltip dinamico
MELISSAPACIENTE.VUE
- Reverte o inject-only do commit 88dff50.
- Mantem o inject(MELISSA_AGENDA_KEY) APENAS pra LER dados pesados
(commitmentOptions, workRules, allEvents, agendaSettings, feriados,
ownerId, tenantId) — evita re-fetch.
- State LOCAL pro dialog: sessaoDialogOpen, sessaoDialogEventRow,
sessaoDialogStartISO, sessaoDialogEndISO. Nao colide com o dialog
global do MelissaLayout que continua na Agenda.
- goAgendar(): inicializa eventRow com paciente_id fixo + tipo='sessao'
+ defaults razoaveis (proximo slot 15min + duracao da agenda),
abre o dialog local.
- Handlers onSessaoDialogSave / onSessaoDialogDelete delegam pros
handlers globais (M.onDialogSave/Delete) e ao final refetcham
sessions+recorrencias do paciente in-place.
- Render <AgendaEventDialog> com lock-type=true + lock-patient=true
+ slot #headerLeft custom (icon pi-calendar-plus em quadrado
primary 40x40 + "Nova sessão" + nome do paciente como subtitulo).
Resultado: prontuario tem o MESMO componente da Agenda (form completo
de sessao, frequencia com preview de ocorrencias + conflitos,
vinculacao de servicos/billing, edicao de serie, etc) mas pre-fixado
no contexto do paciente, com header proprio e single source of truth.
ESLint: 31 errors pre-existentes em ambos arquivos (variaveis declaradas
nao usadas — confirmado via git stash baseline). 0 errors da minha
mudanca.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>