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>
|
||||
Reference in New Issue
Block a user