Commit Graph

4 Commits

Author SHA1 Message Date
Leonardo a7f6bcbe66 F3 schema-per-tenant: frontend usa tenantDb() pra tabelas tenant
- useTenantDb composable + lib/supabase/tenantClient (tenantDb/tenantSchemaName)
- tenantStore: getters activeTenantSlug/activeTenantSchema; my_tenants() RPC
  passa a devolver slug+nome (migration 07)
- codemod scripts/codemod-tenant-db.py: supabase.from('<84 tabelas + 6 views
  tenant>') -> tenantDb().from(...) em 139 arquivos (777 chamadas), remove
  .eq('tenant_id') das cadeias tenant (173)
- passada manual (4 agentes): remove tenant_id de payloads insert/upsert/update,
  selects, .or/.is de defaults; onConflict ajustado pros uniques sem tenant_id
  (singletons usam 'singleton'); realtime de tabelas tenant aponta pro schema
  do tenant ativo; repos dropam tenant_id defensivamente de payloads externos
- agendaSelects: tenant_id fora do AGENDA_EVENT_SELECT (quebraria PostgREST)
- zero embeds cross-schema (todos FK embeds sao tenant->tenant ou global->global)
- build de producao passa; 67 .js checados

Pendente (fora do escopo F3, sao cross-tenant/anon -> F4/F6):
- AgendadorPublicoPage (anon, resolve tenant por link_slug)
- Saas{Feriados,NotificationTemplates,DocumentTemplates,Whatsapp}Page
  (gerenciam defaults do sistema / views cross-tenant)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 04:44:59 -03:00
Leonardo e95ed9b585 agenda: Fase 5 (status change/edit cobrada) + indicadores visuais + UX convenio
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>
2026-05-19 08:31:18 -03:00
Leonardo 39cf0178e6 agenda: expandir e materializar ocorrencias de recorrencia (cross-layout)
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>
2026-05-11 10:46:58 -03:00
Leonardo 6d9b36d592 A66 WIP: AgendaEventDialog quebrado em 5 composables + 265 specs + V2 esqueleto
Sub-sessao 1 entregue (composables):
- agendaEventHelpers (262L) — utilitarios puros (date, format, parse)
- useAgendaEventComposer (485L) — montagem do form + validacao
- useAgendaEventActions (387L) — save/delete/cancel/move actions
- useAgendaEventPickerBilling (378L) — pickers (terapeuta, servico,
  convenio) + calculo de billing
- useAgendaEventLifecycle (474L) — open/close/dirty state + autosave
- 5 specs em __tests__/ (75+76+28+43+43 = 265 testes), 495/495 passing

AgendaEventDialog: 3522 -> 2632 linhas (-25%) consumindo os composables.
Backup byte-identico em AgendaEventDialog.vue.bak pra rollback.

Sub-sessao 2 entregue (esqueleto, NAO TESTADO):
- AgendaEventDialogV2 (~1100L, 3 zonas: PACIENTE/QUANDO/O QUE)
- Preview em /preview/agenda-dialog-v2 com 5 cenarios
- Rota em routes.misc.js
- User testou e nao gostou do design — aguarda feedback especifico
  pra iteracao na sub-sessao 3 (migracao nos 9 consumers).

Dialogs auxiliares novos pro AgendaEventDialog:
- InsurancePlanQuickCreateDialog (criar convenio inline)
- ServiceQuickCreateDialog (criar tipo de sessao inline)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 09:13:22 -03:00