Commit Graph

242 Commits

Author SHA1 Message Date
Leonardo ee82985dc3 F6.2 Lote G: funcoes SQL puras -> plpgsql + roteamento (completa F6.2)
DB (supabase_admin, manual/f6_2g_sql_to_plpgsql.supabase_admin.sql): SQL puro
nao permite set_config dinamico -> converte 5 pra plpgsql + p_tenant_id +
_tenant_route:
- get_financial_summary, get_financial_report, get_patient_session_counts
  (remove filtro tenant_id IN, schema-scoped)
- list_financial_records: RETURNS SETOF financial_records -> jsonb (array,
  transparente no FE)
- get_entity_primary_phone (interno, 0 callers): herda search_path do chamador
  (sem SET, unqualified)
Smoke: get_financial_summary + list_financial_records (array 5) OK.

Frontend (3 arquivos): p_tenant_id de activeTenantId/resolveTenantId nas
chamadas de get_financial_summary/list_financial_records/get_patient_session_
counts. Build passa.

=== F6.2 COMPLETA: 66 funcoes migradas (triggers A/B/C + RPCs D/E/F/G) ===

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 16:00:16 -03:00
Leonardo d240c6678f F6.2 Lote D: RPCs user-facing roteadas pro schema do tenant
DB (supabase_admin, manual/f6_2d_user_rpcs.supabase_admin.sql): 14 RPCs.
Helper _tenant_route(p_tenant_id) valida is_tenant_member + retorna schema
(retorna, nao seta — set_config em helper com SET search_path proprio seria
revertido na saida). Cada RPC: set_config search_path pro schema + unqualify
tabelas tenant + remove WHERE tenant_id= e tenant_id de inserts.
- Grupo 1 (ja tinham p_tenant_id, jsonb/void): delete_commitment_full,
  delete_determined_commitment, seed_default_patient_groups,
  seed_determined_commitments (no-op se schema nao existe)
- Grupo 2 (novo p_tenant_id 1o param, DROP+CREATE): cancel_recurrence_from,
  cancelar_eventos_serie, split_recurrence_at, safe_delete_patient,
  export_patient_data (audit_logs global mantido), search_global
  (patient_intake_requests fica em public/F1b -> qualificado + filtro tenant_id)
- Grupo 3 (RETURNS <tabela>->jsonb): mark_as_paid, create_financial_record_
  for_session, mark_payout_as_paid, create_therapist_payout
- can_delete_patient: unqualified, herda search_path do chamador
Smoke: mark_as_paid (status=paid, jsonb) + search_global (acha paciente) OK.

Frontend (18 sites): p_tenant_id de useTenantStore().activeTenantId (ou helper
local resolveTenantId/currentTenantId). create_financial_record_for_session ja
passava tenant; retorno SETOF->jsonb transparente (nenhum consumidor indexava
array). Build passa.

list_my_signatures (cross-tenant) -> Lote F.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 15:13:16 -03:00
Leonardo f17e9ee786 F1b: 6 tabelas anon-facing ficam em public (decisao roteamento anon)
Fluxos anon identificam tenant por token/slug e nao resolvem o schema fisico.
Decisao (opcao C): manter em public com RLS por token. Volta a global:
patient_intake_requests, patient_invites, patient_invite_attempts,
document_share_links, agendador_configuracoes, agendador_solicitacoes.

- migration 20260613000001_f1b: remove as 6 do _tenant_template (template v2,
  78 tabelas). Smoke: clone gera 78, zero tabelas anon no schema, drop limpo
- frontend: 38 cadeias em 14 arquivos revertidas tenantDb().from() ->
  supabase.from() com tenant_id/owner_id restaurado (via comparacao com main)
- edge: convert-abandoned-intakes restaurada do main (SELECT global)
- save-intake-progress: ja usava public, sem mudanca
- doc F0 atualizado: 78 tenant + 59 global

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 09:09:46 -03:00
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 89bf181742 melissa/paciente-docs: wire-up preview actions + Editar abre dialog em modo edicao
DocumentPreviewDialog emitia @download/@edit/@share/@sign/@delete que
o MelissaPatientDocuments nao ouvia — os 5 botoes da sidebar do preview
caiam no vazio. Adicionado wire-up roteando pros mesmos handlers do
card (onDownload, onEdit, onShare, onSign, onDelete). Share/sign/delete
fecham o preview antes de abrir o proprio dialog pra UX limpa; download
mantem preview aberto (acao instantanea).

DocumentGenerateDialog ganha prop editing-doc-id. Quando setado:
- Busca template_id + dados_preenchidos via loadGeneratedFromDocId
- Pre-seleciona template, popula vars (sobrescreve auto-loaded vars
  com dados_preenchidos pra preservar customizacao anterior)
- Pula direto pra step 'edit'
- Save vira UPDATE in-place (preserva documents.id e audit trail)
- Header muda pra "Editar documento" + icone pi-pencil amber
- Botao final vira "Substituir documento"
- Doc sem registro generated (legado): toast info + flow normal de
  select template; ao salvar, cria o registro generated linkado.

MelissaPatientDocuments:
- onEdit substituido (era shortcut pra onPreview): abre generate dialog
  com editing-doc-id setado.
