5b345c5598
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>
1446 lines
58 KiB
HTML
1446 lines
58 KiB
HTML
<!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 C1–C13 · 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 (P0–P3)</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 >1.000 LOC é candidato a refator; >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 25–35 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 < 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>4–6h</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>2–3h</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>1–2h</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>2–3 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>1–2h</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>3–4h</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>2–4h 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>1–2 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>2–3 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 (1–2 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 1–2</strong> · R3 (audit Promise.allSettled) · 2–3h
|
||
<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 2–3</strong> · R4 (doc invariantes) · 1–2h
|
||
</li>
|
||
<li>
|
||
<strong>Dia 3–5</strong> · R1 (extrair orchestrator) · 4–6h
|
||
</li>
|
||
<li>
|
||
<strong>Dia 5–6</strong> · Replicar fixes pra Rail (AgendaTerapeutaPage)
|
||
via composable shared · 4–6h
|
||
</li>
|
||
<li>
|
||
<strong>Dia 6–7</strong> · Replicar fixes pra Clínica (AgendaClinicaPage)
|
||
· 4–6h
|
||
</li>
|
||
<li>
|
||
<strong>Dia 8</strong> · Bateria de teste manual nos 3 personas · 1 dia
|
||
</li>
|
||
</ol>
|
||
|
||
<h3>Sprint seguinte (2–3 semanas) — Refator P1</h3>
|
||
<ol>
|
||
<li>R5 — Refator <code>AgendaEventDialog</code> em 5 sub-componentes (2–3 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 (3–4h)</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 C1–C13 · 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>C1–C9</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>
|