ROADMAP item #1.4 #14. Fecha Fase 1.4 Fiscal minimo (parcial — #15
NFS-e fica pra depois).
DocumentGenerate.service estendido:
- loadTherapistData puxa registro profissional (#5 migration) e
expoe terapeuta_registro auto-formatado ("CRP 12345/SP", "CRM
98765/RJ"). terapeuta_crp legacy mantido por compat — preenche
somente quando tipo=CRP.
- loadClinicData formata tenants.cpf_cnpj (11 ou 14 digitos) em
CPF (XXX.XXX.XXX-XX) ou CNPJ (XX.XXX.XXX/XXXX-XX).
- loadAllVariables aceita {extras} (valor, formaPagamento) e
computa valor_extenso via novo helper utils/valorExtenso.js
(pt-BR completo ate 999 milhoes).
- saveGeneratedDocument ganha templateTipo + usa
TEMPLATE_TYPE_TO_DOC_TYPE mapping (recibo_pagamento -> 'recibo',
laudo -> 'laudo', atestado -> 'atestado' etc) em vez de
hardcoded 'laudo'.
- emitirReciboParaSessao(eventoId, opts) — quick path one-call:
busca template recibo_pagamento global, carrega variaveis,
gera PDF blob, salva no Storage + documents + document_generated,
dispara download.
Migration 20260521000008 substitui no template recibo_pagamento
"Psicologo(a) - CRP {{terapeuta_crp}}" por "{{terapeuta_registro}}"
e atualiza variaveis[]. Universal — funciona com qualquer conselho
(CRP/CRM/CRFa/CREFITO/CRESS/CRN).
DocumentTemplates.service.TEMPLATE_VARIABLES ganha terapeuta_
registro + _tipo + _numero + _uf (terapeuta_crp marcado legacy).
useDocumentGenerate.generateAndSave passa templateTipo no save.
AgendaEventoFinanceiroPanel ganha botao "Emitir recibo" (icon
pi-file-pdf, outlined, full width) que aparece SOMENTE quando
record.status === 'paid'. Toast de sucesso/erro. Loading state.
Fluxo end-to-end: terapeuta marca sessao como paga -> botao
"Emitir recibo" aparece -> click -> PDF baixado + aparece em
/clinic/documents/templates do paciente como tipo 'recibo'.
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>
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>