Documentos Pacientes, Template Documentos Pacientes Saas, Documentos prontuários, Documentos Externos, Visualização Externa, Permissão de Visualização, Render Otimização
This commit is contained in:
@@ -0,0 +1,766 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Documentos & Arquivos — Status de Implementacao</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0d1117;
|
||||
--bg-card: #161b22;
|
||||
--bg-card-hover: #1c2129;
|
||||
--border: #30363d;
|
||||
--border-light: #21262d;
|
||||
--text: #e6edf3;
|
||||
--text-secondary: #8b949e;
|
||||
--text-muted: #6e7681;
|
||||
--accent: #58a6ff;
|
||||
--accent-dim: #1f6feb33;
|
||||
--green: #3fb950;
|
||||
--green-dim: #23863633;
|
||||
--orange: #d29922;
|
||||
--orange-dim: #9e6a0333;
|
||||
--red: #f85149;
|
||||
--red-dim: #da363333;
|
||||
--purple: #bc8cff;
|
||||
--purple-dim: #8957e533;
|
||||
--cyan: #39d2c0;
|
||||
--cyan-dim: #1b7c6e33;
|
||||
--pink: #f778ba;
|
||||
--pink-dim: #db61a233;
|
||||
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
--font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
--radius: 8px;
|
||||
--radius-lg: 12px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--font);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
padding: 32px 24px 64px;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ── Header ─────────────────────────── */
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 36px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.page-header h1 {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.page-header .subtitle {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.page-header .meta {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.meta-tag {
|
||||
font-size: 11px;
|
||||
font-family: var(--font-mono);
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
/* ── Summary stats ─────────────────── */
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 8px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
.summary-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.summary-card .number { font-size: 24px; font-weight: 700; line-height: 1.2; }
|
||||
.summary-card .label { font-size: 10px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 2px; }
|
||||
.c-green .number { color: var(--green); }
|
||||
.c-orange .number { color: var(--orange); }
|
||||
.c-red .number { color: var(--red); }
|
||||
.c-accent .number { color: var(--accent); }
|
||||
.c-purple .number { color: var(--purple); }
|
||||
.c-cyan .number { color: var(--cyan); }
|
||||
|
||||
/* ── Section ────────────────────────── */
|
||||
.section {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
.section-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.section-icon.blue { background: var(--accent-dim); color: var(--accent); }
|
||||
.section-icon.green { background: var(--green-dim); color: var(--green); }
|
||||
.section-icon.orange { background: var(--orange-dim); color: var(--orange); }
|
||||
.section-icon.purple { background: var(--purple-dim); color: var(--purple); }
|
||||
.section-icon.cyan { background: var(--cyan-dim); color: var(--cyan); }
|
||||
.section-icon.pink { background: var(--pink-dim); color: var(--pink); }
|
||||
.section-icon.red { background: var(--red-dim); color: var(--red); }
|
||||
|
||||
.section-title { font-size: 16px; font-weight: 600; }
|
||||
|
||||
/* ── Layout: cards + sidebar ────────── */
|
||||
.content-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 260px;
|
||||
gap: 14px;
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
.content-row { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* ── Cards ──────────────────────────── */
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 16px 18px;
|
||||
transition: border-color .15s;
|
||||
}
|
||||
.card:hover { border-color: #484f58; }
|
||||
.card-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.card-desc {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.card-fields {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.field {
|
||||
font-size: 10px;
|
||||
font-family: var(--font-mono);
|
||||
padding: 2px 7px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-muted);
|
||||
background: var(--bg);
|
||||
}
|
||||
.card-file {
|
||||
font-size: 11px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--accent);
|
||||
margin-top: 6px;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
/* ── Badges ─────────────────────────── */
|
||||
.badge {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
padding: 2px 9px;
|
||||
border-radius: 20px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
.badge-done { background: var(--green-dim); color: var(--green); }
|
||||
.badge-partial { background: var(--orange-dim); color: var(--orange); }
|
||||
.badge-pending { background: var(--red-dim); color: var(--red); }
|
||||
.badge-db { background: var(--accent-dim); color: var(--accent); }
|
||||
|
||||
/* ── Sidebar ────────────────────────── */
|
||||
.sidebar {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 16px;
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
}
|
||||
.sidebar-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
.sidebar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 5px 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.sidebar-item .dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.dot-green { background: var(--green); }
|
||||
.dot-orange { background: var(--orange); }
|
||||
.dot-red { background: var(--red); }
|
||||
.sidebar-item .label { flex: 1; }
|
||||
.sidebar-item .status {
|
||||
font-size: 10px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.sidebar-divider {
|
||||
height: 1px;
|
||||
background: var(--border-light);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* ── Note box ──────────────────────── */
|
||||
.note {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--accent);
|
||||
border-radius: var(--radius);
|
||||
padding: 12px 16px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.note strong { color: var(--text); }
|
||||
.note.warn { border-left-color: var(--orange); }
|
||||
|
||||
/* ── Legend ─────────────────────────── */
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
justify-content: center;
|
||||
margin-bottom: 28px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.legend-item .dot { width: 8px; height: 8px; border-radius: 50%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ══════════════════════════════════ HEADER ══════════════════════════════════ -->
|
||||
<div class="page-header">
|
||||
<h1>Documentos & Arquivos</h1>
|
||||
<div class="subtitle">Status de implementacao confrontado com o banco de dados</div>
|
||||
<div class="meta">
|
||||
<span class="meta-tag">AgenciaPsi v5</span>
|
||||
<span class="meta-tag">Vue 3 + Supabase</span>
|
||||
<span class="meta-tag">Atualizado: 2026-03-30</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════ STATS ═══════════════════════════════════ -->
|
||||
<div class="summary-grid">
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">6/6</div>
|
||||
<div class="label">Tabelas</div>
|
||||
</div>
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">2/2</div>
|
||||
<div class="label">Buckets</div>
|
||||
</div>
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">7/7</div>
|
||||
<div class="label">Services</div>
|
||||
</div>
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">3/3</div>
|
||||
<div class="label">Composables</div>
|
||||
</div>
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">10/10</div>
|
||||
<div class="label">Componentes</div>
|
||||
</div>
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">14</div>
|
||||
<div class="label">Templates seed</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════ LEGENDA ═════════════════════════════════ -->
|
||||
<div class="legend">
|
||||
<div class="legend-item"><span class="dot dot-green"></span> Implementado</div>
|
||||
<div class="legend-item"><span class="dot dot-orange"></span> Parcial / Migration pendente</div>
|
||||
<div class="legend-item"><span class="dot dot-red"></span> Nao implementado</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════ 1. UPLOAD & ORGANIZACAO ═════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon blue">1</div>
|
||||
<div class="section-title">Upload & Organizacao de Arquivos</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Upload de arquivo ao paciente</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">PDF, imagem, DOCX. Vinculado ao patient_id. Supabase Storage com path estruturado. Drag & drop + seletor. Validacao de tamanho (50MB) e tipo MIME.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">patient_id</span><span class="field">bucket_path</span><span class="field">storage_bucket</span>
|
||||
<span class="field">nome_original</span><span class="field">mime_type</span><span class="field">tamanho_bytes</span>
|
||||
<span class="field">uploaded_by</span><span class="field">uploaded_at</span>
|
||||
</div>
|
||||
<div class="card-file">Documents.service.js → uploadDocument()</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Tipo, categoria & tags</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">11 tipos (laudo, receita, exame, atestado, declaracao, recibo, etc.). Categoria livre. Tags[] com autocomplete. Filtros na listagem.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">tipo_documento</span><span class="field">categoria</span><span class="field">descricao</span><span class="field">tags[]</span>
|
||||
</div>
|
||||
<div class="card-file">DB: CHECK constraint + GIN index em tags</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Vinculo com sessao</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Arquivo linkado a agenda_eventos (sessao) ou session_note. Colunas nullable — nem todo arquivo tem sessao.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">agenda_evento_id</span><span class="field">session_note_id</span>
|
||||
</div>
|
||||
<div class="card-file">DB: FK para agenda_eventos (ON DELETE SET NULL)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Visibilidade & controle de acesso</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Privado, compartilhado com supervisor, ou visivel no portal do paciente. Granular por arquivo. Expiracao de compartilhamento.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">visibilidade</span><span class="field">compartilhado_portal</span><span class="field">compartilhado_supervisor</span>
|
||||
<span class="field">compartilhado_em</span><span class="field">expira_compartilhamento</span>
|
||||
</div>
|
||||
<div class="card-file">DB: CHECK (privado | compartilhado_supervisor | compartilhado_portal)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Soft delete com retencao LGPD</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Arquivo "excluido" some da UI mas fica retido por 5 anos (CFP). Colunas de controle + index parcial para listagem ativa.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">deleted_at</span><span class="field">deleted_by</span><span class="field">retencao_ate</span>
|
||||
</div>
|
||||
<div class="card-file">DB: idx_documents_active (WHERE deleted_at IS NULL)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Preview & download</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Preview inline de PDF e imagens via dialog. Download com URL assinada (60s). Suporte a storage_bucket dinamico (documents ou generated-docs).</div>
|
||||
<div class="card-file">DocumentPreviewDialog.vue + getDownloadUrl(path, expires, bucket)</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Banco de Dados</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">documents</span><span class="status">27 cols</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">RLS owner_id</span><span class="status">ativo</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">Indexes</span><span class="status">9</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">Trigger timeline</span><span class="status">insert</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Storage</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">documents</span><span class="status">50MB</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">generated-docs</span><span class="status">20MB</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Frontend</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">DocumentsListPage</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">DocumentCard</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">DocumentUploadDialog</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">DocumentPreviewDialog</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">DocumentTagsInput</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">useDocuments.js</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════ 2. GERACAO DE DOCUMENTOS ════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon green">2</div>
|
||||
<div class="section-title">Geracao de Documentos (PDF)</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Templates de documentos</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">16 tipos de template. 14 templates globais no seed. Corpo HTML com {{variaveis}}. Cabecalho/rodape personalizaveis. Templates por tenant + globais do sistema.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">nome_template</span><span class="field">tipo</span><span class="field">corpo_html</span>
|
||||
<span class="field">cabecalho_html</span><span class="field">rodape_html</span><span class="field">variaveis[]</span>
|
||||
<span class="field">is_global</span><span class="field">logo_url</span><span class="field">ativo</span>
|
||||
</div>
|
||||
<div class="card-file">document_templates (DB) + seed_015_document_templates.sql</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Geracao de PDF (client-side)</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">jsPDF + html2canvas-pro (substituiu pdfmake por incompatibilidade com Vite). Renderiza HTML preenchido em canvas, converte para PDF A4 com paginacao. JPEG 85%, scale 1.5. ~200-400KB por documento.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">buildFullHtml()</span><span class="field">htmlToPdfBlob()</span><span class="field">fillTemplate()</span>
|
||||
</div>
|
||||
<div class="card-file">pdf.service.js + DocumentGenerate.service.js</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Documento gerado (instancia + listagem)</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Cada PDF gerado: salva snapshot em document_generated (dados preenchidos para auditoria) E automaticamente registra na tabela documents (para aparecer na listagem do paciente). Bucket: generated-docs. Nomes sanitizados (sem acentos).</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">template_id</span><span class="field">dados_preenchidos</span><span class="field">pdf_path</span>
|
||||
<span class="field">gerado_em</span><span class="field">gerado_por</span><span class="field">→ documents</span>
|
||||
</div>
|
||||
<div class="card-file">saveGeneratedDocument() → document_generated + documents</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Fluxo de geracao (UI)</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Dialog 3 etapas: selecionar template → editar variaveis (auto-preenchidas com dados paciente/sessao/terapeuta/clinica) → preview via iframe sandbox → "Salvar documento" (online) ou "So baixar" (local).</div>
|
||||
<div class="card-file">DocumentGenerateDialog.vue + useDocumentGenerate.js</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Dados da clinica no template</div>
|
||||
<span class="badge badge-partial">parcial</span>
|
||||
</div>
|
||||
<div class="card-desc">loadClinicData() usa select('*') na tabela tenants. Atualmente so retorna name. Campos phone, contact_email, logradouro, numero, bairro, cidade, estado dependem da migration 003_tenants_address_fields.sql ser aplicada.</div>
|
||||
<div class="card-file">Migration pendente: 003_tenants_address_fields.sql</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Editor de templates</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Editor rich text para corpo HTML. Insercao de variaveis via dropdown. Preview ao vivo. Config de cabecalho/rodape/logo. Gestao de templates globais e por tenant.</div>
|
||||
<div class="card-file">DocumentTemplateEditor.vue + DocumentTemplatesPage.vue</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Banco de Dados</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">document_templates</span><span class="status">15 cols</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">document_generated</span><span class="status">10 cols</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">RLS</span><span class="status">ativo</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">14 seeds globais</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Motor PDF</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">jsPDF</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">html2canvas-pro</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-red"></span><span class="label">pdfmake</span><span class="status">removido</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Pendencias</div>
|
||||
<div class="sidebar-item"><span class="dot dot-orange"></span><span class="label">tenants address</span><span class="status">migration</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-orange"></span><span class="label">terapeuta_crp</span><span class="status">campo</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════ 3. ASSINATURA ELETRONICA ════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon purple">3</div>
|
||||
<div class="section-title">Assinatura Eletronica</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> TCLE & consentimento</div>
|
||||
<span class="badge badge-done">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Tabela document_signatures com rastreamento completo: IP, timestamp, hash SHA-256, user_agent. Suporte a 3 tipos de signatario (paciente, responsavel_legal, terapeuta). 5 status possiveis.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">documento_id</span><span class="field">signatario_tipo</span><span class="field">signatario_id</span>
|
||||
<span class="field">ordem</span><span class="field">status</span><span class="field">ip</span>
|
||||
<span class="field">hash_documento</span><span class="field">assinado_em</span>
|
||||
</div>
|
||||
<div class="card-file">document_signatures (DB) + DocumentSignatures.service.js</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> UI de assinatura</div>
|
||||
<span class="badge badge-partial">parcial</span>
|
||||
</div>
|
||||
<div class="card-desc">Componente DocumentSignatureDialog.vue existe. Service DocumentSignatures.service.js existe. Fluxo completo de envio por link e assinatura pelo paciente ainda precisa ser validado end-to-end.</div>
|
||||
<div class="card-file">DocumentSignatureDialog.vue</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Banco de Dados</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">document_signatures</span><span class="status">14 cols</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">RLS tenant</span><span class="status">ativo</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">Trigger timeline</span><span class="status">assinado</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Frontend</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">SignatureDialog</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">Signatures.service</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-orange"></span><span class="label">Fluxo e2e</span><span class="status">validar</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════ 4. COMPARTILHAMENTO ═════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon cyan">4</div>
|
||||
<div class="section-title">Compartilhamento & Portal do Paciente</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Links temporarios de acesso</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Token hex 32 bytes, prazo de expiracao, limite de usos. RLS publica por token valido. Link seguro sem necessidade de login.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">token</span><span class="field">expira_em</span><span class="field">usos_max</span>
|
||||
<span class="field">usos</span><span class="field">ativo</span><span class="field">criado_por</span>
|
||||
</div>
|
||||
<div class="card-file">document_share_links (DB) + DocumentShareLinks.service.js</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Documentos compartilhados com paciente</div>
|
||||
<span class="badge badge-done">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Terapeuta decide quais arquivos ficam visiveis pro paciente. Campos compartilhado_portal e expira_compartilhamento na tabela documents.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">compartilhado_portal</span><span class="field">compartilhado_em</span><span class="field">expira_compartilhamento</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Upload pelo paciente</div>
|
||||
<span class="badge badge-done">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Paciente envia exames/laudos pelo portal. Fila de "pendentes de revisao" para o terapeuta aprovar.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">enviado_pelo_paciente</span><span class="field">status_revisao</span>
|
||||
<span class="field">revisado_por</span><span class="field">revisado_em</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Banco de Dados</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">document_share_links</span><span class="status">10 cols</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">RLS token publico</span><span class="status">ativo</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Frontend</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">ShareDialog</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">ShareLinks.service</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">SharedDocumentPage</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════ 5. AUDITORIA ═══════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon red">5</div>
|
||||
<div class="section-title">Auditoria & Conformidade</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Log de acesso a arquivos</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Tabela imutavel (somente INSERT + SELECT, sem UPDATE/DELETE). Cada visualizacao ou download registrado. Conformidade CFP e LGPD. Integrado no composable useDocuments (logAccess automatico).</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">documento_id</span><span class="field">acao</span><span class="field">user_id</span>
|
||||
<span class="field">ip</span><span class="field">user_agent</span><span class="field">acessado_em</span>
|
||||
</div>
|
||||
<div class="card-file">document_access_logs (DB) + DocumentAuditLog.service.js</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Timeline do paciente</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Triggers automaticos registram na patient_timeline quando: documento uploadado (INSERT em documents) e documento assinado (UPDATE em document_signatures).</div>
|
||||
<div class="card-file">DB Triggers: trg_documents_timeline_insert + trg_ds_timeline</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Banco de Dados</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">document_access_logs</span><span class="status">8 cols</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">Imutavel</span><span class="status">no UPDATE</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">RLS tenant</span><span class="status">ativo</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Acoes rastreadas</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">visualizou</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">baixou</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">imprimiu</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">compartilhou</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">assinou</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════ PENDENCIAS ══════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon orange">!</div>
|
||||
<div class="section-title">Pendencias & Migrations Nao Aplicadas</div>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Migration: tenants address fields</div>
|
||||
<span class="badge badge-partial">pendente</span>
|
||||
</div>
|
||||
<div class="card-desc">003_tenants_address_fields.sql — adiciona cep, logradouro, numero, complemento, bairro, cidade, estado a tabela tenants. Tambem faltam phone e contact_email. Necessario para preencher variaveis clinica_endereco, clinica_telefone nos templates.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Campo CRP do terapeuta</div>
|
||||
<span class="badge badge-partial">pendente</span>
|
||||
</div>
|
||||
<div class="card-desc">Variavel terapeuta_crp nos templates retorna vazio. O campo CRP nao existe na tabela profiles nem em tenant_members. Precisa de migration para adicionar coluna crp em profiles.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Fluxo de assinatura end-to-end</div>
|
||||
<span class="badge badge-partial">validar</span>
|
||||
</div>
|
||||
<div class="card-desc">Tabela, service e componente existem. Falta validar: envio de link por email/whatsapp, pagina publica de assinatura, registro de IP/hash, notificacao ao terapeuta quando assinado.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Portal do paciente — visualizacao de docs</div>
|
||||
<span class="badge badge-partial">validar</span>
|
||||
</div>
|
||||
<div class="card-desc">Campos compartilhado_portal e visibilidade existem no banco. SharedDocumentPage.vue existe. Falta validar se o portal do paciente (CadastroPacienteExterno) exibe corretamente os documentos compartilhados.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="note warn" style="margin-top: 14px;">
|
||||
<strong>Decisao tecnica — Motor PDF:</strong> pdfmake foi substituido por jsPDF + html2canvas-pro. O pdfmake (UMD) trava silenciosamente com Vite (ESM) — createPdf().getBlob()/getBuffer() nunca retornam. html2canvas-pro e um fork open source (MIT) com suporte a cores oklch usadas pelo PrimeVue/Tailwind.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,870 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Plano de Implementacao — Modulo Documentos & Arquivos</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0d1117;
|
||||
--bg-card: #161b22;
|
||||
--bg-card-hover: #1c2129;
|
||||
--bg-table-head: #1c2129;
|
||||
--bg-table-row: #161b22;
|
||||
--bg-table-row-alt: #0d1117;
|
||||
--border: #30363d;
|
||||
--border-light: #21262d;
|
||||
--text: #e6edf3;
|
||||
--text-secondary: #8b949e;
|
||||
--text-muted: #6e7681;
|
||||
--accent: #58a6ff;
|
||||
--accent-dim: #1f6feb33;
|
||||
--green: #3fb950;
|
||||
--green-dim: #23863633;
|
||||
--orange: #d29922;
|
||||
--orange-dim: #9e6a0333;
|
||||
--purple: #bc8cff;
|
||||
--purple-dim: #8957e533;
|
||||
--red: #f85149;
|
||||
--red-dim: #da363333;
|
||||
--cyan: #39d2c0;
|
||||
--cyan-dim: #1b7c6e33;
|
||||
--pink: #f778ba;
|
||||
--pink-dim: #db61a233;
|
||||
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
--font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
--radius: 8px;
|
||||
--radius-lg: 12px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--font);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
padding: 32px 24px 64px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ── Header ─────────────────────────── */
|
||||
.page-header {
|
||||
margin-bottom: 40px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.page-header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.page-header .subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.page-header .meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.meta-tag {
|
||||
font-size: 12px;
|
||||
font-family: var(--font-mono);
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
/* ── Summary cards ──────────────────── */
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 10px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.summary-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px 16px;
|
||||
text-align: center;
|
||||
}
|
||||
.summary-card .number {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.summary-card .label {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.summary-card.c-blue .number { color: var(--accent); }
|
||||
.summary-card.c-green .number { color: var(--green); }
|
||||
.summary-card.c-orange .number { color: var(--orange); }
|
||||
.summary-card.c-purple .number { color: var(--purple); }
|
||||
.summary-card.c-cyan .number { color: var(--cyan); }
|
||||
.summary-card.c-pink .number { color: var(--pink); }
|
||||
|
||||
/* ── Sections ───────────────────────── */
|
||||
.section {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
.section-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.section-icon.blue { background: var(--accent-dim); color: var(--accent); }
|
||||
.section-icon.green { background: var(--green-dim); color: var(--green); }
|
||||
.section-icon.orange { background: var(--orange-dim); color: var(--orange); }
|
||||
.section-icon.purple { background: var(--purple-dim); color: var(--purple); }
|
||||
.section-icon.cyan { background: var(--cyan-dim); color: var(--cyan); }
|
||||
.section-icon.pink { background: var(--pink-dim); color: var(--pink); }
|
||||
.section-icon.red { background: var(--red-dim); color: var(--red); }
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.section-desc {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: -6px;
|
||||
margin-bottom: 14px;
|
||||
padding-left: 38px;
|
||||
}
|
||||
|
||||
/* ── Tables ─────────────────────────── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
thead th {
|
||||
background: var(--bg-table-head);
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
padding: 10px 14px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
tbody td {
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
vertical-align: top;
|
||||
}
|
||||
tbody tr:nth-child(odd) { background: var(--bg-table-row); }
|
||||
tbody tr:nth-child(even) { background: var(--bg-table-row-alt); }
|
||||
tbody tr:hover { background: var(--bg-card-hover); }
|
||||
tbody tr:last-child td { border-bottom: none; }
|
||||
|
||||
.col-file {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--accent);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.col-table {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--green);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.col-route {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--orange);
|
||||
}
|
||||
.col-key {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--purple);
|
||||
}
|
||||
.col-bucket {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
/* ── Field chips ────────────────────── */
|
||||
.fields {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.field {
|
||||
font-size: 10px;
|
||||
font-family: var(--font-mono);
|
||||
padding: 2px 7px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-muted);
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
/* ── Notes ──────────────────────────── */
|
||||
.note {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--accent);
|
||||
border-radius: var(--radius);
|
||||
padding: 12px 16px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ── Responsive ─────────────────────── */
|
||||
@media (max-width: 700px) {
|
||||
body { padding: 16px 12px 40px; }
|
||||
.summary-grid { grid-template-columns: repeat(3, 1fr); }
|
||||
table { font-size: 12px; }
|
||||
thead th, tbody td { padding: 8px 10px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ════════════════════════════════════════ HEADER ════════════════════════════════════════ -->
|
||||
<div class="page-header">
|
||||
<h1>Plano de Implementacao — Documentos & Arquivos</h1>
|
||||
<div class="subtitle">Modulo completo: upload, templates, geracao PDF, assinatura eletronica, portal do paciente, auditoria</div>
|
||||
<div class="meta">
|
||||
<span class="meta-tag">AgenciaPsi v5</span>
|
||||
<span class="meta-tag">Vue 3 + Supabase</span>
|
||||
<span class="meta-tag">2026-03-30</span>
|
||||
<span class="meta-tag">Status: em andamento</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ RESUMO ═══════════════════════════════════════ -->
|
||||
<div class="summary-grid">
|
||||
<div class="summary-card c-blue">
|
||||
<div class="number">6</div>
|
||||
<div class="label">Tabelas</div>
|
||||
</div>
|
||||
<div class="summary-card c-cyan">
|
||||
<div class="number">2</div>
|
||||
<div class="label">Buckets</div>
|
||||
</div>
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">7</div>
|
||||
<div class="label">Services</div>
|
||||
</div>
|
||||
<div class="summary-card c-orange">
|
||||
<div class="number">3</div>
|
||||
<div class="label">Composables</div>
|
||||
</div>
|
||||
<div class="summary-card c-purple">
|
||||
<div class="number">~10</div>
|
||||
<div class="label">Componentes</div>
|
||||
</div>
|
||||
<div class="summary-card c-pink">
|
||||
<div class="number">5</div>
|
||||
<div class="label">Feature flags</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ 1. BANCO ═════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon blue">1</div>
|
||||
<div class="section-title">Banco de Dados — Migrations</div>
|
||||
</div>
|
||||
<div class="section-desc">Tabelas, RLS policies, indexes, triggers</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Migration</th>
|
||||
<th>Tabela / Objeto</th>
|
||||
<th>O que faz</th>
|
||||
<th>Campos principais</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-file" rowspan="4">005_create_documents_tables.sql</td>
|
||||
<td class="col-table">documents</td>
|
||||
<td>Arquivo vinculado a paciente. Path no Supabase Storage, tipo/categoria, visibilidade, tags, soft delete com retencao LGPD. Tabela central do modulo. O campo storage_bucket indica qual bucket do Storage contem o arquivo (documents ou generated-docs), permitindo que PDFs gerados aparecam na mesma listagem.</td>
|
||||
<td>
|
||||
<div class="fields">
|
||||
<span class="field">id</span>
|
||||
<span class="field">patient_id</span>
|
||||
<span class="field">tenant_id</span>
|
||||
<span class="field">owner_id</span>
|
||||
<span class="field">bucket_path</span>
|
||||
<span class="field">storage_bucket</span>
|
||||
<span class="field">nome_original</span>
|
||||
<span class="field">mime_type</span>
|
||||
<span class="field">tamanho_bytes</span>
|
||||
<span class="field">tipo_documento</span>
|
||||
<span class="field">categoria</span>
|
||||
<span class="field">descricao</span>
|
||||
<span class="field">tags[]</span>
|
||||
<span class="field">visibilidade</span>
|
||||
<span class="field">compartilhado_portal</span>
|
||||
<span class="field">compartilhado_supervisor</span>
|
||||
<span class="field">agenda_evento_id</span>
|
||||
<span class="field">session_note_id</span>
|
||||
<span class="field">enviado_pelo_paciente</span>
|
||||
<span class="field">status_revisao</span>
|
||||
<span class="field">revisado_por</span>
|
||||
<span class="field">revisado_em</span>
|
||||
<span class="field">uploaded_by</span>
|
||||
<span class="field">uploaded_at</span>
|
||||
<span class="field">deleted_at</span>
|
||||
<span class="field">deleted_by</span>
|
||||
<span class="field">retencao_ate</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-table">document_access_logs</td>
|
||||
<td>Log imutavel de quem visualizou ou baixou cada arquivo. Conformidade CFP e LGPD. Sem UPDATE/DELETE — somente INSERT e SELECT.</td>
|
||||
<td>
|
||||
<div class="fields">
|
||||
<span class="field">id</span>
|
||||
<span class="field">documento_id</span>
|
||||
<span class="field">acao</span>
|
||||
<span class="field">user_id</span>
|
||||
<span class="field">ip</span>
|
||||
<span class="field">user_agent</span>
|
||||
<span class="field">acessado_em</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-table">document_signatures</td>
|
||||
<td>Assinaturas eletronicas. Cada signatario (paciente, responsavel, terapeuta) tem seu registro com IP, timestamp e hash do documento.</td>
|
||||
<td>
|
||||
<div class="fields">
|
||||
<span class="field">id</span>
|
||||
<span class="field">documento_id</span>
|
||||
<span class="field">signatario_tipo</span>
|
||||
<span class="field">signatario_id</span>
|
||||
<span class="field">ordem</span>
|
||||
<span class="field">status</span>
|
||||
<span class="field">ip</span>
|
||||
<span class="field">user_agent</span>
|
||||
<span class="field">assinado_em</span>
|
||||
<span class="field">hash_documento</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-table">document_share_links</td>
|
||||
<td>Links temporarios assinados para compartilhar documento com profissional externo sem conta no sistema. Prazo e limite de usos.</td>
|
||||
<td>
|
||||
<div class="fields">
|
||||
<span class="field">id</span>
|
||||
<span class="field">documento_id</span>
|
||||
<span class="field">token</span>
|
||||
<span class="field">expira_em</span>
|
||||
<span class="field">usos_max</span>
|
||||
<span class="field">usos</span>
|
||||
<span class="field">criado_por</span>
|
||||
<span class="field">criado_em</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file" rowspan="2">006_create_document_templates.sql</td>
|
||||
<td class="col-table">document_templates</td>
|
||||
<td>Templates de documentos (declaracao de comparecimento, atestado, recibo etc.). Corpo HTML com variaveis. Templates globais do sistema + personalizados por tenant com logo/cabecalho.</td>
|
||||
<td>
|
||||
<div class="fields">
|
||||
<span class="field">id</span>
|
||||
<span class="field">tenant_id</span>
|
||||
<span class="field">nome_template</span>
|
||||
<span class="field">tipo</span>
|
||||
<span class="field">corpo_html</span>
|
||||
<span class="field">variaveis[]</span>
|
||||
<span class="field">is_global</span>
|
||||
<span class="field">owner_id</span>
|
||||
<span class="field">logo_url</span>
|
||||
<span class="field">cabecalho_html</span>
|
||||
<span class="field">rodape_html</span>
|
||||
<span class="field">ativo</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-table">document_generated</td>
|
||||
<td>Cada PDF gerado a partir de um template. Guarda os dados usados no preenchimento e o path do PDF resultante no Storage.</td>
|
||||
<td>
|
||||
<div class="fields">
|
||||
<span class="field">id</span>
|
||||
<span class="field">template_id</span>
|
||||
<span class="field">patient_id</span>
|
||||
<span class="field">tenant_id</span>
|
||||
<span class="field">dados_preenchidos</span>
|
||||
<span class="field">pdf_path</span>
|
||||
<span class="field">gerado_em</span>
|
||||
<span class="field">gerado_por</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ 2. STORAGE ═══════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon cyan">2</div>
|
||||
<div class="section-title">Supabase Storage — Buckets</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bucket</th>
|
||||
<th>Uso</th>
|
||||
<th>Path pattern</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-bucket">documents</td>
|
||||
<td>Arquivos enviados por terapeuta ou paciente (PDF, imagem, DOCX, etc.)</td>
|
||||
<td class="col-file">{tenant_id}/{patient_id}/{timestamp}-{filename}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-bucket">generated-docs</td>
|
||||
<td>PDFs gerados pelo sistema a partir de templates. Referenciado tanto por document_generated (snapshot) quanto por documents (listagem do paciente) via campo storage_bucket.</td>
|
||||
<td class="col-file">{tenant_id}/{patient_id}/{template_nome_sanitizado}_{timestamp}.pdf</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ 3. SERVICES ══════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon green">3</div>
|
||||
<div class="section-title">Services — Camada de dados</div>
|
||||
</div>
|
||||
<div class="section-desc">src/services/ — seguem o padrao Medicos.service.js (getOwnerId + getActiveTenantId + CRUD)</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Arquivo</th>
|
||||
<th>O que faz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-file">Documents.service.js</td>
|
||||
<td>CRUD completo de documentos: upload ao Storage + insert no banco, listagem por paciente com filtros (tipo, categoria, tags), soft delete com retencao, restauracao, download com URL assinada</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentTemplates.service.js</td>
|
||||
<td>CRUD de templates: criar/editar templates (globais e por tenant), listar variaveis disponiveis, duplicar template, ativar/desativar</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentGenerate.service.js</td>
|
||||
<td>Gerar PDF a partir de template: preencher variaveis com dados do paciente/sessao, renderizar HTML para PDF via pdf.service.js (jsPDF + html2canvas-pro), salvar no bucket generated-docs, registrar em document_generated E automaticamente na tabela documents (para aparecer na listagem do paciente). Nomes de arquivo sanitizados (sem acentos) para compatibilidade com Supabase Storage.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">pdf.service.js</td>
|
||||
<td>Servico de geracao de PDF client-side usando jsPDF + html2canvas-pro. Substitui pdfmake que apresenta incompatibilidade com Vite (UMD vs ESM — getBlob/getBuffer travam silenciosamente). Recebe HTML completo, renderiza em canvas oculto (scale 1.5, JPEG 85%), gera PDF A4 com paginacao automatica. Retorna Blob para upload/download.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentSignatures.service.js</td>
|
||||
<td>Criar solicitacao de assinatura, registrar assinatura (IP, hash, timestamp, user_agent), consultar status de cada signatario, verificar integridade via hash</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentShareLinks.service.js</td>
|
||||
<td>Gerar link temporario com token, validar token no acesso, registrar uso, expirar link</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentAuditLog.service.js</td>
|
||||
<td>Registrar log de acesso (visualizacao/download) e consultar historico de acessos por documento</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ 4. COMPOSABLES ═══════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon orange">4</div>
|
||||
<div class="section-title">Composables — Logica reativa</div>
|
||||
</div>
|
||||
<div class="section-desc">src/features/documents/composables/</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Arquivo</th>
|
||||
<th>O que faz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-file">useDocuments.js</td>
|
||||
<td>State reativo: lista de documentos do paciente, loading, filtros ativos (tipo, categoria, tags), operacoes CRUD, refresh automatico apos upload/delete</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">useDocumentTemplates.js</td>
|
||||
<td>State reativo: lista de templates disponiveis (globais + tenant), preview com dados ficticios, variaveis extraidas do corpo HTML</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">useDocumentGenerate.js</td>
|
||||
<td>Logica de geracao: carregar dados do paciente/sessao, mapear variaveis, chamar servico de geracao, retornar URL do PDF</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ 5. PAGINAS & COMPONENTES ═════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon purple">5</div>
|
||||
<div class="section-title">Paginas & Componentes Vue</div>
|
||||
</div>
|
||||
<div class="section-desc">src/features/documents/</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Arquivo</th>
|
||||
<th>Tipo</th>
|
||||
<th>O que faz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-file">DocumentsListPage.vue</td>
|
||||
<td>Pagina</td>
|
||||
<td>Pagina principal — lista todos os documentos do paciente com DataTable, filtros (tipo, categoria, tags), botoes de upload, preview, download. Hero header sticky com stats rapidos.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentUploadDialog.vue</td>
|
||||
<td>Dialog</td>
|
||||
<td>Upload de arquivo — drag & drop ou seletor, campos: tipo do documento, categoria, descricao, tags, vinculo com sessao (opcional), visibilidade. Validacao de tamanho e tipo de arquivo.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentPreviewDialog.vue</td>
|
||||
<td>Dialog</td>
|
||||
<td>Preview inline — renderiza PDF/imagem no dialog. Botoes: download, compartilhar, solicitar assinatura, excluir. Exibe metadados (tipo, tags, quem enviou, data).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentTemplatesPage.vue</td>
|
||||
<td>Pagina</td>
|
||||
<td>Gestao de templates — lista templates disponiveis (globais + do tenant), criar novo, editar, duplicar, ativar/desativar. Cards com preview do template.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentTemplateEditor.vue</td>
|
||||
<td>Componente</td>
|
||||
<td>Editor de template — edicao do corpo HTML (editor rich text), insercao de variaveis via dropdown, preview ao vivo com dados ficticios, config de cabecalho/rodape/logo.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentGenerateDialog.vue</td>
|
||||
<td>Dialog</td>
|
||||
<td>Gerar documento — selecionar template, campos preenchidos automaticamente com dados do paciente/sessao, edicao manual se necessario, preview final via iframe sandbox, botao "Salvar documento" (salva online, sem download automatico). Botao "So baixar" gera PDF local sem salvar no banco.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentSignatureDialog.vue</td>
|
||||
<td>Dialog</td>
|
||||
<td>Solicitar assinatura — adicionar signatarios (paciente, responsavel, terapeuta), definir ordem, enviar link por email/whatsapp, acompanhar status de cada signatario.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">DocumentShareDialog.vue</td>
|
||||
<td>Dialog</td>
|
||||
<td>Compartilhar — gerar link temporario com prazo (24h, 48h, 7d) e limite de usos, copiar link, enviar por email. Exibe links ja criados com status.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">components/DocumentCard.vue</td>
|
||||
<td>Componente</td>
|
||||
<td>Card reutilizavel de documento — thumbnail (icone por tipo ou preview de imagem), nome, tipo, data, tags, menu de acoes (3 dots).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">components/DocumentTagsInput.vue</td>
|
||||
<td>Componente</td>
|
||||
<td>Input de tags livres — chips editaveis com autocomplete baseado em tags ja usadas pelo terapeuta. Criacao de novas tags inline.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ 6. INTEGRACAO PRONTUARIO ═════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon pink">6</div>
|
||||
<div class="section-title">Integracao com Prontuario (arquivo existente)</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Arquivo existente</th>
|
||||
<th>Alteracao</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-file">src/features/patients/prontuario/PatientProntuario.vue</td>
|
||||
<td>Adicionar aba/secao "Documentos" que renderiza DocumentsListPage filtrada pelo patient_id atual. Botao rapido de upload direto do prontuario.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ 7. ROTAS ═════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon orange">7</div>
|
||||
<div class="section-title">Rotas</div>
|
||||
</div>
|
||||
<div class="section-desc">Adicionadas em routes.therapist.js e routes.clinic.js</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rota</th>
|
||||
<th>Pagina</th>
|
||||
<th>Descricao</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-route">/therapist/documents</td>
|
||||
<td class="col-file">DocumentsListPage.vue</td>
|
||||
<td>Lista geral de documentos (todos os pacientes do terapeuta)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-route">/therapist/documents/templates</td>
|
||||
<td class="col-file">DocumentTemplatesPage.vue</td>
|
||||
<td>Gestao de templates do terapeuta</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-route">/therapist/patients/:id/documents</td>
|
||||
<td class="col-file">DocumentsListPage.vue</td>
|
||||
<td>Documentos de um paciente especifico (via props)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-route">/clinic/documents/templates</td>
|
||||
<td class="col-file">DocumentTemplatesPage.vue</td>
|
||||
<td>Templates da clinica (admin configura templates compartilhados)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ 8. MENUS ═════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon green">8</div>
|
||||
<div class="section-title">Menus de Navegacao</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Arquivo</th>
|
||||
<th>Item adicionado</th>
|
||||
<th>Onde no menu</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-file">therapist.menu.js</td>
|
||||
<td>"Documentos" — icon: pi-file, to: /therapist/documents</td>
|
||||
<td>Grupo "Pacientes", abaixo de "Tags"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">therapist.menu.js</td>
|
||||
<td>"Templates" — icon: pi-file-edit, to: /therapist/documents/templates</td>
|
||||
<td>Sub-item de Documentos</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-file">clinic.menu.js</td>
|
||||
<td>"Templates de Documentos" — icon: pi-file-edit, to: /clinic/documents/templates</td>
|
||||
<td>Grupo "Configuracoes"</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ 9. SAAS FEATURES ═════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon red">9</div>
|
||||
<div class="section-title">SaaS — Feature Flags</div>
|
||||
</div>
|
||||
<div class="section-desc">Inseridas em saas_features e vinculadas aos planos via plan_features</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feature key</th>
|
||||
<th>Descricao</th>
|
||||
<th>Planos</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-key">documents.upload</td>
|
||||
<td>Upload de arquivos a pacientes — funcionalidade base</td>
|
||||
<td>Free + Pro</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-key">documents.templates</td>
|
||||
<td>Templates de documentos (declaracao, atestado, recibo etc.)</td>
|
||||
<td>Pro</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-key">documents.signatures</td>
|
||||
<td>Assinatura eletronica (TCLE, consentimentos)</td>
|
||||
<td>Pro</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-key">documents.share_links</td>
|
||||
<td>Links temporarios para compartilhamento externo</td>
|
||||
<td>Pro</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-key">documents.patient_portal</td>
|
||||
<td>Paciente visualiza e envia documentos pelo portal</td>
|
||||
<td>Pro</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ 10. SEED DATA ════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon cyan">10</div>
|
||||
<div class="section-title">Seed Data — Templates Padrao</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Arquivo</th>
|
||||
<th>O que insere</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-file">seed_015_document_templates.sql</td>
|
||||
<td>
|
||||
4 templates globais (is_global = true) com corpo HTML e variaveis mapeadas:
|
||||
<div class="fields" style="margin-top: 8px;">
|
||||
<span class="field">Declaracao de Comparecimento</span>
|
||||
<span class="field">Atestado Psicologico</span>
|
||||
<span class="field">Relatorio de Acompanhamento</span>
|
||||
<span class="field">Recibo de Pagamento</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="note">
|
||||
<strong>Variaveis dos templates:</strong> {{paciente_nome}}, {{paciente_cpf}}, {{data_sessao}}, {{hora_inicio}}, {{hora_fim}}, {{terapeuta_nome}}, {{terapeuta_crp}}, {{clinica_nome}}, {{clinica_endereco}}, {{valor}}, {{data_atual}}, entre outras. Cada template define quais variaveis utiliza no campo variaveis[].
|
||||
</div>
|
||||
|
||||
<div class="note" style="border-left-color: var(--orange); margin-top: 8px;">
|
||||
<strong>Decisao tecnica — Motor PDF:</strong> pdfmake foi substituido por jsPDF + html2canvas-pro. O pdfmake (UMD) trava silenciosamente com Vite (ESM) — createPdf().getBlob()/getBuffer() nunca retornam, mesmo com optimizeDeps configurado. A solucao final usa html2canvas-pro (fork com suporte a cores oklch do PrimeVue/Tailwind) para renderizar o HTML preenchido em canvas, e jsPDF para converter em PDF A4 com paginacao. Resultado: ~200-400KB por documento (JPEG 85%, scale 1.5).
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════ ORDEM DE EXECUCAO ════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon blue">!</div>
|
||||
<div class="section-title">Ordem de Execucao Sugerida</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Fase</th>
|
||||
<th>O que</th>
|
||||
<th>Depende de</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>1</strong></td>
|
||||
<td>Migrations (tabelas, RLS, triggers, indexes)</td>
|
||||
<td>—</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>2</strong></td>
|
||||
<td>Buckets no Supabase Storage</td>
|
||||
<td>Fase 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>3</strong></td>
|
||||
<td>Services (camada de dados)</td>
|
||||
<td>Fase 1 + 2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>4</strong></td>
|
||||
<td>Composables (logica reativa)</td>
|
||||
<td>Fase 3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>5</strong></td>
|
||||
<td>Componentes e Paginas Vue</td>
|
||||
<td>Fase 4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>6</strong></td>
|
||||
<td>Rotas, menus, feature flags</td>
|
||||
<td>Fase 5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>7</strong></td>
|
||||
<td>Integracao com Prontuario</td>
|
||||
<td>Fase 5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>8</strong></td>
|
||||
<td>Seed data (templates padrao)</td>
|
||||
<td>Fase 1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
372
docs/architecture/Pacientes/cadastro_pacientes_levantamento.html
Normal file
372
docs/architecture/Pacientes/cadastro_pacientes_levantamento.html
Normal file
@@ -0,0 +1,372 @@
|
||||
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
.page { padding: 0 0 32px; }
|
||||
.section { margin-bottom: 28px; }
|
||||
.section-title { font-size: 11px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--color-text-tertiary); margin: 0 0 10px; padding-bottom: 6px; border-bottom: 0.5px solid var(--color-border-tertiary); }
|
||||
.cards { display: grid; gap: 10px; }
|
||||
.cards-2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
|
||||
.cards-3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
|
||||
.card { background: var(--color-background-primary); border: 0.5px solid var(--color-border-tertiary); border-radius: var(--border-radius-lg); padding: 14px 16px; }
|
||||
.card-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
|
||||
.card-title { font-size: 13px; font-weight: 500; color: var(--color-text-primary); margin: 0; display: flex; align-items: center; gap: 7px; }
|
||||
.card-desc { font-size: 12px; color: var(--color-text-secondary); line-height: 1.55; margin: 0; }
|
||||
.card-fields { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 5px; }
|
||||
.field { font-size: 11px; padding: 3px 8px; border-radius: 20px; border: 0.5px solid var(--color-border-secondary); color: var(--color-text-secondary); background: var(--color-background-secondary); font-family: var(--font-mono); }
|
||||
.field-has { background: #EAF3DE; border-color: #C0DD97; color: #27500A; }
|
||||
.field-miss { background: #FCEBEB; border-color: #F7C1C1; color: #791F1F; }
|
||||
.badge { font-size: 11px; font-weight: 500; padding: 2px 8px; border-radius: 20px; white-space: nowrap; flex-shrink: 0; align-self: flex-start; margin-top: 1px; }
|
||||
.badge-has { background: #EAF3DE; color: #27500A; }
|
||||
.badge-part { background: #FAEEDA; color: #633806; }
|
||||
.badge-miss { background: #FCEBEB; color: #791F1F; }
|
||||
.badge-diff { background: #E6F1FB; color: #0C447C; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.field-has { background: #173404; border-color: #27500A; color: #C0DD97; }
|
||||
.field-miss { background: #501313; border-color: #791F1F; color: #F7C1C1; }
|
||||
.badge-has { background: #173404; color: #C0DD97; }
|
||||
.badge-part { background: #412402; color: #FAC775; }
|
||||
.badge-miss { background: #501313; color: #F7C1C1; }
|
||||
.badge-diff { background: #042C53; color: #B5D4F4; }
|
||||
}
|
||||
.icon-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; display: inline-block; margin-top: 3px; }
|
||||
.dot-has { background: #639922; }
|
||||
.dot-part { background: #EF9F27; }
|
||||
.dot-miss { background: #E24B4A; }
|
||||
.dot-diff { background: #378ADD; }
|
||||
.legend { display: flex; gap: 16px; margin-bottom: 20px; flex-wrap: wrap; }
|
||||
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--color-text-secondary); }
|
||||
.sub { font-size: 11px; color: var(--color-text-tertiary); margin: 2px 0 6px; font-family: var(--font-mono); }
|
||||
.note { font-size: 12px; color: var(--color-text-secondary); background: var(--color-background-secondary); border-left: 2px solid var(--color-border-secondary); padding: 8px 12px; margin-top: 10px; line-height: 1.5; border-radius: 0; }
|
||||
</style>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div class="legend">
|
||||
<div class="legend-item"><span class="icon-dot dot-has"></span> você já tem</div>
|
||||
<div class="legend-item"><span class="icon-dot dot-part"></span> tem parcialmente</div>
|
||||
<div class="legend-item"><span class="icon-dot dot-miss"></span> faltando</div>
|
||||
<div class="legend-item"><span class="icon-dot dot-diff"></span> diferencial de mercado</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">1 · Identificação & dados pessoais</div>
|
||||
<div class="cards cards-2">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-has"></span> Dados básicos de identificação</div><div class="sub">núcleo do cadastro</div></div>
|
||||
<span class="badge badge-has">completo</span>
|
||||
</div>
|
||||
<div class="card-desc">Nome, email, telefone, data de nascimento, CPF, RG, gênero, naturalidade, estado civil, escolaridade e profissão.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">nome_completo</span><span class="field field-has">email_principal</span><span class="field field-has">telefone</span><span class="field field-has">data_nascimento</span><span class="field field-has">cpf</span><span class="field field-has">rg</span><span class="field field-has">genero</span><span class="field field-has">estado_civil</span><span class="field field-has">escolaridade</span><span class="field field-has">profissao</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-part"></span> Gênero & pronomes</div><div class="sub">campo genero existe, pronomes não</div></div>
|
||||
<span class="badge badge-part">parcial</span>
|
||||
</div>
|
||||
<div class="card-desc">Você tem o campo <span style="font-family:var(--font-mono);font-size:11px">genero</span> como texto livre. Faltam pronomes preferidos (ele/ela/eles) — padrão nos sistemas modernos de saúde mental, especialmente para público LGBTQIA+.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">genero</span>
|
||||
<span class="field field-miss">pronomes</span>
|
||||
<span class="field field-miss">nome_social</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-has"></span> Endereço completo</div><div class="sub">CEP, cidade, estado, complemento</div></div>
|
||||
<span class="badge badge-has">completo</span>
|
||||
</div>
|
||||
<div class="card-desc">CEP, endereço, número, bairro, complemento, cidade, estado e país. Estrutura adequada.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">cep</span><span class="field field-has">endereco</span><span class="field field-has">numero</span><span class="field field-has">bairro</span><span class="field field-has">cidade</span><span class="field field-has">estado</span><span class="field field-has">pais</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-miss"></span> Dados socioeconômicos</div><div class="sub">renda e contexto social</div></div>
|
||||
<span class="badge badge-miss">faltando</span>
|
||||
</div>
|
||||
<div class="card-desc">Faixa de renda, religião/espiritualidade, etnia. Campos opcionais mas relevantes clinicamente e para política de precificação solidária. SimplePractice e Psicologia Viva coletam isso.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">faixa_renda</span>
|
||||
<span class="field field-miss">etnia</span>
|
||||
<span class="field field-miss">religiao</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">2 · Contatos & rede de suporte</div>
|
||||
<div class="cards cards-2">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-part"></span> Contato de emergência</div><div class="sub">só um contato, sem estrutura</div></div>
|
||||
<span class="badge badge-part">parcial</span>
|
||||
</div>
|
||||
<div class="card-desc">Você tem <span style="font-family:var(--font-mono);font-size:11px">nome_parente</span>, <span style="font-family:var(--font-mono);font-size:11px">grau_parentesco</span> e <span style="font-family:var(--font-mono);font-size:11px">telefone_parente</span> como campos soltos na tabela. Falta suporte a múltiplos contatos e campo de email do contato.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">nome_parente</span><span class="field field-has">grau_parentesco</span><span class="field field-has">telefone_parente</span>
|
||||
<span class="field field-miss">email_contato</span><span class="field field-miss">multiplos_contatos</span><span class="field field-miss">contato_primario</span>
|
||||
</div>
|
||||
<div class="note">Ideal: tabela separada <span style="font-family:var(--font-mono)">patient_contacts</span> com N contatos por paciente.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-has"></span> Responsável legal</div><div class="sub">para menores de idade</div></div>
|
||||
<span class="badge badge-has">completo</span>
|
||||
</div>
|
||||
<div class="card-desc">Nome, CPF, telefone do responsável e flag de cobrança no responsável. Cobre bem o caso de pacientes menores.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">nome_responsavel</span><span class="field field-has">telefone_responsavel</span><span class="field field-has">cpf_responsavel</span><span class="field field-has">cobranca_no_responsavel</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-miss"></span> Outros profissionais de saúde</div><div class="sub">psiquiatra, médico, nutricionista</div></div>
|
||||
<span class="badge badge-miss">faltando</span>
|
||||
</div>
|
||||
<div class="card-desc">Nome e contato do psiquiatra, médico ou outros profissionais que acompanham o paciente. Essencial para coordenação de cuidados. Presente no SimplePractice e TheraNest.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">nome_profissional</span><span class="field field-miss">especialidade</span><span class="field field-miss">telefone_profissional</span><span class="field field-miss">email_profissional</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-diff"></span> Preferências de comunicação</div><div class="sub">como o paciente quer ser contatado</div></div>
|
||||
<span class="badge badge-diff">diferencial</span>
|
||||
</div>
|
||||
<div class="card-desc">Canal preferido (WhatsApp, email, SMS), horário preferido para contato, idioma preferido. Alimenta diretamente os lembretes automáticos com as preferências do paciente.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">canal_preferido</span><span class="field field-miss">horario_contato</span><span class="field field-miss">idioma</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">3 · Origem & encaminhamento</div>
|
||||
<div class="cards cards-2">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-part"></span> Como chegou ao terapeuta</div><div class="sub">campos existem mas são texto livre</div></div>
|
||||
<span class="badge badge-part">parcial</span>
|
||||
</div>
|
||||
<div class="card-desc">Você tem <span style="font-family:var(--font-mono);font-size:11px">onde_nos_conheceu</span> e <span style="font-family:var(--font-mono);font-size:11px">encaminhado_por</span> como texto livre. Ideal ser enum + texto opcional para permitir filtros e relatórios de origem.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">onde_nos_conheceu</span><span class="field field-has">encaminhado_por</span>
|
||||
<span class="field field-miss">origem_enum</span><span class="field field-miss">agendador_publico_ref</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-miss"></span> Motivo de inatividade ou alta</div><div class="sub">por que o paciente saiu</div></div>
|
||||
<span class="badge badge-miss">faltando</span>
|
||||
</div>
|
||||
<div class="card-desc">Quando paciente vai para "Alta", "Inativo" ou "Encaminhado" — qual o motivo? Alta terapêutica, abandono, encaminhamento, mudança de cidade. Essencial para relatórios e qualidade clínica.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">motivo_saida</span><span class="field field-miss">data_saida</span><span class="field field-miss">encaminhado_para</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">4 · Status & ciclo de vida do paciente</div>
|
||||
<div class="cards cards-2">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-has"></span> Status do paciente</div><div class="sub">Ativo, Inativo, Alta, Encaminhado, Arquivado</div></div>
|
||||
<span class="badge badge-has">completo</span>
|
||||
</div>
|
||||
<div class="card-desc">Enum bem definido com os 5 status mais relevantes. Constraint no banco garante integridade.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">Ativo</span><span class="field field-has">Inativo</span><span class="field field-has">Alta</span><span class="field field-has">Encaminhado</span><span class="field field-has">Arquivado</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-miss"></span> Histórico de mudanças de status</div><div class="sub">trilha de auditoria do ciclo de vida</div></div>
|
||||
<span class="badge badge-miss">faltando</span>
|
||||
</div>
|
||||
<div class="card-desc">Quando o status mudou, quem mudou e por quê. Permite ver o histórico completo: "Ativo → Inativo (01/03) → Ativo (15/04)". Exigência de auditoria clínica.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">status_anterior</span><span class="field field-miss">status_novo</span><span class="field field-miss">motivo</span><span class="field field-miss">alterado_por</span><span class="field field-miss">alterado_em</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-has"></span> Escopo do paciente (clínica vs. terapeuta)</div><div class="sub">patient_scope bem modelado</div></div>
|
||||
<span class="badge badge-has">completo</span>
|
||||
</div>
|
||||
<div class="card-desc">Distinção entre paciente da clínica (qualquer terapeuta pode atender) e paciente particular do terapeuta. Com constraint de consistência.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">patient_scope</span><span class="field field-has">therapist_member_id</span><span class="field field-has">responsible_member_id</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-diff"></span> Alerta & flag de risco</div><div class="sub">sinalização visível no topo do cadastro</div></div>
|
||||
<span class="badge badge-diff">diferencial</span>
|
||||
</div>
|
||||
<div class="card-desc">Flag booleano de risco elevado com nota associada. Exibe alerta vermelho no topo do cadastro e do prontuário. Terapeuta sinaliza pacientes que precisam de atenção especial (ideação, crise recente).</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">risco_elevado</span><span class="field field-miss">nota_risco</span><span class="field field-miss">sinalizado_em</span><span class="field field-miss">sinalizado_por</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">5 · Organização & segmentação</div>
|
||||
<div class="cards cards-2">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-has"></span> Tags de paciente</div><div class="sub">patient_tags + patient_patient_tag</div></div>
|
||||
<span class="badge badge-has">completo</span>
|
||||
</div>
|
||||
<div class="card-desc">Tags com nome e cor, por tenant, com many-to-many. Bem estruturado.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">patient_tags</span><span class="field field-has">patient_patient_tag</span><span class="field field-has">cor</span><span class="field field-has">is_padrao</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-has"></span> Grupos de pacientes</div><div class="sub">patient_groups com many-to-many</div></div>
|
||||
<span class="badge badge-has">completo</span>
|
||||
</div>
|
||||
<div class="card-desc">Grupos com nome, cor, descrição, status ativo e flag de sistema. Relação many-to-many com <span style="font-family:var(--font-mono);font-size:11px">patient_group_patient</span>.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">patient_groups</span><span class="field field-has">patient_group_patient</span><span class="field field-has">is_system</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-has"></span> Cor de identificação</div><div class="sub">identification_color na agenda</div></div>
|
||||
<span class="badge badge-has">completo</span>
|
||||
</div>
|
||||
<div class="card-desc">Cor atribuída ao paciente para visualização rápida na agenda. Diferencial visual que poucos sistemas brasileiros têm.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">identification_color</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-diff"></span> Score de engajamento</div><div class="sub">calculado automaticamente</div></div>
|
||||
<span class="badge badge-diff">diferencial</span>
|
||||
</div>
|
||||
<div class="card-desc">Score calculado por view/função baseado em: frequência de sessões, taxa de comparecimento, dias desde última sessão, pagamentos em dia. Exibido como indicador no card do paciente. Ajuda a identificar quem precisa de atenção.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">engajamento_score</span><span class="field field-miss">taxa_comparecimento</span><span class="field field-miss">dias_sem_sessao</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">6 · Financeiro vinculado ao paciente</div>
|
||||
<div class="cards cards-2">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-has"></span> Descontos individuais</div><div class="sub">patient_discounts bem modelado</div></div>
|
||||
<span class="badge badge-has">completo</span>
|
||||
</div>
|
||||
<div class="card-desc">Desconto percentual ou fixo por paciente, com período de validade e motivo. Bem estruturado com active_from e active_to.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">discount_pct</span><span class="field field-has">discount_flat</span><span class="field field-has">active_from</span><span class="field field-has">active_to</span><span class="field field-has">reason</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-miss"></span> Limite de sessões por período</div><div class="sub">controle de plano ou convênio</div></div>
|
||||
<span class="badge badge-miss">faltando</span>
|
||||
</div>
|
||||
<div class="card-desc">Pacientes de convênio frequentemente têm limite de sessões autorizadas por mês. Campo para registrar o limite e controlar o consumo — alerta quando está próximo do teto.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">limite_sessoes_mes</span><span class="field field-miss">sessoes_usadas</span><span class="field field-miss">periodo_referencia</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-miss"></span> Método de pagamento preferido</div><div class="sub">como esse paciente costuma pagar</div></div>
|
||||
<span class="badge badge-miss">faltando</span>
|
||||
</div>
|
||||
<div class="card-desc">PIX, cartão, dinheiro, convênio. Aparece como sugestão padrão ao registrar cobrança. Evita perguntar toda vez como o paciente paga.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">metodo_pagamento_preferido</span><span class="field field-miss">dados_pagamento_obs</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-diff"></span> LTV & métricas financeiras do paciente</div><div class="sub">calculado por view</div></div>
|
||||
<span class="badge badge-diff">diferencial</span>
|
||||
</div>
|
||||
<div class="card-desc">Total pago desde o início, ticket médio por sessão, total de sessões realizadas. Calculado por view em cima de financial_records — sem armazenar, sem inconsistência.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">v_patient_ltv</span><span class="field field-miss">total_pago</span><span class="field field-miss">ticket_medio</span><span class="field field-miss">total_sessoes</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">7 · Observações & notas internas</div>
|
||||
<div class="cards cards-2">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-part"></span> Observações gerais</div><div class="sub">dois campos de texto soltos</div></div>
|
||||
<span class="badge badge-part">parcial</span>
|
||||
</div>
|
||||
<div class="card-desc">Você tem <span style="font-family:var(--font-mono);font-size:11px">observacoes</span> e <span style="font-family:var(--font-mono);font-size:11px">notas_internas</span> como campos de texto livre. Funciona, mas sem distinção clara de propósito ou histórico de edições.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-has">observacoes</span><span class="field field-has">notas_internas</span>
|
||||
<span class="field field-miss">historico_edicoes</span><span class="field field-miss">editado_por</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div><div class="card-title"><span class="icon-dot dot-diff"></span> Linha do tempo do paciente</div><div class="sub">feed cronológico de tudo que aconteceu</div></div>
|
||||
<span class="badge badge-diff">diferencial</span>
|
||||
</div>
|
||||
<div class="card-desc">Feed automático com eventos relevantes: "Primeira sessão", "Mudança de status", "Documento assinado", "Escala respondida", "Pagamento em atraso". Visível no topo do cadastro como timeline. SimplePractice tem isso.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field field-miss">patient_timeline</span><span class="field field-miss">evento_tipo</span><span class="field field-miss">descricao</span><span class="field field-miss">ocorrido_em</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
964
docs/architecture/Pacientes/pacientes_status_implementacao.html
Normal file
964
docs/architecture/Pacientes/pacientes_status_implementacao.html
Normal file
@@ -0,0 +1,964 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pacientes — Status de Implementacao</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-card: #f8f9fb;
|
||||
--bg-card-hover: #f1f3f6;
|
||||
--bg-sidebar: #f4f5f7;
|
||||
--border: #e2e5ea;
|
||||
--border-light: #eceef2;
|
||||
--text: #1a1d23;
|
||||
--text-secondary: #5f6775;
|
||||
--text-muted: #8a91a0;
|
||||
--accent: #2563eb;
|
||||
--accent-dim: #2563eb14;
|
||||
--green: #16a34a;
|
||||
--green-dim: #16a34a14;
|
||||
--green-bg: #dcfce7;
|
||||
--orange: #d97706;
|
||||
--orange-dim: #d9770614;
|
||||
--orange-bg: #fef3c7;
|
||||
--red: #dc2626;
|
||||
--red-dim: #dc262614;
|
||||
--red-bg: #fee2e2;
|
||||
--purple: #7c3aed;
|
||||
--purple-dim: #7c3aed14;
|
||||
--cyan: #0891b2;
|
||||
--cyan-dim: #0891b214;
|
||||
--pink: #db2777;
|
||||
--pink-dim: #db277714;
|
||||
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
--font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace;
|
||||
--radius: 8px;
|
||||
--radius-lg: 12px;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
|
||||
--shadow-lg: 0 4px 12px rgba(0,0,0,.08);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--font);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
padding: 40px 24px 80px;
|
||||
max-width: 1120px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ── Header ─────────────────────────── */
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 36px;
|
||||
padding-bottom: 28px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.page-header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--text);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.page-header .subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.meta {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.meta-tag {
|
||||
font-size: 11px;
|
||||
font-family: var(--font-mono);
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
/* ── Summary grid ──────────────────── */
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
||||
gap: 10px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
.summary-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px 14px;
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.summary-card .number { font-size: 26px; font-weight: 700; line-height: 1.2; }
|
||||
.summary-card .label { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.07em; margin-top: 3px; }
|
||||
.c-green .number { color: var(--green); }
|
||||
.c-orange .number { color: var(--orange); }
|
||||
.c-red .number { color: var(--red); }
|
||||
.c-accent .number { color: var(--accent); }
|
||||
.c-purple .number { color: var(--purple); }
|
||||
.c-cyan .number { color: var(--cyan); }
|
||||
.c-pink .number { color: var(--pink); }
|
||||
|
||||
/* ── Legend ─────────────────────────── */
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
margin-bottom: 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.legend-item .dot { width: 9px; height: 9px; border-radius: 50%; }
|
||||
.dot-green { background: var(--green); }
|
||||
.dot-orange { background: var(--orange); }
|
||||
.dot-red { background: var(--red); }
|
||||
|
||||
/* ── Section ────────────────────────── */
|
||||
.section { margin-bottom: 36px; }
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--border-light);
|
||||
}
|
||||
.section-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.section-icon.blue { background: var(--accent-dim); color: var(--accent); }
|
||||
.section-icon.green { background: var(--green-dim); color: var(--green); }
|
||||
.section-icon.orange { background: var(--orange-dim); color: var(--orange); }
|
||||
.section-icon.purple { background: var(--purple-dim); color: var(--purple); }
|
||||
.section-icon.cyan { background: var(--cyan-dim); color: var(--cyan); }
|
||||
.section-icon.pink { background: var(--pink-dim); color: var(--pink); }
|
||||
.section-icon.red { background: var(--red-dim); color: var(--red); }
|
||||
.section-title { font-size: 17px; font-weight: 600; }
|
||||
|
||||
/* ── Content: cards + sidebar ───────── */
|
||||
.content-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 270px;
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 850px) {
|
||||
.content-row { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* ── Cards ──────────────────────────── */
|
||||
.cards { display: flex; flex-direction: column; gap: 10px; }
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 16px 20px;
|
||||
box-shadow: var(--shadow);
|
||||
transition: box-shadow .15s, border-color .15s;
|
||||
}
|
||||
.card:hover { box-shadow: var(--shadow-lg); border-color: #d0d4db; }
|
||||
.card-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.card-desc {
|
||||
font-size: 12.5px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
.card-fields {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.field {
|
||||
font-size: 10px;
|
||||
font-family: var(--font-mono);
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-muted);
|
||||
background: var(--bg);
|
||||
}
|
||||
.card-file {
|
||||
font-size: 11px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--accent);
|
||||
margin-top: 8px;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
/* ── Badges ─────────────────────────── */
|
||||
.badge {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.badge-done { background: var(--green-bg); color: var(--green); }
|
||||
.badge-partial { background: var(--orange-bg); color: var(--orange); }
|
||||
.badge-pending { background: var(--red-bg); color: var(--red); }
|
||||
|
||||
/* ── Sidebar ────────────────────────── */
|
||||
.sidebar {
|
||||
background: var(--bg-sidebar);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 18px;
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.sidebar-title {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border-light);
|
||||
}
|
||||
.sidebar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 5px 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.sidebar-item .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
||||
.sidebar-item .label { flex: 1; }
|
||||
.sidebar-item .status {
|
||||
font-size: 10px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.sidebar-divider { height: 1px; background: var(--border-light); margin: 12px 0; }
|
||||
|
||||
/* ── Note ───────────────────────────── */
|
||||
.note {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--accent);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px 18px;
|
||||
font-size: 12.5px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 14px;
|
||||
line-height: 1.6;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.note strong { color: var(--text); }
|
||||
.note.warn { border-left-color: var(--orange); }
|
||||
.note.info { border-left-color: var(--cyan); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ══════════════════════════════ HEADER ══════════════════════════════ -->
|
||||
<div class="page-header">
|
||||
<h1>Modulo Pacientes</h1>
|
||||
<div class="subtitle">Status de implementacao confrontado com banco de dados, services e frontend</div>
|
||||
<div class="meta">
|
||||
<span class="meta-tag">AgenciaPsi v5</span>
|
||||
<span class="meta-tag">Vue 3 + Supabase</span>
|
||||
<span class="meta-tag">Atualizado: 2026-03-30</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════ STATS ═══════════════════════════════ -->
|
||||
<div class="summary-grid">
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">5</div>
|
||||
<div class="label">Tabelas core</div>
|
||||
</div>
|
||||
<div class="summary-card c-accent">
|
||||
<div class="number">4</div>
|
||||
<div class="label">Tabelas aux</div>
|
||||
</div>
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">2</div>
|
||||
<div class="label">Views</div>
|
||||
</div>
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">3</div>
|
||||
<div class="label">Services</div>
|
||||
</div>
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">10</div>
|
||||
<div class="label">Componentes</div>
|
||||
</div>
|
||||
<div class="summary-card c-green">
|
||||
<div class="number">8+8</div>
|
||||
<div class="label">Rotas (T+C)</div>
|
||||
</div>
|
||||
<div class="summary-card c-purple">
|
||||
<div class="number">50+</div>
|
||||
<div class="label">Colunas patients</div>
|
||||
</div>
|
||||
<div class="summary-card c-cyan">
|
||||
<div class="number">5</div>
|
||||
<div class="label">Triggers</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════ LEGENDA ═════════════════════════════ -->
|
||||
<div class="legend">
|
||||
<div class="legend-item"><span class="dot dot-green"></span> Implementado (DB + frontend)</div>
|
||||
<div class="legend-item"><span class="dot dot-orange"></span> Parcial / migration pendente</div>
|
||||
<div class="legend-item"><span class="dot dot-red"></span> Planejado / nao implementado</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════ 1. CADASTRO ═════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon blue">1</div>
|
||||
<div class="section-title">Cadastro & Dados Pessoais</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Identidade & dados pessoais</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Formulario completo com 6 secoes em accordion. Nome completo, nome social, pronomes, data nascimento, genero, estado civil, CPF (validacao checksum), RG, naturalidade, etnia, profissao, escolaridade. Avatar com upload ao Storage.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">nome_completo</span><span class="field">nome_social</span><span class="field">pronomes</span>
|
||||
<span class="field">data_nascimento</span><span class="field">genero</span><span class="field">estado_civil</span>
|
||||
<span class="field">cpf</span><span class="field">rg</span><span class="field">etnia</span>
|
||||
<span class="field">profissao</span><span class="field">escolaridade</span><span class="field">avatar_url</span>
|
||||
</div>
|
||||
<div class="card-file">PatientsCadastroPage.vue → secao "Identidade"</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Contato & preferencias</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Telefone principal e alternativo, email principal e alternativo. Canal preferido de contato (WhatsApp, Telefone, E-mail, SMS). Horario preferido para contato com janela inicio/fim. Idioma.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">telefone</span><span class="field">telefone_alternativo</span><span class="field">email_principal</span>
|
||||
<span class="field">email_alternativo</span><span class="field">canal_preferido</span>
|
||||
<span class="field">horario_contato_inicio</span><span class="field">horario_contato_fim</span><span class="field">idioma</span>
|
||||
</div>
|
||||
<div class="card-file">DB: CHECK canal_preferido IN (whatsapp, email, sms, telefone)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Endereco com auto-preenchimento</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">CEP com consulta ViaCEP automatica (onBlur). Preenche logradouro, bairro, cidade, estado. Complemento e numero manuais. Pais default Brasil.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">cep</span><span class="field">endereco</span><span class="field">numero</span>
|
||||
<span class="field">bairro</span><span class="field">complemento</span><span class="field">cidade</span>
|
||||
<span class="field">estado</span><span class="field">pais</span>
|
||||
</div>
|
||||
<div class="card-file">PatientsCadastroPage.vue → secao "Endereco" + ViaCEP API</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Responsavel legal</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Para menores ou cobranca em terceiro. Nome, CPF (validacao), telefone, observacao. Flag de cobranca no responsavel.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">nome_responsavel</span><span class="field">cpf_responsavel</span>
|
||||
<span class="field">telefone_responsavel</span><span class="field">observacao_responsavel</span>
|
||||
<span class="field">cobranca_no_responsavel</span>
|
||||
</div>
|
||||
<div class="card-file">PatientsCadastroPage.vue → secao "Responsavel"</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Cadastro rapido & link externo</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">3 modos de criacao: Cadastro rapido (nome, email, telefone), Cadastro completo (formulario full), Link externo (paciente preenche). Convite via token com validade.</div>
|
||||
<div class="card-file">ComponentCadastroRapido.vue + PatientCreatePopover.vue + PatientsExternalLinkPage.vue</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Dados socioeconomicos</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Religiao, faixa de renda (ate_1sm, 1_3sm, 3_6sm, 6_10sm, acima_10sm, nao_informado), origem (indicacao, agendador, redes_sociais, encaminhamento).</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">religiao</span><span class="field">faixa_renda</span><span class="field">origem</span>
|
||||
<span class="field">onde_nos_conheceu</span><span class="field">encaminhado_por</span>
|
||||
</div>
|
||||
<div class="card-file">DB: CHECK constraints com valores permitidos</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Banco de Dados</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patients</span><span class="status">50+ cols</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">RLS por owner + tenant</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">12+ indexes</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">5 triggers</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">CPF checksum</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Frontend</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">PatientsCadastroPage</span><span class="status">1985 ln</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">CadastroRapido</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">PatientCreatePopover</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">ExternalLinkPage</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Validacao</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">useFormValidation</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">validators.js</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">CPF, Phone, Email, CEP</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════ 2. LISTAGEM & BUSCA ════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon green">2</div>
|
||||
<div class="section-title">Listagem, Busca & Organizacao</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Lista de pacientes com filtros</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">DataTable com busca por nome/email/telefone (debounce 250ms). Filtros: status, grupo, tag, data de criacao. Colunas dinamicas com visibilidade configuravel. Vista tabela (desktop) e cards (mobile). Vista agrupada por grupo.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">search</span><span class="field">status</span><span class="field">groupId</span>
|
||||
<span class="field">tagId</span><span class="field">createdFrom</span><span class="field">createdTo</span>
|
||||
</div>
|
||||
<div class="card-file">PatientsListPage.vue (1457 linhas)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Grupos de pacientes</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">CRUD completo de grupos com cor. Associacao paciente ↔ grupo via junction table. Contagem de pacientes por grupo. Pagina de gestao dedicada.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">patient_groups</span><span class="field">patient_group_patient</span>
|
||||
</div>
|
||||
<div class="card-file">GruposPacientesPage.vue + GruposPacientes.service.js</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Tags de pacientes</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">CRUD de tags com cor e nome. Multi-select no cadastro. Autocomplete. Contagem de pacientes por tag. Tags padrao do sistema.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">patient_tags</span><span class="field">patient_patient_tag</span>
|
||||
</div>
|
||||
<div class="card-file">TagsPage.vue + patientTags.service.js</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Medicos & referencias</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Cadastro de medicos que encaminham pacientes. CRM, especialidade (13 opcoes), contatos. Contagem de pacientes por medico. Soft delete. Busca de pacientes referidos.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">medicos</span><span class="field">nome</span><span class="field">crm</span>
|
||||
<span class="field">especialidade</span><span class="field">encaminhado_por</span>
|
||||
</div>
|
||||
<div class="card-file">MedicosPage.vue + Medicos.service.js</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Lista de espera</div>
|
||||
<span class="badge badge-partial">placeholder</span>
|
||||
</div>
|
||||
<div class="card-desc">Tab "Lista de espera" existe na PatientsListPage mas e um placeholder. Comentario no codigo: "Quando voce quiser, podemos ligar isso a uma tabela (ex: patient_waitlist)". Nao tem tabela no banco.</div>
|
||||
<div class="card-file">PatientsListPage.vue → tab placeholder</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Cadastros recebidos (intake)</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Formularios de cadastro externo submetidos por pacientes prospectivos. Status: new, converted, rejected. Pagina de gestao dedicada com badge no menu.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">patient_intake_requests</span><span class="field">status</span><span class="field">converted_patient_id</span>
|
||||
</div>
|
||||
<div class="card-file">CadastrosRecebidosPage.vue</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Tabelas Auxiliares</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patient_groups</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patient_group_patient</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patient_tags</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patient_patient_tag</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">medicos</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patient_intake_requests</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patient_invites</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-red"></span><span class="label">patient_waitlist</span><span class="status">nao existe</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Rotas</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">/therapist/patients</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">/therapist/.../grupos</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">/therapist/.../tags</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">/therapist/.../medicos</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">/admin/pacientes/*</span><span class="status">8 rotas</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════ 3. PRONTUARIO ══════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon purple">3</div>
|
||||
<div class="section-title">Prontuario do Paciente</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Perfil completo (tab 1)</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Sidebar com avatar, badges (status, convenio, scope), tags e metricas (sessoes, comparecimento %, LTV, dias sem sessao). Corpo com 6 sub-secoes em accordion: dados pessoais, contato, endereco, dados adicionais, responsavel, anotacoes.</div>
|
||||
<div class="card-file">PatientProntuario.vue → tab "Perfil" (1167 linhas total)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Prontuario clinico (tab 2)</div>
|
||||
<span class="badge badge-partial">estrutura</span>
|
||||
</div>
|
||||
<div class="card-desc">Tab existe no componente mas conteudo clinico (notas de sessao, evolucao, plano terapeutico) ainda precisa ser detalhado. Placeholder no modal.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Agenda do paciente (tab 3)</div>
|
||||
<span class="badge badge-partial">estrutura</span>
|
||||
</div>
|
||||
<div class="card-desc">Tab de sessoes/agenda existe. Lista de agenda_eventos carregada. Falta validar se a UI mostra corretamente os agendamentos futuros e historico completo.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Financeiro do paciente (tab 4)</div>
|
||||
<span class="badge badge-partial">estrutura</span>
|
||||
</div>
|
||||
<div class="card-desc">Tab existe. patient_discounts funciona (desconto percentual ou valor fixo, periodo de validade). Detalhes de cobrancas e pagamentos por paciente a validar.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">patient_discounts</span><span class="field">discount_pct</span><span class="field">discount_flat</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Documentos do paciente (tab 5)</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">DocumentsListPage embarcada no prontuario com prop embedded. Upload, preview, download, geracao de PDF, compartilhamento. Integrado com modulo de documentos completo.</div>
|
||||
<div class="card-file">DocumentsListPage.vue (embedded)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Flag de risco clinico</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Banner vermelho no prontuario quando risco_elevado = true. Obriga risco_nota e risco_sinalizado_por (CHECK constraint). Trigger registra na patient_timeline. View v_patients_risco para dashboard.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">risco_elevado</span><span class="field">risco_nota</span>
|
||||
<span class="field">risco_sinalizado_em</span><span class="field">risco_sinalizado_por</span>
|
||||
</div>
|
||||
<div class="card-file">DB: trg_patient_risco_timeline + v_patients_risco</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Tabs do Prontuario</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">1. Perfil</span><span class="status">completo</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-orange"></span><span class="label">2. Prontuario</span><span class="status">estrutura</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-orange"></span><span class="label">3. Agenda</span><span class="status">estrutura</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-orange"></span><span class="label">4. Financeiro</span><span class="status">estrutura</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">5. Documentos</span><span class="status">completo</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Metricas Sidebar</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">Total sessoes</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">Comparecimento %</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">LTV total</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">Dias sem sessao</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">Risk flag</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════ 4. REDE DE SUPORTE ═════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon cyan">4</div>
|
||||
<div class="section-title">Rede de Suporte & Contatos</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Contatos de suporte (legado)</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">patient_support_contacts: nome, relacao, tipo (emergencia, familiar, profissional_saude, amigo, outro), telefone, email, flag is_primario. CRUD no cadastro do paciente.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">nome</span><span class="field">relacao</span><span class="field">tipo</span>
|
||||
<span class="field">telefone</span><span class="field">email</span><span class="field">is_primario</span>
|
||||
</div>
|
||||
<div class="card-file">PatientsCadastroPage.vue → secao "Rede de Suporte"</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Contatos estruturados (novo)</div>
|
||||
<span class="badge badge-partial">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">patient_contacts: tabela mais completa que substitui campos legado (nome_parente, telefone_parente). Inclui CPF, especialidade, registro profissional (CRM/CRP). Unique constraint para contato primario. Migrada com dados legados. Frontend ainda usa patient_support_contacts.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">nome</span><span class="field">tipo</span><span class="field">cpf</span>
|
||||
<span class="field">especialidade</span><span class="field">registro_profissional</span>
|
||||
<span class="field">is_primario</span><span class="field">ativo</span>
|
||||
</div>
|
||||
<div class="card-file">DB: patient_contacts (migration_patients.sql) — frontend pendente</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Tabelas de Contatos</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patient_support_contacts</span><span class="status">em uso</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-orange"></span><span class="label">patient_contacts</span><span class="status">db ok</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Campos legado (patients)</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">nome_parente</span><span class="status">legado</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">telefone_parente</span><span class="status">legado</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">nome_responsavel</span><span class="status">legado</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════ 5. STATUS & TIMELINE ═══════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon pink">5</div>
|
||||
<div class="section-title">Status, Timeline & Engajamento</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Gestao de status</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">6 status: Ativo, Em espera, Inativo, Alta, Encaminhado, Arquivado. Campos de saida: motivo_saida, data_saida, encaminhado_para. Historico automatico via trigger.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">status</span><span class="field">motivo_saida</span><span class="field">data_saida</span>
|
||||
<span class="field">encaminhado_para</span>
|
||||
</div>
|
||||
<div class="card-file">DB: patient_status_history (trigger automatico)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Historico de status</div>
|
||||
<span class="badge badge-done">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Tabela imutavel patient_status_history. Registra automaticamente: status anterior, novo, motivo, encaminhamento, data saida, quem alterou. Trigger trg_patient_status_history.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">status_anterior</span><span class="field">status_novo</span><span class="field">motivo</span>
|
||||
<span class="field">alterado_por</span><span class="field">alterado_em</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Timeline do paciente</div>
|
||||
<span class="badge badge-done">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">patient_timeline: feed cronologico com 18 tipos de evento. Auto-populada por triggers (status, risco, documentos, assinaturas). Cores por tipo. Referencia polimorfica para links.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">evento_tipo</span><span class="field">titulo</span><span class="field">descricao</span>
|
||||
<span class="field">icone_cor</span><span class="field">link_ref_tipo</span><span class="field">link_ref_id</span>
|
||||
</div>
|
||||
<div class="card-file">DB: 18 event types + 3 triggers auto-insert</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> View de engajamento</div>
|
||||
<span class="badge badge-done">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">v_patient_engajamento: score 0-100 calculado em real-time. Metricas: total sessoes, sessoes ultimo mes, taxa comparecimento, LTV, ticket medio, cobrancas vencidas/pagas, taxa pagamentos em dia, duracao tratamento. Formula: 50% frequencia + 30% financeiro + 20% recencia.</div>
|
||||
<div class="card-file">DB: VIEW v_patient_engajamento (security_invoker = on)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> View de pacientes em risco</div>
|
||||
<span class="badge badge-done">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">v_patients_risco: lista pacientes que precisam de atencao. Criterios: risco_elevado, sem sessao ha 30+ dias, comparecimento <60%, cobranca vencida. Alertas categorizados.</div>
|
||||
<div class="card-file">DB: VIEW v_patients_risco</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> UI de timeline no prontuario</div>
|
||||
<span class="badge badge-partial">pendente</span>
|
||||
</div>
|
||||
<div class="card-desc">A tabela patient_timeline e as views estao prontas no banco. Falta o componente frontend para exibir a timeline visualmente no prontuario do paciente (feed cronologico com icones e cores).</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Tabelas</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patient_status_history</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patient_timeline</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Views</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">v_patient_engajamento</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">v_patients_risco</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Triggers automaticos</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">status → history</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">status → timeline</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">risco → timeline</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">18 Eventos Timeline</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">primeira_sessao</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">sessao_realizada</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">status_alterado</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">risco_sinalizado</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">documento_adicionado</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">pagamento_recebido</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">+ 12 outros</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════ 6. FINANCEIRO ══════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon orange">6</div>
|
||||
<div class="section-title">Financeiro & Convenios</div>
|
||||
</div>
|
||||
|
||||
<div class="content-row">
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Convenio / plano de saude</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Associacao paciente → insurance_plans. Campo convenio (texto livre) + convenio_id (FK). Cadastro rapido de convenio via dialog. Exibido no prontuario como badge.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">convenio</span><span class="field">convenio_id</span><span class="field">insurance_plans</span>
|
||||
</div>
|
||||
<div class="card-file">CadastroRapidoConvenio.vue + PatientsCadastroPage.vue</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Metodo de pagamento preferido</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">PIX, cartao, dinheiro, deposito, convenio. CHECK constraint no banco. Selecionavel no cadastro.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">metodo_pagamento_preferido</span>
|
||||
</div>
|
||||
<div class="card-file">DB: CHECK (pix, cartao, dinheiro, deposito, convenio)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-green"></span> Descontos por paciente</div>
|
||||
<span class="badge badge-done">pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">patient_discounts: desconto percentual ou valor fixo, motivo, periodo de validade (active_from, active_to). Gerenciavel pela listagem de pacientes.</div>
|
||||
<div class="card-fields">
|
||||
<span class="field">discount_pct</span><span class="field">discount_flat</span><span class="field">reason</span>
|
||||
<span class="field">active_from</span><span class="field">active_to</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-title">Tabelas</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">insurance_plans</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">patient_discounts</span></div>
|
||||
<div class="sidebar-divider"></div>
|
||||
<div class="sidebar-title">Validacao</div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">metodo_pagamento CHECK</span></div>
|
||||
<div class="sidebar-item"><span class="dot dot-green"></span><span class="label">convenio_id FK</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════ PENDENCIAS ══════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<div class="section-icon red">!</div>
|
||||
<div class="section-title">Pendencias & Itens a Implementar</div>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Prontuario clinico (tab 2) — notas de sessao e evolucao</div>
|
||||
<span class="badge badge-partial">estrutura</span>
|
||||
</div>
|
||||
<div class="card-desc">Tab existe no modal mas sem conteudo clinico detalhado. Precisa: notas de sessao vinculadas a agenda_eventos, evolucao terapeutica, plano de tratamento, hipoteses diagnosticas.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> UI da timeline no prontuario</div>
|
||||
<span class="badge badge-partial">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Tabela patient_timeline com 18 event types e triggers automaticos existe. Falta componente frontend para exibir feed cronologico no prontuario (icones, cores, links para entidades referenciadas).</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Dashboard de engajamento e risco</div>
|
||||
<span class="badge badge-partial">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Views v_patient_engajamento e v_patients_risco existem no banco. Score de engajamento (0-100) calculado em real-time. Falta UI para exibir dashboard de risco e engajamento geral dos pacientes.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Migrar frontend para patient_contacts</div>
|
||||
<span class="badge badge-partial">db pronto</span>
|
||||
</div>
|
||||
<div class="card-desc">Tabela patient_contacts (mais completa: CPF, especialidade, registro profissional) existe e foi populada com dados legados. Frontend ainda usa patient_support_contacts (mais simples). Migrar UI para usar a nova tabela.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-red"></span> Lista de espera (patient_waitlist)</div>
|
||||
<span class="badge badge-pending">nao existe</span>
|
||||
</div>
|
||||
<div class="card-desc">Tab placeholder na PatientsListPage. Nao existe tabela no banco. Precisa: criacao da tabela, service, UI com gestao de fila (posicao, prioridade, data de entrada, notificacao quando vaga abrir).</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Botao "+ Sessao" no prontuario</div>
|
||||
<span class="badge badge-partial">placeholder</span>
|
||||
</div>
|
||||
<div class="card-desc">Botao existe no header do prontuario mas sem click handler. Precisa abrir dialog de agendamento rapido pre-preenchido com o paciente atual.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Tabs Agenda e Financeiro no prontuario</div>
|
||||
<span class="badge badge-partial">estrutura</span>
|
||||
</div>
|
||||
<div class="card-desc">Tabs existem no modal. Dados de agenda_eventos e patient_discounts carregam. Falta validar se a UI mostra corretamente: agendamentos futuros, historico completo, cobrancas, pagamentos, recibos.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-top">
|
||||
<div class="card-title"><span class="dot dot-orange"></span> Migrations nao aplicadas</div>
|
||||
<span class="badge badge-partial">verificar</span>
|
||||
</div>
|
||||
<div class="card-desc">Verificar se as migrations estao aplicadas no banco local: 20260328000002 (new columns), 20260328000003 (drop constraints), 20260328000004 (support_contacts), migration_patients.sql (timeline, contacts, views, risk). Algumas dependem de tabelas que podem nao existir ainda (insurance_plans, etc).</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="note info" style="margin-top: 16px;">
|
||||
<strong>Multi-tenant:</strong> Todas as queries filtram por owner_id (terapeuta individual) ou tenant_id (clinica). RLS no banco garante isolamento. Feature flags (patients.view, patients.create, patients.edit, patients.delete) controlam acesso por plano. Rotas admin usam meta tenantFeature: 'patients'.
|
||||
</div>
|
||||
|
||||
<div class="note" style="margin-top: 10px;">
|
||||
<strong>Escopo dual:</strong> patient_scope = 'clinic' (paciente da clinica, sem therapist_member_id) ou 'therapist' (paciente particular, com therapist_member_id obrigatorio). CHECK constraint garante consistencia.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user