- Novo ref editingDoc dedicado (separado do selectedDoc que serve
  preview/share/sign/delete) pra evitar vazar "edit state" pro botao
  "Gerar" do header quando user so abre preview e fecha.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:42:39 -03:00
Leonardo 473e0f026e melissa/layout: topbar->tray bottom-right + dock 4 builtins + mobile collapse
Tray no canto inferior direito (substitui o topbar band do topo):
busca + plan-DEV + bell + ajuda + cog. Sibling de .melissa-dock
(fora de .win11-summary) pra ficar sempre interativo mesmo com
secao aberta (que aplica blur+pointer-none). z-index 66 (acima
do dock=65). Em <md (768px) collapse parcial — bell/help/cog/
plan-DEV somem e viram popup vertical no botao ⋮; dot vermelho
no ⋮ quando ha notificacoes nao lidas. Search sempre visivel.

Dock: 4 builtins na ordem Agenda · Pacientes · WhatsApp · Financeiro
(antes so Agenda+WhatsApp). MRU (max 3) ganha @media (max-width:
767px) display:none — utility 'hidden' do Tailwind perdia pro
.dock-pin{display:grid} por ordem de carga. Divisor entre builtins
e pins user some em mobile se so houver MRU (que ja esta oculto).

Wire-ups das commits anteriores:
- ref melissaBuscaRef + provide('openMelissaBusca') pra acoes
  contextuais futuras (botao tray chama direto via ref)
- @goto-date no <MelissaBusca> -> onBuscaGotoDate via _callOnAgenda
- @iniciar-cronometro no <MelissaTimelineHoje> -> handler que abre
  o cronometro com sessionPlan + autostart; opcao (b) "ja ativo"
  mostra toast warn sem trocar paciente
- Card "Proximo paciente" troca CTA pra "Iniciar cronometro" quando
  emCurso E tem patient_id; @open chama o mesmo handler do timeline

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:41:45 -03:00
Leonardo 9f3a047d6d melissa/cronometro: pre-selecionar paciente + sessionPlan + confirm fechar
MelissaCronometro.abrir() agora aceita opts { pacienteId, autostart,
sessionPlan }. Retorna { opened, alreadyRunning, samePaciente, ... }
pra caller decidir o feedback. Estado sessionPlan { startH, endH }
exibe "Programado: HH:MM – HH:MM" sob o select + badge laranja
"atrasada Xmin" quando hNow > startH. Cronometro NAO auto-ajusta —
analista decide quando comecar/parar. Tick a 30s atualiza atraso.
sessionPlan persiste no localStorage junto com o snapshot.

X agora dispara confirmarFechar(): pede ConfirmDialog quando ha
sessao em andamento OU tempo decorrido nao salvo; fecha direto se
clean. Tooltip mudou pra "Encerrar sem salvar".

Chip minimizado: nome do paciente fica display:none em <md (mobile)
pra nao estourar largura do dock — icone + timer cobrem o essencial.

MelissaTimelineHoje: botao ⏱ overlay no canto sup. direito das pills
(horizontal + vertical) quando ev esta em curso E tem patient_id.
Pulso emerald sutil pra chamar atencao; @click.stop pra nao abrir
o evento. Novo emit iniciar-cronometro(ev).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:41:27 -03:00
Leonardo 8bf992910d melissa/busca-global: 'Ir para [data]' + Ctrl+K unificado
MelissaBusca ganha parser de data ('hoje', 'amanha', 'ontem',
DD/MM/YYYY) e card destacado azul "Ir para [data]" como primeiro
item do flatList. Quando query parseia como data, pula a RPC
search_global (nao busca paciente com nome '20/06'). Enter sem
selecao explicita pega o primeiro item — UX spotlight padrao.

Novo emit goto-date(date) capturado em MelissaLayout via helper
_callOnAgenda que abre a agenda se fechada e chama gotoDate exposto
pela MelissaAgenda (alias pro onBuscaGotoDate existente).

MelissaAgenda perde o popover proprio (MelissaAgendaSearchPopover
deletado), o ref searchPopover, o hotkey Ctrl+K local e
onBuscaSelectEvento. Ctrl+K agora vive so na MelissaBusca — evita
dois listeners no mesmo atalho. MelissaBusca expoe openDialog via
defineExpose pra a lupa do tray chamar.

