Files
agenciapsilmno/relatorios/RELATORIO-AGENDA-2026-05-20.html
Leonardo 5b345c5598 relatorios: analise senior do modulo agenda pos C1-C13
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>
2026-05-20 16:22:25 -03:00

1446 lines
58 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Relatório de Análise — Módulo Agenda · 2026-05-20</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
/* ─── Reset & base ──────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', sans-serif;
font-size: 11pt;
line-height: 1.55;
color: #1a1a1a;
background: #f5f5f7;
}
.page {
max-width: 900px;
margin: 0 auto;
background: white;
padding: 56px 64px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
.page + .page { margin-top: 24px; }
/* ─── Tipografia ───────────────────────────────────────────── */
h1 {
font-size: 28pt;
font-weight: 700;
margin: 0 0 4px;
color: #0f172a;
letter-spacing: -0.02em;
}
h1 .h-sub {
display: block;
font-size: 13pt;
font-weight: 500;
color: #64748b;
margin-top: 8px;
letter-spacing: 0;
}
h2 {
font-size: 18pt;
font-weight: 700;
margin: 40px 0 12px;
color: #1e293b;
padding-bottom: 8px;
border-bottom: 2px solid #e2e8f0;
letter-spacing: -0.01em;
}
h2 .h2-num {
display: inline-block;
background: #1e293b;
color: white;
width: 32px;
height: 32px;
border-radius: 8px;
text-align: center;
line-height: 32px;
font-size: 14pt;
margin-right: 12px;
vertical-align: middle;
}
h3 {
font-size: 13pt;
font-weight: 600;
margin: 24px 0 8px;
color: #334155;
}
h4 {
font-size: 11pt;
font-weight: 600;
margin: 16px 0 6px;
color: #475569;
text-transform: uppercase;
letter-spacing: 0.04em;
}
p { margin: 0 0 12px; }
ul, ol { margin: 0 0 12px; padding-left: 22px; }
li { margin-bottom: 4px; }
code {
font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
font-size: 9.5pt;
background: #f1f5f9;
padding: 1px 5px;
border-radius: 3px;
color: #be185d;
}
pre {
font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
font-size: 9pt;
background: #0f172a;
color: #e2e8f0;
padding: 12px 16px;
border-radius: 6px;
overflow-x: auto;
margin: 12px 0;
line-height: 1.5;
}
pre code { background: transparent; color: inherit; padding: 0; }
blockquote {
margin: 12px 0;
padding: 12px 18px;
border-left: 4px solid #3b82f6;
background: #eff6ff;
color: #1e3a8a;
font-size: 10pt;
border-radius: 0 4px 4px 0;
}
/* ─── Cards e elementos visuais ────────────────────────────── */
.meta-strip {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin: 20px 0 28px;
}
.meta-card {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 12px 14px;
}
.meta-card__label {
font-size: 8pt;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
}
.meta-card__value {
font-size: 22pt;
font-weight: 700;
color: #0f172a;
line-height: 1.1;
margin-top: 4px;
}
.meta-card__sub {
font-size: 8.5pt;
color: #94a3b8;
margin-top: 2px;
}
.severity-table {
width: 100%;
border-collapse: collapse;
font-size: 10pt;
margin: 12px 0 20px;
}
.severity-table th, .severity-table td {
padding: 9px 12px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
vertical-align: top;
}
.severity-table th {
background: #f8fafc;
font-weight: 600;
color: #475569;
font-size: 9.5pt;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.severity-table tr:hover td { background: #f8fafc; }
.pill {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
font-size: 8.5pt;
font-weight: 600;
letter-spacing: 0.02em;
text-transform: uppercase;
}
.pill-critical { background: #fee2e2; color: #991b1b; }
.pill-high { background: #fed7aa; color: #9a3412; }
.pill-medium { background: #fef3c7; color: #92400e; }
.pill-low { background: #dbeafe; color: #1e40af; }
.pill-info { background: #e0e7ff; color: #3730a3; }
.pill-good { background: #d1fae5; color: #065f46; }
.pill-p0 { background: #dc2626; color: white; }
.pill-p1 { background: #f59e0b; color: white; }
.pill-p2 { background: #3b82f6; color: white; }
.pill-p3 { background: #64748b; color: white; }
.callout {
margin: 16px 0;
padding: 14px 18px;
border-radius: 6px;
border-left: 4px solid;
font-size: 10.5pt;
}
.callout-warn { background: #fffbeb; border-color: #f59e0b; color: #78350f; }
.callout-info { background: #eff6ff; border-color: #3b82f6; color: #1e3a8a; }
.callout-success { background: #ecfdf5; border-color: #10b981; color: #064e3b; }
.callout-danger { background: #fef2f2; border-color: #ef4444; color: #7f1d1d; }
.callout strong { font-weight: 700; }
.toc {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 16px 20px;
margin: 20px 0 30px;
}
.toc h3 { margin-top: 0; color: #475569; font-size: 11pt; }
.toc ol { padding-left: 18px; margin: 0; }
.toc li { font-size: 10pt; margin-bottom: 3px; }
.toc li a { color: #1e40af; text-decoration: none; }
.toc li a:hover { text-decoration: underline; }
.signature {
margin-top: 48px;
padding-top: 18px;
border-top: 1px solid #e2e8f0;
font-size: 9pt;
color: #94a3b8;
}
/* ─── Print styles (PDF export) ────────────────────────────── */
@page {
size: A4;
margin: 18mm 16mm 22mm;
}
@media print {
body { background: white; font-size: 10pt; }
.page {
box-shadow: none;
padding: 0;
max-width: none;
margin: 0;
}
.page + .page { margin-top: 0; page-break-before: always; }
h2 { page-break-after: avoid; }
h3 { page-break-after: avoid; }
pre, blockquote, .callout, .severity-table { page-break-inside: avoid; }
a { color: #1e40af; text-decoration: none; }
.print-hide { display: none; }
}
/* ─── Print hint visible only on screen ────────────────────── */
.print-hint {
position: fixed;
bottom: 20px;
right: 20px;
background: #1e293b;
color: white;
padding: 10px 16px;
border-radius: 6px;
font-size: 9.5pt;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 100;
}
@media print { .print-hint { display: none; } }
</style>
</head>
<body>
<div class="print-hint">💡 Ctrl+P → <b>Salvar como PDF</b></div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PÁGINA 1 — CAPA + SUMÁRIO EXECUTIVO -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="page">
<h1>
Análise Sênior · Módulo Agenda
<span class="h-sub">Estado pós-bateria de testes C1C13 · 2026-05-20 · Sistema AgênciaPSI</span>
</h1>
<div class="meta-strip">
<div class="meta-card">
<div class="meta-card__label">Files</div>
<div class="meta-card__value">142</div>
<div class="meta-card__sub">Arquivos no escopo agenda</div>
</div>
<div class="meta-card">
<div class="meta-card__label">LOC</div>
<div class="meta-card__value">15.6k</div>
<div class="meta-card__sub">Linhas em hotspots-chave</div>
</div>
<div class="meta-card">
<div class="meta-card__label">Bugs/dia</div>
<div class="meta-card__value">14</div>
<div class="meta-card__sub">Corrigidos em 20/05</div>
</div>
<div class="meta-card">
<div class="meta-card__label">Commits/dia</div>
<div class="meta-card__value">15</div>
<div class="meta-card__sub">Working tree clean</div>
</div>
</div>
<h2><span class="h2-num">0</span>Sumário Executivo</h2>
<p>
O módulo de agenda do AgênciaPSI atravessou uma rodada estrutural de validação
via 13 cenários do documento vivo <code>src/docs/agenda-compromisso-financeiro-cenarios.html</code>.
A bateria expôs <strong>14 bugs reais</strong> em apenas um dia — todos corrigidos —
e revelou um padrão recorrente: <strong>regressões silenciosas em fluxos
paralelos</strong>, particularmente em interações com <code>billing_contracts</code> e
<code>financial_records</code>.
</p>
<p>
A engenharia do módulo é <strong>funcionalmente robusta</strong> — os fluxos
críticos (status change, pacote saldo, pacote upfront, multa, antecipação,
edit imutável) operam corretamente após os fixes. Mas a base de código carrega
sintomas que devem ser endereçados antes da replicação para Rail e Clínica,
sob risco de duplicação de dívida.
</p>
<div class="callout callout-warn">
<strong>Verdict resumido:</strong> arquitetura sólida em conceito, dívida
técnica acumulada em execução. Refatoração de 3 hotspots e adoção de tipagem
forte resolveriam ≥60% dos bugs futuros antes de virarem produção. A
replicação Rail/Clínica é o ponto natural de inflexão para consolidar.
</div>
<h3>Top 5 achados</h3>
<ol>
<li>
<strong>3 hotspots ultrapassam 2,8k LOC cada</strong>
<code>AgendaEventDialog.vue</code> (6.070), <code>MelissaLayout.vue</code> (4.331),
<code>AgendaTerapeutaPage.vue</code> (3.567). Refator sustentado é inviável neste estado.
<span class="pill pill-critical">P0</span>
</li>
<li>
<strong>Lógica de status change triplicada</strong> entre Melissa, Rail e
Clínica. Bugs corrigidos hoje em Melissa ainda persistem nas outras duas implementações.
<span class="pill pill-high">P0</span>
</li>
<li>
<strong>Gotcha <code>billing_contracts.updated_at</code></strong>
coluna inexistente. UPDATEs falhavam silenciosamente em
<code>Promise.allSettled</code>. Patch tipo "trigger SQL"
eliminaria a categoria inteira.
<span class="pill pill-high">P1</span>
</li>
<li>
<strong>Snapshot stale em popover</strong><code>eventoSelecionado.value</code>
é cópia, não referência reativa. Padrão repetido em outros componentes do
Melissa. Ergonomia ruim para mutações otimistas.
<span class="pill pill-medium">P1</span>
</li>
<li>
<strong>Audit trail acumulando ruído</strong> — ciclos
antecipar/revogar/realizar geram records cancelled que poluem
a view <code>/financeiro</code> sem filtro. Falta UI dedicada de auditoria.
<span class="pill pill-medium">P2</span>
</li>
</ol>
<div class="toc">
<h3>Índice</h3>
<ol>
<li><a href="#estado">Estado atual — métricas e estrutura</a></li>
<li><a href="#fortes">Pontos fortes</a></li>
<li><a href="#debitos">Dívidas técnicas / code smells</a></li>
<li><a href="#ux">Issues de UX</a></li>
<li><a href="#riscos">Riscos arquiteturais</a></li>
<li><a href="#recom">Recomendações priorizadas (P0P3)</a></li>
<li><a href="#roadmap">Roadmap proposto</a></li>
<li><a href="#apendices">Apêndices — bugs do dia, pendências, commits</a></li>
</ol>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PÁGINA 2 — ESTADO ATUAL -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="page">
<h2 id="estado"><span class="h2-num">1</span>Estado atual</h2>
<h3>1.1 Distribuição de complexidade (hotspots)</h3>
<table class="severity-table">
<thead>
<tr>
<th>Arquivo</th>
<th style="text-align:right;">LOC</th>
<th>Tipo</th>
<th>Observação</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AgendaEventDialog.vue</code></td>
<td style="text-align:right;"><strong>6.070</strong></td>
<td>Componente Vue monolítico</td>
<td>≥7 responsabilidades distintas: cadastro, recorrência, billing, multa, pacote, lock, preview</td>
</tr>
<tr>
<td><code>MelissaLayout.vue</code></td>
<td style="text-align:right;"><strong>4.331</strong></td>
<td>Layout container</td>
<td>Container + ~30 handlers + 8 dialogs montados; deveria ser shell</td>
</tr>
<tr>
<td><code>AgendaTerapeutaPage.vue</code></td>
<td style="text-align:right;"><strong>3.567</strong></td>
<td>Página Rail (legacy)</td>
<td>Implementação paralela com lógica duplicada</td>
</tr>
<tr>
<td><code>useMelissaAgenda.js</code></td>
<td style="text-align:right;"><strong>2.863</strong></td>
<td>Composable monolítico</td>
<td>Bulk-load, normalize, handlers, status change, decisions, applies — tudo aqui</td>
</tr>
<tr>
<td><code>AgendaClinicaPage.vue</code></td>
<td style="text-align:right;"><strong>2.811</strong></td>
<td>Página Clínica (legacy)</td>
<td>3ª implementação paralela; receberá os mesmos bugs</td>
</tr>
<tr>
<td><code>MelissaEventoPanel.vue</code></td>
<td style="text-align:right;">1.010</td>
<td>Popover</td>
<td>Aceitável, mas concentra lógica de derivação (paymentLabel, contractInfo, ações)</td>
</tr>
<tr>
<td><code>useAgendaEventLifecycle.js</code></td>
<td style="text-align:right;">685</td>
<td>Composable de lifecycle</td>
<td>Carregamento, edit, delete; saudável em tamanho</td>
</tr>
<tr>
<td><code>AgendaStatusChangeConfirmDialog.vue</code></td>
<td style="text-align:right;">671</td>
<td>Dialog dedicado</td>
<td>Recém-ampliado com bloco reverse (Fase 5); ainda dentro do aceitável</td>
</tr>
</tbody>
</table>
<p style="font-size:9.5pt;color:#64748b;">
Total nos hotspots acima: <strong>~22k LOC</strong>. Em escala de manutenção,
qualquer arquivo &gt;1.000 LOC é candidato a refator; &gt;2.500 LOC é débito
crítico — toda navegação demanda Ctrl+F.
</p>
<h3>1.2 Estrutura de pastas (recap)</h3>
<pre><code>src/
├── features/agenda/
│ ├── components/ (15 .vue — DialogV2, Toolbar, Calendar, etc)
│ ├── composables/ (20 .js — lifecycle, events, services, etc)
│ └── pages/ (5 .vue — Terapeuta, Clínica, Recorrencias, etc)
├── layout/melissa/
│ ├── composables/ (useMelissaAgenda 2.863 LOC)
│ ├── MelissaLayout.vue (shell + handlers)
│ ├── MelissaAgenda.vue (FullCalendar wrapper)
│ ├── MelissaEventoPanel (popover)
│ └── ...
└── components/agenda/ (AgendaEventoFinanceiroPanel, etc)</code></pre>
<h3>1.3 Cobertura de testes</h3>
<p>
<code>src/features/agenda/composables/__tests__/</code> tem 7 specs Vitest
cobrindo composables principais (events, lifecycle, composer, picker, recurrence).
<strong>Não há testes E2E.</strong> Toda validação de fluxo financeiro
(status change, pacote, antecipar) depende de bateria manual contra o documento
vivo HTML.
</p>
<div class="callout callout-info">
<strong>Métrica de risco:</strong> 14 bugs encontrados num único dia de
teste manual num único persona (Melissa). Extrapolando para Rail/Clínica
sem refator antes, esperar 2535 bugs equivalentes na replicação.
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PÁGINA 3 — PONTOS FORTES -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="page">
<h2 id="fortes"><span class="h2-num">2</span>Pontos fortes</h2>
<p>Análise reconhece méritos antes de criticar. O que está bem feito:</p>
<h3>2.1 Cobertura de cenários documentada</h3>
<p>
O arquivo <code>src/docs/agenda-compromisso-financeiro-cenarios.html</code>
é uma <strong>boa prática rara</strong>: doc vivo que descreve cada cenário
com mockup interativo + critério de teste. Funciona como spec executável
informal. O addendum de C10 que adicionamos hoje preservou o histórico
de divergências.
<span class="pill pill-good">Manter</span>
</p>
<h3>2.2 Audit trail em <code>financial_records.notes</code></h3>
<p>
Todas as transições de cancelamento, reversão e revogação registram entrada
cronológica em <code>notes</code> com formato <code>[YYYY-MM-DD] motivo</code>.
Permite reconstruir o histórico financeiro de qualquer sessão.
Estrutura simples mas efetiva — não criou tabela de auditoria paralela.
<span class="pill pill-good">Manter</span>
</p>
<h3>2.3 Reverse transition trava (recém-implementada)</h3>
<p>
O dialog <code>AgendaStatusChangeConfirmDialog</code> agora cobre 4 contextos
distintos: realizada (avulsa/pacote), faltou/cancelado (com multa+saldo),
realizada com paid pré-existente (antecipação), e reversão para agendado
(com cancelar records + devolver saldo). É o ponto único de decisão
para mudanças com impacto financeiro.
<span class="pill pill-good">Manter</span>
</p>
<h3>2.4 Bubble <code>@cobranca-atualizada</code></h3>
<p>
Sub-componentes (panel financeiro, dialog) emitem evento que sobe até
<code>MelissaLayout</code> e dispara <code>M.refetch()</code>. Padrão limpo
para propagar updates de pagamento sem acoplar pais e filhos via store global.
<span class="pill pill-good">Manter — replicar em Rail/Clínica</span>
</p>
<h3>2.5 Lock em sessão encerrada</h3>
<p>
Sessões em <code>cancelado</code>/<code>faltou</code> agora bloqueiam
"Editar sessão" + transições de status (exceto "Agendada" como caminho
explícito de recuperação) + label e badge cientes do estado. Evita
operações financeiras inadvertidas em sessões que não aconteceram.
<span class="pill pill-good">Manter</span>
</p>
<h3>2.6 Schema com constraints adequadas</h3>
<p>
<code>financial_exceptions_charge_chk</code> restringe valores possíveis
de <code>charge_mode</code>. <code>financial_records.status_check</code>
enumera estados válidos. Constraints de check ao invés de validação
apenas em código — defesa em profundidade.
<span class="pill pill-good">Manter</span>
</p>
<h3>2.7 Quick-create inline</h3>
<p>
Em vez de navegar para outro form quando falta uma dep (serviço, convênio,
paciente), abre quick-create por cima. Preserva contexto do usuário.
Padrão consistente.
<span class="pill pill-good">Manter — padrão de design</span>
</p>
<h3>2.8 Convenção de variantes visuais</h3>
<p>
Cores semânticas consistentes — <code>--ok</code> (verde realizada),
<code>--warn</code> (amarelo falta), <code>--danger</code> (vermelho cancelar),
<code>--info</code> (azul agendada recém-adicionada). Padrão se mantém
entre popover, dialog e calendário.
<span class="pill pill-good">Manter</span>
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PÁGINA 4 — DÍVIDAS TÉCNICAS -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="page">
<h2 id="debitos"><span class="h2-num">3</span>Dívidas técnicas / code smells</h2>
<h3>3.1 Monólitos Vue (≥2.500 LOC)</h3>
<p>
<code>AgendaEventDialog.vue</code> com 6k LOC concentra: cadastro de evento,
recorrência, billing config, multas, pacote saldo/upfront, lock de cobrança
emitida, preview da série, edição de ocorrência única vs todos. <strong>Sete
responsabilidades distintas em um único componente</strong> — viola SRP.
</p>
<p>
Sintomas operacionais já observados: edição perigosa (qualquer alteração
precisa varrer todo o arquivo), regressões frequentes em flows não-relacionados,
teste unitário não consegue isolar um caso sem mockar 80% do contexto.
</p>
<p>
Quebra natural:
</p>
<ul>
<li><code>AgendaEventDialogShell</code> — orquestrador, gerencia steps e props</li>
<li><code>AgendaEventDialogStepBasic</code> — paciente, data, hora, modalidade</li>
<li><code>AgendaEventDialogStepRecurrence</code> — recorrência + preview de série</li>
<li><code>AgendaEventDialogStepBilling</code> — service/insurance/billing type/lock</li>
<li><code>AgendaEventDialogStepConfirm</code> — preview final + diff antes/depois</li>
</ul>
<h3>3.2 Triplicação de lógica (Melissa / Rail / Clínica)</h3>
<div class="callout callout-danger">
<strong>Maior risco arquitetural identificado.</strong> Os bugs corrigidos
hoje em <code>useMelissaAgenda._applyStatusDecisions</code> (cobrança dupla,
<code>updated_at</code> gotcha, link universal) <strong>não foram propagados</strong>
para <code>AgendaTerapeutaPage</code> nem <code>AgendaClinicaPage</code>.
</div>
<p>
O caminho que <em>vai dar errado</em>: replicar manualmente os fixes para
cada um dos 3 contextos. Multiplica esforço por 3 e introduz drift.
</p>
<p>
Caminho correto: extrair em <code>useAgendaStatusChangeOrchestrator.js</code>
consumido pelos três (Melissa via composable, Rail/Clínica via importação direta).
Tornar <code>_applyStatusDecisions</code>, <code>_loadStatusChangeContext</code>,
<code>_needsConfirmDialog</code> e <code>_applyStatusUpdateOnly</code> em
funções puras com deps por injeção.
</p>
<h3>3.3 <code>Promise.allSettled</code> escondendo falhas</h3>
<p>
Padrão recorrente: <code>tasks.push(...); await Promise.allSettled(tasks)</code>.
Quando uma das tasks falha, vira <code>{status:'rejected'}</code> e o toast
warn genérico aparece — fácil de ignorar.
</p>
<p>
Caso real (corrigido hoje): UPDATE em <code>billing_contracts</code> com
campo <code>updated_at</code> inexistente. Falha silenciosa por <strong>semanas</strong>.
Foi descoberto manualmente quando o saldo não incrementava em teste E2E manual.
</p>
<p>
Política sugerida:
</p>
<ul>
<li>Tasks com efeitos colaterais financeiros usam <code>await</code> sequencial
+ <code>try/catch</code> + toast/console específico por falha</li>
<li><code>Promise.allSettled</code> apenas para operações realmente
independentes (ex: pré-buscas de UI auxiliar)</li>
<li>Logger estruturado com tags por domínio
(<code>[agenda/status]</code>, <code>[agenda/billing]</code>) ao invés de
<code>console.warn</code> espalhado</li>
</ul>
<h3>3.4 <code>normalizeForMelissa</code> whitelist silenciosa</h3>
<p>
A função <code>normalizeForMelissa(r)</code> retorna um objeto com campos
explícitos. Campos no <code>r</code> original que não estão no return
<strong>somem silenciosamente</strong>. Bug do dia: <code>owner_id</code>
não estava no return → após o watch sync introduzido, virava <code>null</code>
INSERT em <code>financial_records</code> violava NOT NULL.
</p>
<p>
Memória existente (<code>project_pickdbfields_whitelist</code>) documenta
bug análogo no <code>pickDbFields</code>. Padrão repetido.
</p>
<p>
Mitigação imediata: documentar e auditar manualmente todos os campos
necessários. Mitigação estrutural: <strong>TypeScript</strong> ou JSDoc tipado
no return — o linter sinalizaria campos faltantes antes do runtime.
</p>
<h3>3.5 Snapshot stale em refs reativas</h3>
<p>
<code>eventoSelecionado.value = ev</code> copia a referência. Quando o
<code>eventos</code> computed recalcula com novo objeto, a ref guardada não
acompanha. Padrão repetido em outros lugares do Melissa (já há memória
documentando).
</p>
<p>
Mitigação aplicada hoje: watch em <code>M.eventos</code> com lookup por
id + recurrence_id/date (cobre transição virtual→materializada).
</p>
<p>
Mitigação estrutural: trocar o pattern por <code>const selectedId =
ref(null)</code> + <code>const selectedEvent = computed(() =>
eventos.value.find(e => e.id === selectedId.value))</code>. Reatividade
nativa, zero watches, zero race conditions.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PÁGINA 5 — DÍVIDAS TÉCNICAS (cont.) -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="page">
<h2>3 · Dívidas técnicas (continuação)</h2>
<h3>3.6 Race conditions em <code>refetch()</code></h3>
<p>
<code>_reloadRange</code> dispara queries paralelas (eventos, virtuais,
bloqueios, payment state, propagação cross-week). Operações UI sequenciais
rápidas (antecipar→revogar→antecipar) podem ler ctx antes do refetch anterior
propagar, levando a decrements/increments com base em valores stale.
</p>
<p>
Mitigação aplicada hoje no bloco <code>reverseRestoreSaldo</code>: refetch
explícito do <code>billing_contracts.sessions_used</code> direto do DB
<strong>imediatamente antes</strong> do UPDATE. Padrão "lê fresh, escreve
consciente".
</p>
<p>
Mitigação estrutural: mover lógica de increment/decrement de saldo para
RPC server-side com lock pessimista (<code>SELECT ... FOR UPDATE</code>).
Elimina race condition no nível de banco.
</p>
<h3>3.7 RPCs sem idempotência forte</h3>
<p>
<code>create_financial_record_for_session</code> foi corrigido para ignorar
<code>cancelled</code> em idempotência (commit <code>c23d0a5</code>, já
documentado em memória). Mas o handler do antecipar fazia UPDATE manual
em record cancelled <em>após</em> chamar a RPC, reativando-o como paid.
Audit trail polluído.
</p>
<p>
Princípio: <strong>RPCs financeiras nunca devem reusar records cancelados</strong>.
Toda operação que cria cobrança gera record novo. Cancellation é terminal.
</p>
<h3>3.8 Pattern <code>updated_at</code> inconsistente entre tabelas</h3>
<p>
Algumas tabelas têm trigger <code>set_updated_at</code> automático
(ex: <code>financial_exceptions</code>), outras não
(<code>billing_contracts</code>). Código cliente assume incorretamente
que todas têm. Foi origem direta do bug do <code>updated_at</code>.
</p>
<p>
Tabela atual:
</p>
<table class="severity-table">
<thead>
<tr><th>Tabela</th><th>tem updated_at?</th><th>tem trigger?</th></tr>
</thead>
<tbody>
<tr><td><code>financial_records</code></td><td></td><td>presume-se sim</td></tr>
<tr><td><code>financial_exceptions</code></td><td></td><td><code>trg_financial_exceptions_updated_at</code></td></tr>
<tr><td><code>agenda_eventos</code></td><td></td><td>presume-se sim</td></tr>
<tr><td><code>recurrence_rules</code></td><td></td><td>presume-se sim</td></tr>
<tr style="background:#fee2e2;"><td><code>billing_contracts</code></td><td></td><td></td></tr>
</tbody>
</table>
<p>
Recomendação: adicionar <code>updated_at</code> + trigger em
<code>billing_contracts</code> via migration. Tornar consistente em todas
as tabelas de domínio operacional.
</p>
<h3>3.9 RLS pode mascarar erros</h3>
<p>
UPDATEs em RLS-protected tables retornam silenciosamente 0 rows quando
a policy filtra. Sem <code>RETURNING</code> explícito o cliente não detecta.
No fluxo de status change atual, vários UPDATEs assumem que rodaram.
</p>
<p>
Mitigação: adicionar <code>.select()</code> ou <code>.select('id').single()</code>
em UPDATEs sensíveis; falhar explicitamente se 0 rows afetadas.
</p>
<h3>3.10 Estado distribuído sem fonte da verdade</h3>
<p>
Mesma informação (e.g. <code>sessions_used</code>) lida de múltiplas fontes:
<code>ctx.billingContract.sessions_used</code> (snapshot do load),
<code>ev.contract.sessionsUsed</code> (via ruleContractMap), DB fresh,
cached em variáveis locais. Pontos de divergência são pontos de bug.
</p>
<p>
Princípio: <strong>single source of truth</strong> por operação.
Antes de UPDATE crítico, fresh-fetch + uso somente da leitura recente.
Em UIs reativas, derivar de um único store/computed.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PÁGINA 6 — UX ISSUES -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="page">
<h2 id="ux"><span class="h2-num">4</span>Issues de UX</h2>
<h3>4.1 Sobreposição semântica: Usar / Antecipar / Realizada</h3>
<p>
Em pacote saldo, três botões diferentes podem operar sobre a mesma sessão:
</p>
<ul>
<li><strong>Usar</strong> (popover saldo card) → materializa + realizada + cria record + decrementa</li>
<li><strong>Antecipar pagamento</strong> (popover financeiro) → materializa + cria record paid SEM decrementar</li>
<li><strong>Realizada</strong> (popover status group) → dialog forward com checkbox "Gerar cobrança"</li>
</ul>
<p>
Cada um cobre um caso de uso ligeiramente diferente, mas o overlap confunde.
Durante o teste do dia, usuário ficou perdido entre os três caminhos.
</p>
<p>
Proposta: <strong>unified action menu</strong>. Um único botão "Marcar sessão"
abre menu contextual:
</p>
<pre><code>┌─ Marcar sessão como… ─────────────┐
│ ✓ Realizada │
│ ├─ Já recebi (PIX/dinheiro/...) │
│ └─ Cobrar depois (saldo/avulsa) │
│ ⚠ Faltou (+ multa/saldo) │
│ ✕ Cancelar (+ regra) │
├───────────────────────────────────┤
│ 💰 Antecipar pagamento (futura) │
└───────────────────────────────────┘</code></pre>
<h3>4.2 Recovery único via "Agendada" — pouco descobrível</h3>
<p>
Sessão em <code>cancelado</code> tem 4 botões disabled (Realizada/Falta/
Reagendar/Cancelar) com tooltip "use Agendada para reativar". O
aprendizado depende do hover no tooltip — primeiro encontro pode ser frustrante.
</p>
<p>
Proposta: badge inline ao lado dos botões disabled, ou banner amarelo no topo
do popover: "Sessão encerrada. Clique <em>Agendada</em> para reabrir e
permitir novas ações."
</p>
<h3>4.3 Múltiplos records cancelled poluindo audit</h3>
<p>
Ciclos antecipar/revogar/realizar geram records cancelled empilhados.
Em <code>/financeiro</code> o filtro padrão <code>.neq('status','cancelled')</code>
esconde do user normal, mas em queries de debug ou em telas
administrativas a poluição aparece.
</p>
<p>
Propostas:
</p>
<ul>
<li>UI de "histórico" da sessão (drawer lateral) listando todos os
records, agrupando cancelled como timeline colapsada</li>
<li>Soft-archive: após N dias com sessão em terminal state, mover records
cancelled para tabela de audit (<code>financial_records_archive</code>)</li>
</ul>
<h3>4.4 Dialog "Como cobrar?" / "Já recebi?" — refatorado, mas inspecionar mais flows</h3>
<p>
Hoje foi refatorado o bloco "Cobrança no pacote saldo" para ter
sub-question explícita <em>"A sessão já foi paga?"</em>. O mesmo padrão deve
ser auditado em outros lugares onde aparece "método de pagamento" com
opções mistas:
</p>
<ul>
<li>Dialog Antecipar pagamento — ainda usa
<code>paymentMethodOptionsCobranca</code> com "Já recebi — PIX". Padrão
novo deveria propagar para consistência.</li>
<li>Dialog Bloqueio, Dialog Service Quick — mesma checagem.</li>
</ul>
<h3>4.5 "A cobrar R$ X" enganoso para pacote saldo</h3>
<p>
Fix aplicado hoje: label muda para "Aguardando uso do pacote" (saldo) ou
"Coberta pelo pacote (upfront)". Verificar consistência em:
</p>
<ul>
<li>Card no FullCalendar — badge $ suprimido em pacote-tied state=none ✓</li>
<li>Dialog AgendaEventoFinanceiroPanel — verificar se exibe label correta</li>
<li>/financeiro lista — pacote saldo session sem record não deve aparecer
como "a cobrar"</li>
</ul>
<h3>4.6 Feedback de erro fraco em handlers async</h3>
<p>
Toasts genéricos "Falha ao processar mudança de status" não ajudam usuário.
Quando RLS bloqueia ou constraint quebra, mensagem deveria ser específica:
</p>
<ul>
<li>"Cobrança paga não pode ser editada — use estorno em /financeiro"</li>
<li>"Saldo do pacote insuficiente — disponíveis: 0 de 4"</li>
<li>"Sessão tem cobrança vinculada — revogue antes de cancelar"</li>
</ul>
<h3>4.7 Botão "Antecipar pagamento" → "Revogar pagamento" alternando</h3>
<p>
Padrão implementado hoje (alterna baseado em <code>isAntecipacaoAtiva</code>).
Pode ser confuso para usuário que esquece do que clicou — botão "muda
sozinho". Considerar:
</p>
<ul>
<li>Manter "Antecipar pagamento" sempre visível; quando há pagamento
ativo, exibir banner inline acima: "Pagamento antecipado: R$ X via
PIX em 20/05. <button>Revogar</button>"</li>
<li>Mais info, menos ambiguidade — mostra estado e ação no mesmo bloco.</li>
</ul>
<h3>4.8 Reverse dialog: "manter cobrança ativa" potencialmente perigoso</h3>
<p>
No dialog reverse, radio "Manter cobrança ativa" deixa user reativar sessão
com record pending órfão. É uma escolha legítima mas raramente intencional.
Considerar adicionar warning quando seleciona "manter".
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PÁGINA 7 — RISCOS -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="page">
<h2 id="riscos"><span class="h2-num">5</span>Riscos arquiteturais</h2>
<h3>5.1 Replicação Rail/Clínica sem refator = duplicação de dívida</h3>
<div class="callout callout-danger">
Replicar os fixes do dia em <code>AgendaTerapeutaPage</code> e
<code>AgendaClinicaPage</code> mantém as 3 implementações divergentes.
Primeira sprint após replicação vai gastar tempo descobrindo "por que
funciona em Melissa mas não em Clínica" (ou vice-versa).
</div>
<p>
Sequência recomendada antes da replicação:
</p>
<ol>
<li>Extrair status change orchestrator para composable shared</li>
<li>Migrar Melissa para usar o composable shared</li>
<li>Testar (a bateria já existe — 13 cenários)</li>
<li>Migrar Rail/Clínica para o composable shared (1 commit por persona)</li>
</ol>
<h3>5.2 Estado distribuído sem migration testada</h3>
<p>
Várias decisões de domínio são implícitas:
</p>
<ul>
<li>Sessão paid + status=cancelado → estado válido ou inválido?</li>
<li>Sessão sem billing_contract_id mas com record paid em pacote saldo →
órfã ou intencional?</li>
<li>Contract com status='completed' mas sessions_used &lt; total —
possível após sessions_used decrementar via reverse</li>
</ul>
<p>
Não há documentação formal das invariantes do domínio. Consequência: cada
bug exige reconstrução mental do que <em>deveria</em> ser válido.
</p>
<p>
Recomendação: <strong>documento de invariantes do domínio</strong> em
<code>Obsidian/Brain/wiki/agenda-invariantes.md</code> + (futuramente)
constraints SQL ou triggers checando os mais críticos no banco.
</p>
<h3>5.3 Multi-tenant: <code>tenant_id</code> manual em todo INSERT</h3>
<p>
Cada INSERT em <code>financial_records</code>, <code>agenda_eventos</code>,
<code>billing_contracts</code> requer <code>tenant_id</code> explícito.
Esquecer = constraint violation, ou pior, escrever no tenant errado.
</p>
<p>
Mitigação parcial: RLS bloqueia leitura cross-tenant. Mas writes ainda
dependem do código cliente. Pattern de "service layer" com tenant injetado
centralmente reduziria a área de risco.
</p>
<h3>5.4 Edge functions desacopladas da migration story</h3>
<p>
Edge functions (asaas-webhook, evolution-whatsapp, etc) operam direto no DB
e podem deixar state inconsistente com o que o frontend esperar.
Não foi escopo deste relatório, mas merece auditoria separada.
</p>
<h3>5.5 Ausência de feature flags</h3>
<p>
Cada fix do dia foi merged direto pra main e deploy implícito. Não há
feature flag para experimentar "novo dialog reverse" em produção sem
expor para todos. Em sistema com clientes pagantes, é fragil.
</p>
<h3>5.6 Backfills manuais via psql não rastreados</h3>
<p>
Durante o teste, vários <code>UPDATE billing_contracts SET sessions_used=X</code>
foram aplicados manualmente para limpar estado. Em produção isso é
impraticável e arriscado. Falta:
</p>
<ul>
<li>Migration ferramenta para "operações de correção de dados"
com versionamento e rollback</li>
<li>Audit log de operações administrativas sobre dados</li>
</ul>
<h3>5.7 Indicador de saldo derivado vs persistido</h3>
<p>
<code>billing_contracts.sessions_used</code> é INTEIRO incrementado/
decrementado manualmente. Verdade "real" é o COUNT de records associados.
Risco: drift entre os dois ao longo do tempo.
</p>
<p>
Alternativa: tornar <code>sessions_used</code> uma VIEW computed via
<code>COUNT(*) FROM financial_records WHERE billing_contract_id=X AND
status IN ('paid','pending')</code>. Performance pode requerer materialização
ou índice; mas elimina classe inteira de bugs de sync.
</p>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PÁGINA 8 — RECOMENDAÇÕES -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="page">
<h2 id="recom"><span class="h2-num">6</span>Recomendações priorizadas</h2>
<p>
Priorização: <strong>P0</strong> = antes da próxima replicação (Rail/Clínica).
<strong>P1</strong> = sprint atual ou próximo. <strong>P2</strong> = quando
couber, valor compounding. <strong>P3</strong> = ideal mas não bloqueante.
</p>
<table class="severity-table">
<thead>
<tr>
<th>#</th>
<th>Recomendação</th>
<th>Prio</th>
<th>Esforço</th>
<th>Impacto</th>
</tr>
</thead>
<tbody>
<tr>
<td>R1</td>
<td>
<strong>Extrair status change orchestrator</strong> para composable shared
<code>useAgendaStatusChangeOrchestrator.js</code>. Funções puras (loadContext,
needsDialog, applyDecisions, applyStatusUpdate). Melissa migra primeiro,
depois Rail/Clínica importam.
</td>
<td><span class="pill pill-p0">P0</span></td>
<td>46h</td>
<td>Alto · elimina trio</td>
</tr>
<tr>
<td>R2</td>
<td>
<strong>Migration: adicionar <code>updated_at</code> + trigger em
<code>billing_contracts</code></strong>. Elimina o gotcha que custou
horas hoje. Padroniza com outras tabelas.
</td>
<td><span class="pill pill-p0">P0</span></td>
<td>30min</td>
<td>Alto · elimina classe</td>
</tr>
<tr>
<td>R3</td>
<td>
<strong>Auditar Promise.allSettled em fluxos financeiros</strong>.
Trocar por awaits sequenciais com try/catch dedicado. Adicionar
logger estruturado (<code>[agenda/billing/saldo]</code>).
</td>
<td><span class="pill pill-p0">P0</span></td>
<td>23h</td>
<td>Alto · visibilidade</td>
</tr>
<tr>
<td>R4</td>
<td>
<strong>Documentar invariantes do domínio</strong> em
<code>Obsidian/Brain/wiki/agenda-invariantes.md</code>. Estado válido
de status × records × contract. Referência consultável.
</td>
<td><span class="pill pill-p0">P0</span></td>
<td>12h</td>
<td>Médio-alto · onboarding</td>
</tr>
<tr>
<td>R5</td>
<td>
<strong>Refatorar <code>AgendaEventDialog.vue</code></strong> em 5
sub-componentes (Shell, StepBasic, StepRecurrence, StepBilling,
StepConfirm). Manter API atual; migration interna.
</td>
<td><span class="pill pill-p1">P1</span></td>
<td>23 dias</td>
<td>Alto · manutenção</td>
</tr>
<tr>
<td>R6</td>
<td>
<strong>Trocar <code>eventoSelecionado</code> snapshot por
<code>selectedId</code> + computed</strong>. Elimina snapshot stale.
Replicar em outros refs do Melissa.
</td>
<td><span class="pill pill-p1">P1</span></td>
<td>12h</td>
<td>Médio · estabilidade</td>
</tr>
<tr>
<td>R7</td>
<td>
<strong>Auditoria visual da sessão</strong> — drawer com timeline
de records (pending/paid/cancelled), com cancelled colapsados por
default. Reduz poluição em <code>/financeiro</code>.
</td>
<td><span class="pill pill-p1">P1</span></td>
<td>1 dia</td>
<td>Médio · UX</td>
</tr>
<tr>
<td>R8</td>
<td>
<strong>RPCs server-side para increment/decrement de saldo</strong>
com <code>SELECT FOR UPDATE</code>. Elimina race condition em
fluxos rápidos.
</td>
<td><span class="pill pill-p1">P1</span></td>
<td>34h</td>
<td>Médio-alto · correctness</td>
</tr>
<tr>
<td>R9</td>
<td>
<strong>Mensagens de erro específicas</strong> em handlers de
status/billing. Catalogar erros típicos (RLS, constraint,
state inválido).
</td>
<td><span class="pill pill-p1">P1</span></td>
<td>2h</td>
<td>Médio · UX</td>
</tr>
<tr>
<td>R10</td>
<td>
<strong>TypeScript ou JSDoc tipado</strong> em
<code>normalizeForMelissa</code>, <code>pickDbFields</code> e funções
de transformação de dados. Lint flagga campos faltantes.
</td>
<td><span class="pill pill-p2">P2</span></td>
<td>24h por área</td>
<td>Alto longo prazo</td>
</tr>
<tr>
<td>R11</td>
<td>
<strong>Testes E2E (Playwright)</strong> dos 13 cenários. Substitui
bateria manual. CI roda em cada PR.
</td>
<td><span class="pill pill-p2">P2</span></td>
<td>1 semana</td>
<td>Alto · confiança</td>
</tr>
<tr>
<td>R12</td>
<td>
<strong>Unified action menu</strong> "Marcar sessão" substituindo
os 3 botões sobrepostos (Usar/Antecipar/Realizada).
</td>
<td><span class="pill pill-p2">P2</span></td>
<td>1 dia</td>
<td>Médio · clarity</td>
</tr>
<tr>
<td>R13</td>
<td>
<strong>Feature flags</strong> via <code>tenant_settings</code> ou
biblioteca específica. Permite rollout gradual de mudanças no dialog.
</td>
<td><span class="pill pill-p3">P3</span></td>
<td>1 dia</td>
<td>Baixo curto, alto longo</td>
</tr>
<tr>
<td>R14</td>
<td>
<strong><code>sessions_used</code> como VIEW computed</strong>
em vez de coluna incrementada. Elimina drift de sincronia.
Avaliar performance.
</td>
<td><span class="pill pill-p3">P3</span></td>
<td>12 dias</td>
<td>Alto correctness, médio risco</td>
</tr>
<tr>
<td>R15</td>
<td>
<strong>Ferramenta de "data fix"</strong> versionada para correções
administrativas em produção. Audit log obrigatório.
</td>
<td><span class="pill pill-p3">P3</span></td>
<td>23 dias</td>
<td>Alto operacional</td>
</tr>
</tbody>
</table>
<div class="callout callout-info">
<strong>Sequência sugerida:</strong> R2 (migration updated_at) → R1 (extrair
orchestrator) → R4 (doc invariantes) → R3 (audit Promise.allSettled) →
replicar Rail/Clínica. Tudo P0 entrega antes da próxima replicação reduz
drasticamente o risco de regressão.
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PÁGINA 9 — ROADMAP -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="page">
<h2 id="roadmap"><span class="h2-num">7</span>Roadmap proposto</h2>
<h3>Sprint imediato (12 semanas) — Consolidação P0</h3>
<ol>
<li>
<strong>Dia 1</strong> · R2 (migration <code>updated_at</code> +
trigger billing_contracts) · 30 min
</li>
<li>
<strong>Dia 12</strong> · R3 (audit Promise.allSettled) · 23h
<ul>
<li>Grep <code>Promise.allSettled</code> em todo src/</li>
<li>Reescrever blocos financeiros para awaits sequenciais</li>
<li>Logger por domínio</li>
</ul>
</li>
<li>
<strong>Dia 23</strong> · R4 (doc invariantes) · 12h
</li>
<li>
<strong>Dia 35</strong> · R1 (extrair orchestrator) · 46h
</li>
<li>
<strong>Dia 56</strong> · Replicar fixes pra Rail (AgendaTerapeutaPage)
via composable shared · 46h
</li>
<li>
<strong>Dia 67</strong> · Replicar fixes pra Clínica (AgendaClinicaPage)
· 46h
</li>
<li>
<strong>Dia 8</strong> · Bateria de teste manual nos 3 personas · 1 dia
</li>
</ol>
<h3>Sprint seguinte (23 semanas) — Refator P1</h3>
<ol>
<li>R5 — Refator <code>AgendaEventDialog</code> em 5 sub-componentes (23 dias)</li>
<li>R6 — Snapshot stale fix global (2h)</li>
<li>R7 — Drawer de audit history (1 dia)</li>
<li>R8 — RPCs increment/decrement com lock (34h)</li>
<li>R9 — Mensagens de erro catalogadas (2h)</li>
<li>Iteração de UX C12 (consolidar Usar/Antecipar/Realizada) · 1 dia</li>
</ol>
<h3>Próximo trimestre — Maturação P2</h3>
<ol>
<li>R10 — JSDoc tipado em normalizers (incremental por arquivo)</li>
<li>R11 — Testes E2E Playwright dos 13 cenários (1 semana)</li>
<li>R12 — Unified action menu (refator UX)</li>
<li>Auditoria de Edge Functions vs frontend (sprint dedicado)</li>
</ol>
<h3>Backlog longo prazo P3</h3>
<ul>
<li>R13 — Feature flags infrastructure</li>
<li>R14 — <code>sessions_used</code> como VIEW</li>
<li>R15 — Data-fix tooling</li>
<li>Migração TypeScript completa (decisão de produto)</li>
</ul>
<h2 style="margin-top:48px;">Considerações finais</h2>
<p>
O módulo de agenda é o <strong>coração operacional</strong> do AgênciaPSI —
é por onde o terapeuta vive o dia. Investimentos em estabilidade aqui têm
retorno desproporcional: cada bug evitado é uma sessão real do paciente
que não vira incidente.
</p>
<p>
A bateria de testes do dia <strong>provou que a metodologia funciona</strong>:
13 cenários documentados + bateria manual + iteração rápida descobriu mais
bugs em 1 dia do que toda a auditoria anterior. Manter esse loop e
automatizá-lo (R11) é o investimento de maior alavancagem.
</p>
<p>
O risco maior <strong>não é o que já encontramos</strong> — esses estão
consertados. É o que ainda não foi exercitado em Rail e Clínica. A janela
para consolidar antes de replicar é agora.
</p>
<p>
A boa notícia: a base é sólida. Os bugs encontrados foram <em>de execução</em>,
não <em>de design</em>. As decisões arquiteturais maiores (SimplePractice-style
invariantes, status change centralizado, audit trail em notes, separação
avulsa/pacote) <strong>provaram-se corretas em uso real</strong>. O trabalho
é polir, não reescrever.
</p>
<div class="signature">
<strong>Relatório gerado em 2026-05-20</strong> · Análise pós-bateria C1C13 · Sistema AgênciaPSI v5.0 ·
<br>
Baseado em: 15 commits do dia, 14 bugs corrigidos, 9 memórias de sessão,
~22k LOC inspecionadas nos hotspots.
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════ -->
<!-- PÁGINA 10 — APÊNDICES -->
<!-- ═══════════════════════════════════════════════════════════════ -->
<div class="page">
<h2 id="apendices"><span class="h2-num">8</span>Apêndices</h2>
<h3>A · Bugs corrigidos em 2026-05-20</h3>
<table class="severity-table">
<thead>
<tr>
<th>#</th>
<th>Bug</th>
<th>Severidade</th>
<th>Commit</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Cobrança dupla em multa: <code>_applyStatusDecisions</code> inseria
multa mas deixava original pending</td>
<td><span class="pill pill-critical">Crítico</span></td>
<td><code>d6423da</code></td>
</tr>
<tr>
<td>2</td>
<td><code>'fixed'</code> vs <code>'fixed_fee'</code> off-by-key em
<code>calcChargeAmount</code></td>
<td><span class="pill pill-medium">Médio (dormente)</span></td>
<td><code>d6423da</code></td>
</tr>
<tr>
<td>3</td>
<td>Label "Como cobrar?" com options "Já recebi" misturadas — usuário
marcou paid achando que era cobrar</td>
<td><span class="pill pill-high">Alto UX</span></td>
<td><code>079509e</code></td>
</tr>
<tr>
<td>4</td>
<td><code>billing_contracts.updated_at</code> inexistente causando
UPDATE silently falhar em <code>Promise.allSettled</code></td>
<td><span class="pill pill-critical">Crítico</span></td>
<td><code>16dfa02</code></td>
</tr>
<tr>
<td>5</td>
<td>Reverse transitions deixavam multa órfã (faltou→agendado)</td>
<td><span class="pill pill-high">Alto</span></td>
<td><code>5684297</code></td>
</tr>
<tr>
<td>6</td>
<td><code>consumeSaldo</code> não amarrava <code>billing_contract_id</code>
ao evento</td>
<td><span class="pill pill-high">Alto</span></td>
<td><code>3f3f2ac</code></td>
</tr>
<tr>
<td>7</td>
<td>Badge $ amber em sessão cancelada</td>
<td><span class="pill pill-medium">Médio</span></td>
<td><code>753182c</code></td>
</tr>
<tr>
<td>8</td>
<td><code>paymentLabel</code> usava <code>ev.price</code> em vez de
<code>paymentAmount</code> para pending</td>
<td><span class="pill pill-medium">Médio</span></td>
<td><code>753182c</code></td>
</tr>
<tr>
<td>9</td>
<td>"Gerar fatura" disponível em sessão encerrada</td>
<td><span class="pill pill-high">Alto</span></td>
<td><code>753182c</code></td>
</tr>
<tr>
<td>10</td>
<td><code>_reloadRange</code> não destruturado em
<code>_buildHandlers(deps)</code></td>
<td><span class="pill pill-medium">Médio</span></td>
<td><code>753182c</code></td>
</tr>
<tr>
<td>11</td>
<td>Faltou+multa-sem-consume não amarrava
<code>billing_contract_id</code></td>
<td><span class="pill pill-medium">Médio</span></td>
<td><code>5965b05</code></td>
</tr>
<tr>
<td>12</td>
<td>Re-antecipar reutilizava record cancelled (audit trail confuso)</td>
<td><span class="pill pill-high">Alto</span></td>
<td><code>b5e00a7</code></td>
</tr>
<tr>
<td>13</td>
<td>Popover snapshot stale após materialização virtual→real</td>
<td><span class="pill pill-high">Alto</span></td>
<td><code>b5e00a7</code> + <code>f83315b</code></td>
</tr>
<tr>
<td>14</td>
<td><code>normalizeForMelissa</code> não expunha <code>owner_id</code>
→ INSERT null violation</td>
<td><span class="pill pill-critical">Crítico</span></td>
<td><code>7d2a405</code></td>
</tr>
</tbody>
</table>
<h3>B · Pendências mapeadas em memória (pós-C13)</h3>
<ul>
<li><code>project_c12_antecipar_iterar</code> — iterar UX da antecipação (deferido pelo user)</li>
<li><code>project_melissa_popover_snapshot</code> — refator pra computed (mitigado mas não estrutural)</li>
<li><code>project_billing_contracts_no_updated_at</code> — gotcha documentado, fix em R2</li>
<li><code>project_pendencia_doc_ajuda</code> — doc completa para área de ajuda pós Fase 9</li>
<li>Cleanup: sessão Otto Rank 5364f631 com record pending leftover</li>
</ul>
<h3>C · Commits do dia (20/05)</h3>
<pre><code>4da0bc2 HANDOFF + log: C12 deferred · testando C13
f83315b agenda: popover watch acompanha transicao virtual->materializada
7d2a405 agenda: normalizeForMelissa expoe owner_id/tenant_id/contract_id
b5e00a7 agenda: popover sincroniza com eventos + antecipar nao reusa cancelled
272c804 agenda: revogar antecipacao de pagamento
00c4168 agenda: C12 prep — detectar paid pre-existente em pacote saldo realizada
9ead3fd HANDOFF + log: C11 fechado · 4/4 sub-testes OK · proximo C12
5965b05 agenda: link universal pacote + refresh saldo no reverse
45984e8 agenda: label + badge cientes de pacote em sessoes state=none
3f3f2ac agenda: consumeSaldo agora amarra billing_contract_id no evento
5684297 agenda: reverse transition trava (Agendada apos artefatos)
16dfa02 agenda pacote saldo: fix root cause + sequential awaits
079509e agenda: dialog pacote saldo realizada — 2 sub-questions claras
7dc7dce wiki: log session C10 fechado completo
1e74a11 HANDOFF: C10 fechado · 5/5 sub-testes OK · proximo C11
753182c agenda: C10 pos-test fixes + lock sessao encerrada + addendum doc
3caf579 agenda popover: botao Agendada + fixes pos-C10/B
d6423da agenda: pre-C10 fix _applyStatusDecisions cancela pendingRecord</code></pre>
<h3>D · Status dos cenários documentados</h3>
<table class="severity-table">
<thead>
<tr><th>#</th><th>Cenário</th><th>Persona testada</th><th>Status</th></tr>
</thead>
<tbody>
<tr><td>C1C9</td><td>Bloqueio, avulsas, recorrência, pacote, per_session</td><td>Melissa</td><td><span class="pill pill-good">OK (anteriormente)</span></td></tr>
<tr><td>C10</td><td>Status change avulsa (5 sub-testes)</td><td>Melissa</td><td><span class="pill pill-good">OK</span></td></tr>
<tr><td>C11</td><td>Status change pacote saldo (4 sub-testes)</td><td>Melissa</td><td><span class="pill pill-good">OK</span></td></tr>
<tr><td>C12</td><td>Antecipar pagamento</td><td>Melissa</td><td><span class="pill pill-medium">DB OK · UX a iterar</span></td></tr>
<tr><td>C13</td><td>Edit cobrada (imutabilidade)</td><td>Melissa</td><td><span class="pill pill-info">Em teste agora</span></td></tr>
<tr><td></td><td>Rail (AgendaTerapeutaPage)</td><td>Rail</td><td><span class="pill pill-critical">Pendente — após refator</span></td></tr>
<tr><td></td><td>Clínica (AgendaClinicaPage)</td><td>Clínica</td><td><span class="pill pill-critical">Pendente — após refator</span></td></tr>
</tbody>
</table>
<h3>E · Referências internas</h3>
<ul>
<li><code>src/docs/agenda-compromisso-financeiro-cenarios.html</code> — doc vivo dos 13 cenários (com addendum C10)</li>
<li><code>HANDOFF.md</code> — estado de continuidade da sessão</li>
<li><code>Obsidian/Brain/log.md</code> — log cronológico</li>
<li><code>Obsidian/Brain/wiki/agenda-compromisso-fluxo.md</code> — fluxo conceitual</li>
<li><code>~/.claude/projects/.../memory/MEMORY.md</code> — índice de memórias persistentes</li>
</ul>
<div class="signature">
Fim do relatório · 10 páginas · Geração local sem dependências externas ·
Para PDF, abrir no browser e <strong>Ctrl+P → Salvar como PDF</strong>.
</div>
</div>
</body>
</html>