MelissaPacientes: comment update mencionando o tray.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:41:12 -03:00
Leonardo fa2b431a56 melissa/hero: contagem 'cancelado/remarcado' no resumo do dia
Acrescenta sufixo "(x foi cancelado, x foi remarcado)" depois do chip
de atendimentos quando ha sessoes nesses status em eventosHojeReais.
Sufixo nao-clicavel, peso menor pra nao competir com o link do total.
Pluralizacao gramatical (1 foi / 2+ foram).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:41:00 -03:00
Leonardo eb42759979 melissa/settings+ajuda: fechar ao clicar fora
Popover Personalizar (cog) e drawer de Ajuda agora fecham quando o
user clica em qualquer lugar fora do panel. Listener mousedown em
capture, watch em open pra anexar/desanexar; ignora o proprio botao
trigger (data-ajuda-toggle pro ajuda; cogBtnEl ref pro settings) pra
nao fazer close+reopen. Tambem flipa o panel do settings de top-12
pra bottom-12 (cog agora vive no bottom da .melissa-tray).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:40:48 -03:00
Leonardo 6c39c58dc8 melissa/paciente-docs: drawer mobile herda tema (win11-root no portal)
Drawer teleportado pro body perdia as vars --m-* (definidas em
.win11-root no MelissaLayout), caia nos fallbacks hardcoded (#1a1d2e)
e ficava mais escuro que o resto do tema.

Fix:
- Wrapper .mpd-drawer-portal recebe class win11-root pra trazer as
  vars --m-* pro escopo teleportado.
- Vars locais --mpd-bg/--mpd-border/--mpd-text com cascata:
  --m-* (win11-root) -> --p-* (PrimeVue global) -> hardcoded.
  Respeita dark/light automaticamente.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:54:37 -03:00
Leonardo 4e1ebeba13 melissa/paciente-docs: fix drawer mobile (teleport body + style global)
Bug: drawer abria mas travava — sidebar interna nao aparecia.
Duas causas combinadas:

1. position:fixed preso em stacking context: o MelissaPaciente
   tem transform/filter num ancestral, fazendo o fixed virar
   relativo ao pai em vez da viewport.
2. Styles scoped: ate corrigir o stacking context, ao teleportar
   pro body os data-v scoped attrs sumiriam e o CSS nao aplicaria.

Fix:
- <Teleport to="body"> wrap nos elementos drawer + backdrop. Saem
  da arvore do componente e ficam no body raiz.
- Styles do drawer movidos pra um <style> NAO-scoped no fim do
  arquivo. Classes globais .mpd-mobile-drawer* garantem que
  aplique nos elementos teleportados (que perdem data-v).
- Fallbacks adicionados nas vars CSS (--m-bg-medium, --m-border,
  --m-text) caso o body nao tenha o tema melissa carregado.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:48:50 -03:00
Leonardo 51c33e73b9 melissa/paciente: aba Documentos vira pagina nativa 2-col
Antes: <DocumentsListPage embedded /> reusava o componente do
Rail/Classic em modo embed — visual conflitava com o padrao
Melissa, sem agrupamento por tipo, scroll inconsistente.

Novo: MelissaPatientDocuments.vue (componente nativo 2-col
seguindo MelissaDocumentosTemplates):
- Sidebar esquerda: tipos de documento com contadores
  (Todos, Laudo, Receita, Exame, Termo assinado, Relatorio
  externo, Identidade, Convenio, Declaracao, Atestado,
  Recibo, Outro). Item ativo destaca primary; vazios em
  opacity 50%.
- Main direita: header com titulo do tipo + count, DataView
  com cards (DocumentCard reusado), paginacao automatica >12,
  empty states distintos (global vs filtrado).
- Header da pagina: botoes Refresh / Gerar / Upload (primary
  outlined no dark-friendly).
- Mobile <1024px: sidebar vira drawer com botao "Tipos" no
  header (espelha padrao MelissaBloqueios/Templates).

Reaproveita do features/documents:
- useDocuments composable
- DocumentCard, DocumentUploadDialog, DocumentPreviewDialog,
  DocumentGenerateDialog, DocumentSignatureDialog,
  DocumentShareDialog

MelissaPaciente.vue: import DocumentsListPage -> Melissa
PatientDocuments + uso na aba.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:44:44 -03:00
Leonardo c6105df98a melissa/templates: count badges usam .mdt-page__count (consistencia + fix dark)
Badges .mdt-section__count.is-info e .is-accent tinham o mesmo
problema do botao primary: bg solido com texto branco/cor primary
quebrava o contraste no modo escuro (texto sumia).

Trocados pelo .mdt-page__count (mesmo estilo do badge no header
da pagina) — usa var(--m-accent-soft) que adapta ao tema.

Tambem removido o CSS .mdt-section__count (e .is-info / .is-accent)
que ficou orfao.

Visual: numero do contador (17 globais, N tenant) com o mesmo
estilo do "17" no header — consistencia visual + dark mode safe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:11:28 -03:00
Leonardo 402def7539 melissa/templates: botoes primary viram outlined (fix dark mode)
Bug: no modo escuro o bg primary do botao --primary tornava o
texto branco ilegivel — cor primary clara contra fundo claro.

Fix: estilo outlined em vez de filled:
- background transparente
- border-color: var(--p-primary-color)
- color: var(--p-primary-color)
- hover: bg sutil 10% mix com primary

Mantem hierarquia visual (a borda destacada sinaliza acao primaria)
mas sem o conflito de contraste. Funciona em ambos os temas.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:09:49 -03:00
Leonardo 5dc91614ad melissa/templates: titulo do card quebra em ate 3 linhas
.mdt-card__name: -webkit-line-clamp 1 -> 3 + word-break:break-word
+ line-height 1.3. Nomes longos (ex: "Termo de Consentimento Livre
e Esclarecido para Atendimento Online") cabem inteiros em ate 3
linhas, com elipses no final se passar.

.mdt-card max-height: 200px -> 240px pra acomodar o titulo mais
alto + tipo + descricao (2 linhas) + footer com variaveis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:07:43 -03:00
Leonardo 597f8c05d5 melissa/templates: remove ConfirmDialog duplicado (toast 2x bug)
Bug: duplicar template disparava toast 2x e criava 2 copias.
Causa: MelissaLayout ja monta <ConfirmDialog /> global. Quando
MelissaDocumentosTemplates tambem montava, o confirm.require()
do PrimeVue dispara em TODOS os ConfirmDialog ativos -> callback
do accept executa 2x.

Fix: remove o <ConfirmDialog /> local de MelissaDocumentosTemplates.
O global do MelissaLayout cobre tudo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:02:25 -03:00
Leonardo bbbb08ba9d melissa/templates: drawer mobile com templates do sistema
Mobile (<1024px) agora segue padrao MelissaBloqueios:
- Coluna esquerda (Templates do sistema) eh teleportada pra um
  drawer fixed que abre via botao "Templates do sistema" no header.
- Botao .mdt-menu-btn--mobile-only substitui o titulo no mobile
  (mais legivel + acao clara).
- Backdrop escuro com blur fecha o drawer ao clicar fora.
- Auto-fecha quando o user seleciona um template (libera viewport
  pra ver o preview no main).

Script:
- drawerOpen + isMobile refs + matchMedia listener
- toggleDrawer/fecharDrawer helpers
- onMounted setup + onBeforeUnmount cleanup

Template:
- <Transition name="mdt-drawer-fade"> wrap (slide horizontal +
  fade do backdrop)
- <Teleport to="#mdt-mobile-drawer-target" :disabled="!isMobile">
  envolvendo a <aside class="mdt-side">
- Botao "Menu" no header com class mdt-menu-btn--mobile-only

CSS:
- .mdt-mobile-drawer fixed left, transform translateX, 250ms cubic
- .mdt-mobile-drawer__backdrop overlay com blur
- @media (max-width: 1023px): cols vira 1-col, sidebar inline some,
  botao menu aparece, titulo canonico some, acções viram icone-only

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:19:17 -03:00
Leonardo 17f114f32f melissa/templates: usa DataView pros templates do tenant
Substitui o <div class="mdt-grid"> v-for simples por <DataView>
do PrimeVue na coluna "Seus documentos".

Beneficios:
- Paginacao automatica quando passa de 12 templates (era scroll
  infinito virando lento)
- Slot #grid permite manter o layout de cards atual
- Footer com paginator integrado ao design (border-top + bg
  transparente)

CSS:
- .mdt-dataview flex column ocupando o main
- :deep(.p-dataview-content) flex 1 + overflow auto = scroll
  interno dos cards
- :deep(.p-dataview-paginator-bottom) flex-shrink 0 = paginator
  sempre visivel no fundo
- .mdt-main .mdt-grid passa a ter padding 12 e gap 10 (era
  herdado do .mdt-grid global)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:11:10 -03:00
Leonardo f1c24242e0 melissa/templates: ajustes UI dos cards e icones (4 fixes)
1. Icone "eye" do header de Preview -> cor primary (classe
   mdt-main__title-icon-eye).

2. Icone "ellipsis-v" (3 pontos) dos cards do tenant -> cor
   primary via :deep(.p-button-icon) selector.

3. Variaveis do card: formato "< 12 variaveis >" (entities
   HTML &lt;/&gt;) em font monospace + cor primary + bold.
   Removido o icone pi-code (a propria notacao < > sinaliza).

4. .mdt-card max-height: 200px + overflow hidden. Foot agora
   tem justify-content: center + margin-top: auto pra grudar
   no fundo do card.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:53:08 -03:00
Leonardo b821db6438 melissa/templates: fix parse error em variaveis do preview
Erro: \"Unexpected token, expected }\" em \`{{ array.map(v => \`{{...${v}}}...\`) }}\`.
Vue parser confunde os \`{{\` da template string aninhada com os
delimitadores de interpolacao Vue, abortando parse.

Fix: extrai pra helper externo formatVarsPreview(vars, max) que
monta as chaves via concatenacao de strings (open + open + v +
close + close) — sem template literal com \`{{\` literal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:47:00 -03:00
Leonardo 0fafc28581 melissa/templates: layout 2-col + preview antes de duplicar
Refatora MelissaDocumentosTemplates seguindo padrao do
MelissaAgendaConfig (2-col com sidebar). Dois ajustes pedidos:

1. Layout 2-col (mdt-cols grid 360px + 1fr):
   - COL 1 (sidebar): "Templates do sistema" — lista vertical
     compacta com nome/tipo/descricao. Click abre preview.
   - COL 2 (main): "Seus documentos" + subtitulo + grid de cards
     dos templates do tenant.
   - Empty states distintos por coluna.
   - Mobile (<900px): empilha 1-col.

2. Preview antes de duplicar:
   - View 'preview' nova (alem de list/create/edit).
   - Click num template do sistema -> view='preview' (substitui
     "Seus documentos" no main, sidebar permanece pra navegar).
   - Header da main muda: nome do template + tipo/desc + 2 botoes
     (Voltar / Duplicar).
   - Iframe sandbox=allow-same-origin renderiza HTML completo
     (cabecalho+corpo+rodape com CSS basico A4-like).
   - Footer com lista de variaveis {{...}} do template (5 +N).
   - Item ativo na sidebar destaca borda primary + opacity 1 no
     icone de visualizar.
   - Pos-duplicar: volta pra view='list' pra mostrar o novo
     template no main.

UX result: user le antes de copiar (evita lixo em "Seus documentos"
de copias que nao queria).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:42:05 -03:00
Leonardo 75e67eae5d docs/templates: move pra Configurações (3 layouts)
Templates de documentos sao "setup", nao operacao diaria — deveriam
viver em Configuracoes, nao no menu de Documentos do paciente.

Mudancas:

1. Melissa — melissaConfigGrupos.js ganha grupo "Documentos" com
   1 item "Modelos de documentos" -> slug `documentos-templates`
   (pagina nativa MelissaDocumentosTemplates ja existe + ja esta
   wired no MelissaLayout linha 2896).

2. Rail/Classic — routes.configs.js ganha rota
   /configuracoes/documentos/templates (name=ConfiguracoesDocumentos
   Templates) apontando pro mesmo DocumentTemplatesPage.vue.

3. Rotas antigas removidas — routes.therapist.js e routes.clinic.js
   nao tem mais /documents/templates nem nomes de rota
   therapist-documents-templates / admin-documents-templates.
   URLs antigas dao 404 (decisao do user — limpa).

4. ConfiguracoesPage (sidebar Rail/Classic) ganha grupo
   "Documentos" antes do "Empresa & Plataforma" com item "Modelos
   de documentos".

5. Menus de pacientes (therapist.menu + clinic.menu) NAO tem mais
   "Templates" — caminho de acesso e Configuracoes.

6. pagesIndex.js (busca global) atualizado: novo path, novos
   keywords (recibo, atestado, laudo, tcle, lgpd, consent), roles
   ['therapist','admin'].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:11:33 -03:00
Leonardo 30367392ff busca melissa: troca popover por Dialog Spotlight (CMD+K pattern)
Refatora MelissaBusca pra usar PrimeVue Dialog em vez de popover
absolute. Resolve definitivamente o bug do panel estourar viewport
quando ha muitos resultados.

Mudancas:

1. Trigger no dock: input -> <button> com aparencia de input. Clica
   ou Ctrl+K abre Dialog. Mantem placeholder + Ctrl+K kbd hint.

2. Dialog Spotlight: 640px max-width, posicionado 10vh do topo
   (estilo Spotlight macOS / Linear / GitHub). Backdrop blur escuro,
   dismissable mask, sem header, sem closable button (Esc cobre).

3. Input REAL dentro do Dialog: autofocus on open via nextTick.
   Mantem v-model="query" + @keydown="onKeydown" (Arrow/Enter).

4. Panel de resultados: era position:absolute com max-height:60vh
   (estourava em layouts com input perto do bottom). Agora vive
   DENTRO do Dialog (flex:1, max-height:70vh no content), scroll
   interno garantido por design — conteudo NUNCA passa do bottom
   da pagina.

5. Remove: onClickOutside (dismissableMask cobre), Transition
   mb-fade (Dialog tem sua animacao).

Comportamento end-user identico (Ctrl+K, navegacao com setas, Enter
seleciona, Esc fecha) mas visual + manutencao muito melhor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:52:19 -03:00
Leonardo b40116fe5d busca melissa: panel usa surface do tema (resolve branco-em-branco)
Texto branco hardcoded ficava ilegivel no tema claro do PrimeVue
(branco em branco). Tema escuro funcionava ok pq fundo era escuro.

Fix: troca cores hardcoded por CSS tokens do tema:
- mb-panel background: var(--surface-card)
- mb-panel border: var(--surface-border)
- mb-item color: var(--text-color)
- mb-item__sub color: var(--text-color-secondary)
- mb-group__title color: var(--text-color-secondary) com opacity
- mb-item hover: color-mix com p-primary-color 8%

Icones semanticos (patient pink, sessao indigo, doc sky, intake
orange) ficam mais saturados no tema claro e suavizados no escuro
via :root.app-dark selectors.

Input field do search bar mantem fallback `white` — ele fica no
shell escuro do Melissa (lockscreen-style), nao depende do tema
PrimeVue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:30:09 -03:00
Leonardo ffd8eab72d busca melissa: shape RPC + cores legíveis + cor de sessão
3 fixes pedidos no teste manual:

1. Shape errado da RPC: search_global retorna { id, label, sublabel,
   deeplink } pra TODOS os tipos, mas o codigo lia campos diretos
   (nome_completo, paciente_nome, inicio_em, nome_original etc) que
   nao existem -> resultados saiam "(sem nome)", sem datas.
   Fix: filteredPacientes + rpcAppointments + rpcDocuments + rpcIntakes
   agora usam label/sublabel direto. selectEntry extrai patient_id da
   deeplink quando precisa.

2. Cores ilegiveis: fundo do panel transparente demais (var(--m-bg-medium)
   nao tinha contraste em alguns temas). Fix: fundo solido rgba(20,22,32,
   0.92), border 14% white, text 96% white pra label, 65% pra sub
   (sobe pra 78% no hover/active). Group title 50% + bold pra hierarquia
   clara.

3. Cor das sessoes: grupo "Sessoes" tinha icone cinza generico. Fix:
   classes .mb-item__icon--{patient,sessao,doc,intake} com paleta
   espelhando a agenda — sessao = indigo-500 (#a5b4fc texto +
   rgba(99,102,241,0.20) bg, mesma cor do pickColor() padrao);
   patient = pink-400; doc = sky-500; intake = orange-400.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 12:26:24 -03:00
Leonardo dee89ccd84 registro profissional: campo livre quando tipo='outro'
Quando o profissional seleciona "Outro" no Tipo de registro, agora
aparece um campo adicional pra informar o nome do conselho/instituicao
livre (ex: APM, ABRAP, conselhos nao-listados).

Migration 20260521000009 adiciona profiles.professional_registration_
type_other (text livre). Aplicada e marcada no _db_migrations.

ProfilePage e MelissaPerfil:
- form.professional_registration_type_other no reactive
- SELECT/UPDATE inclui a nova coluna
- UI condicional: campo aparece SOMENTE quando type === 'outro'
- Preview ao vivo usa type_other no lugar de 'outro' quando aplicavel
- Save limpa type_other automaticamente quando troca pra outro tipo

DocumentGenerate.service.loadTherapistData puxa type_other da query.
Quando profile.type='outro', terapeuta_registro_tipo recebe o valor
livre (ex: 'APM 12345/SP' em vez de 'outro 12345/SP'). terapeuta_crp
(legacy compat) continua so preenchido quando type RAW = 'CRP'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:26:21 -03:00
Leonardo 7516468f78 melissa/perfil: ajustes UX nos cards novos
4 fixes pedidos no teste manual:

1. Card "Registro profissional" movido pra apos Identidade (em vez
   de antes do Layout). Faz sentido contextual — dados pessoais
   profissionais ficam juntos.

2. Inputs do Registro convertidos pra FloatLabel variant="on"
   (padrao Melissa do resto da tela). Tres campos: tipo, numero, uf
   + preview box.

3. Card "Preferencias" tema agora em 1 linha (grid 2-col fixo,
   classe .mpr-theme-row). Antes podia quebrar em 2 linhas via
   flex-wrap.

4. "Trocar senha" navega pra /melissa/seguranca (rota nativa
   Melissa, MelissaSeguranca.vue ja existente) em vez de
   /account/security (que sairia do shell Melissa). Nao vaza mais
   pro layout classico.

Styles novos extraidos do inline pro <style scoped>: mpr-preview-box,
mpr-theme-row, mpr-theme-card, mpr-info-row, mpr-action-card.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:14:29 -03:00
Leonardo 20d2b3aee4 melissa/perfil: 3 cards novos — registro CFP + preferencias + seguranca
Espelha as melhorias do ProfilePage no perfil nativo Melissa
(/melissa/perfil), com 4 changes:

1. Card "Registro Profissional" (id=mpr-sec-registro, antes do
   card Layout): Select tipo + Number + Select UF + preview ao vivo
   "Aparecera nos documentos como: CRP 06/12345/SP". 3 colunas de
   migration 20260521000003 wire-up no load + save.

2. Card "Layout" — sub do Rail atualizado pra mensagem solicitada:
   "Icones no canto esquerdo + painel expansivel. Disponivel apenas
   no desktop."

3. Card "Preferencias" (id=mpr-sec-preferencias, depois do Layout):
   toggle Tema Claro vs Escuro com cards visuais + sun/moon icons.
   Usa isDarkTheme + toggleDarkMode do useLayout.

4. Card "Seguranca" (id=mpr-sec-seguranca, ultimo): mostra e-mail
   atual readonly + botao "Trocar senha" que navega pra
   /account/security.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:04:32 -03:00
Leonardo 049dd91b9b agenda Fase B2: extrai mutations pro agendaBilling.service
Continua decomposicao da agenda. Extrai 3 mutations:
- applyStatusDecisions          (~330L — reverse, consume saldo,
                                  multa, mark paid, generate package
                                  charge, antecipated payment)
- createPackageContract         (~140L — upfront ou saldo)
- materializeAndChargePerSession (~90L — N events + N records)

Padrao das assinaturas:
- supabase como dep explicita (em vez de closure)
- toast OPCIONAL (callsite fora de UI pode passar null;
  applyStatusDecisions ramifica via `if (toast?.add)`)
- ownerId/tenantId como args (em vez de capturar refs)

createPackageContract + materializeAndChargePerSession ja retornavam
{ toast: {...} } pra caller mostrar — pattern preservado.

useMelissaAgenda.js: 2593L -> 2042L (-551L). 3 wrappers finos
injetam supabase/toast/refs do escopo do composable. Comportamento
identico — codigo movido linha-a-linha, so refactor de signature.

TOTAL nas fases A+B1+B2: -1525L extraidas do useMelissaAgenda
(de 3033L original pra 2042L atual). Tres pages (Melissa/Rail/
Clinica) agora podem reusar mesmo billing core.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 09:37:09 -03:00
Leonardo e7e3d1beb1 agenda Fase B1: agendaBilling.service (read-only + helpers puros)
Continua decomposicao da agenda (apos Fase A utils). Extrai pro
service os componentes read-only / pure:

- computeSeriePrice          (puro)
- generateOccurrenceDates    (puro)
- loadStatusChangeContext    (read-only DB — assina supabase,
                              ownerId, tenantId, row, eventoId,
                              status)
- needsStatusConfirmDialog   (puro — depende so do ctx)

useMelissaAgenda.js: 2792L -> 2593L (-199L). _loadStatusChangeContext
agora e wrapper fino que injeta supabase/ownerId/tenantId do
composable scope. _needsConfirmDialog vira alias direto.
_computeSeriePrice/_generateOccurrenceDates importados direto.

Fase B1 deixa Rail/Clínica capazes de reusar TODA a logica
read-only de status change. Mutations (applyStatusDecisions,
createPackageContract, materializeAndChargePerSession) ficam pra
Fase B2.

Risco: zero comportamental — toda chamada produz o mesmo ctx
de antes. Codigo movido sem mudancas de logica.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 09:30:06 -03:00
Leonardo ee117eafe6 agenda Fase A: extrai utils puros pra features/agenda/utils
Decomposicao da agenda em prep pra replicar Rail/Clinica.

4 arquivos novos em src/features/agenda/utils/:
- eventoTipo.js  -> EVENTO_TIPO + normalize/derive + MAX_SESSION_MINUTES
- dbFields.js    -> pickDbFields whitelist (memoria pickdbfields_whitelist)
- timeHelpers.js -> isUuid + addMinutesToTime + isoToDecimalHour + dateToISO
- colors.js      -> pickColor (status+tipo+isOccurrence)

useMelissaAgenda.js (2863L -> 2792L): removeu definicoes locais
(83 linhas), passou a importar dos utils. Aliases _addMinutesToTime
e _dateToISO mantidos no escopo via import "as" pra nao mexer
em 70+ callsites internos.

Fase A = baseline zero-comportamental pra Rail/Clinica adotarem
os mesmos helpers. Fase B (service de billing — applyStatusDecisions,
createPackageContract, materializeAndCharge) vem em seguida.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 09:21:12 -03:00
Leonardo b7f3c23ad6 agenda C12 UX: filtrar cancelled do dialog Lancamentos da sessao
Iteracao UX #2 do C12: records cancelled (do ciclo Revogar+Antecipar
e tambem das multas) poluiam o dialog "Lancamentos da sessao",
escondendo o que importa (ativos).

lancamentosShowHistory ref (default false) + lancamentosFiltered
computed filtra status !== 'cancelled'. lancamentosCancelledCount
computa contagem pra feedback.

UI:
- Dialog abre limpo (sempre lancamentosShowHistory=false em
  onVerLancamentos)
- Quando ha cancelled e existe ativo: linha acima da lista com
  "{N} cancelado(s) ocultos" + botao toggle "Mostrar/Ocultar
  historico"
- Quando todos sao cancelled: empty state especial "Sem
  lancamentos ativos. {N} cancelado(s) no historico" + botao
  pra expandir
- Cards cancelled atenuados (opacity 0.55, border-dashed,
  background sutil, description com line-through) — claramente
  audit trail, nao-ativo

Combina com "Trocar metodo" (commit anterior) — agora o caso 99%
do tempo ele ve so o record ativo, nao precisa nem expandir
historico.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 05:31:01 -03:00
Leonardo 9c518a2b44 agenda C12 UX: "Trocar metodo" em vez de Revogar+Antecipar
Iteracao UX do C12 (antecipar pagamento) — antes user que queria
trocar PIX por dinheiro precisava Revogar (cancela record) +
Antecipar de novo (cria record novo), acumulando lixo no audit
trail (memoria project_c12_antecipar_iterar: ciclos longos chegaram
a 5+ records cancelled num mesmo evento).

MelissaEventoPanel ganha 3 botoes quando isAntecipacaoAtiva:
  - "Trocar metodo"   (default, icone pi-sync)
  - "Revogar pagamento" (danger, icone pi-times-circle)
Antes mostrava so "Revogar".

MelissaLayout:
- anteciparMode ref ('create' | 'update') + onTrocarMetodoAntecipacao
  pre-seleciona o metodo atual lendo o paid record antes de abrir
  o dialog
- confirmAnteciparPagamento ramifica: mode='update' faz UPDATE no
  paid existente (payment_method + paid_at + notes audit "metodo
  trocado: X -> Y"). Sem cancel cycle, sem record novo.
- Dialog header/labels/CTA dinamicos por mode

Result: ciclo trocar metodo agora gera 0 records cancelled (so
update + nota auditoria). Revogar continua disponivel pra quando
realmente precisar cancelar o pagamento.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 05:29:02 -03:00
Leonardo b1e8e010c0 roadmap #13: relatorios export PDF + Excel + CSV
ROADMAP item #1.3 #13. exceljs e jspdf ja estavam no package.json
mas as paginas de relatorio so renderizavam UI — zero export.

src/services/reportExport.service.js (novo) com 3 funcoes:
- exportSessionsToPDF: layout HTML→PDF via pdf.service.js (header
  com branding tenant, KPI grid, tabela A4 com striping)
- exportSessionsToXLSX: ExcelJS workbook formatado (titulo + subtitle
  + KPIs inline + tabela com header escuro + alternating row + frozen
  header). Import dinamico — exceljs e pesado, so carrega no click.
- exportSessionsToCSV: vanilla (sem deps) com BOM UTF-8 + separador
  ';' (Excel-friendly em pt-BR)

3 botoes em ambas paginas:
- RelatoriosPage.vue (/therapist/relatorios): icones pi-file-pdf +
  pi-file-excel + pi-table no header (rounded), tooltip, disabled
  quando total=0 ou loading, toast de sucesso/erro
- MelissaRelatorios.vue (Melissa secao): mesma logica, botoes nativos
  .mr-head-btn no padrao Melissa

Filtro de status da tabela e respeitado no export (exporta o que
o usuario esta vendo). KPIs incluidos no PDF e XLSX.

§1.3 UX = 3/4 fechado: #10 (busca global) + #11 (recently viewed) +
#13 (relatorios export). #12 (papel timbrado) bloqueado em codigo
externo do UniaoApp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 05:21:36 -03:00
Leonardo 2dae4a11ae roadmap #11: recently viewed (ultimos 5 pacientes acessados)
ROADMAP item #1.3 #11. localStorage por user_id pra isolar sessoes
diferentes no mesmo browser. ROADMAP sugeria localStorage OU tabela
user_recent_access — escolhi localStorage por simplicidade (sem
migration adicional + zero round-trip por visita).

composables/useRecentPatients.js:
- useRecentPatients() — composable reativo Tipo A: items + hasItems
  + addVisit + remove + clear + refresh
- registerPatientVisit(patient) — helper stateless pra usar fora
  de setup (ex: navigation guards, action handlers)
- Sincroniza entre instancias na mesma aba via CustomEvent + 'storage'
- Max 5 items. Dedup por id, novo no topo.

Wire-up de visita (registra ao carregar prontuario):
- MelissaPaciente.vue: registerPatientVisit no loadAll apos detail.load
- PatientProntuario.vue: registerPatientVisit em loadDetail apos p resolved

Wire-up de visualizacao (mostra quando query vazia):
- GlobalSearch.vue: grupo "Acessados recentemente" antes dos Atalhos.
  goTo("recent") navega pra /therapist/patients/:id.
- MelissaBusca.vue: grupo "Acessados recentemente". emit('paciente')
  reusando a logica do MelissaLayout que ja navega pra
  /melissa/paciente?id=X.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 05:17:51 -03:00
Leonardo e7a9bdab5f roadmap #10: MelissaBusca usa RPC search_global (promovida de preview)
Fecha ROADMAP #1.3 #10 (busca global topbar). GlobalSearch.vue
classic+rail ja usava RPC. MelissaBusca era client-side preview com
fallback nas props (pacientes+eventos do dia) — agora consulta a
mesma RPC search_global com debounce 200ms + searchSeq pra descartar
respostas obsoletas.

3 grupos novos exibidos quando RPC retorna:
  - rpc-appointments  -> sessoes qualquer data (alem de "hoje")
  - rpc-documents     -> documentos por nome/tipo
  - rpc-intakes       -> cadastros recebidos

Pacientes mescla: RPC tem prioridade (todos os pacientes); props
mantida como fallback rapido (digitacao curta antes do debounce).
Emits estendidos: novos 'documento' + 'intake' alem dos existentes
'acao' + 'paciente' + 'evento'.

MelissaLayout atualizado:
  - @paciente agora navega pra /melissa/paciente?id=X (antes ignorava
    payload e so abria secao generica — bug existente)
  - @documento abre prontuario do paciente com tab=documentos
  - @intake abre /melissa cadastros-recebidos

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 05:13:47 -03:00
Leonardo 1c2a2b6e19 M2: patients — selects + repository + 8 composables refatorados
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>
2026-05-21 04:20:08 -03:00
Leonardo 27467bbb68 M1: features/medicos + features/insurance + ComponentCadastroRapido refactor
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>
2026-05-21 04:19:57 -03:00
Leonardo f83315baba agenda: popover watch acompanha transicao virtual->materializada
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>
2026-05-20 15:23:18 -03:00
Leonardo 7d2a405d05 agenda: normalizeForMelissa expoe owner_id/tenant_id/contract_id
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>
2026-05-20 15:10:51 -03:00
Leonardo b5e00a7022 agenda: popover sincroniza com eventos + antecipar nao reusa cancelled
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>
2026-05-20 15:07:11 -03:00
Leonardo 272c804335 agenda: revogar antecipacao de pagamento
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>
2026-05-20 14:28:04 -03:00
Leonardo 00c4168393 agenda: C12 prep — detectar paid pre-existente em pacote saldo realizada
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>
2026-05-20 12:28:19 -03:00
Leonardo 5965b05378 agenda: link universal pacote + refresh saldo no reverse
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>
2026-05-20 12:21:04 -03:00
Leonardo 45984e885b agenda: label + badge cientes de pacote em sessoes state=none
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>
2026-05-20 12:09:45 -03:00
Leonardo 3f3f2acc70 agenda: consumeSaldo agora amarra billing_contract_id no evento
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>
2026-05-20 11:50:26 -03:00
Leonardo 5684297243 agenda: reverse transition trava (Agendada apos artefatos)
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>
2026-05-20 11:40:19 -03:00
Leonardo 16dfa02bd1 agenda pacote saldo: fix root cause + sequential awaits
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>
2026-05-20 11:25:04 -03:00
Leonardo 079509e001 agenda: dialog pacote saldo realizada — 2 sub-questions claras
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>
2026-05-20 11:11:28 -03:00