Files
whatsapp/DOCUMENTACION_LAB.html
T

1713 lines
88 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documentación — Sistema de Laboratorio</title>
<style>
:root {
--primary: #2563eb;
--primary-dk: #1d4ed8;
--secondary: #0891b2;
--success: #16a34a;
--warning: #d97706;
--danger: #dc2626;
--dark: #1e293b;
--muted: #64748b;
--light: #f8fafc;
--border: #e2e8f0;
--radius: 8px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f1f5f9;
color: #334155;
font-size: 15px;
line-height: 1.7;
}
/* ── Layout ── */
.layout { display: flex; min-height: 100vh; }
/* ── Sidebar TOC ── */
.toc {
width: 280px;
flex-shrink: 0;
background: var(--dark);
color: #cbd5e1;
position: sticky;
top: 0;
height: 100vh;
overflow-y: auto;
padding: 0 0 40px;
}
.toc-brand {
padding: 20px 20px 16px;
border-bottom: 1px solid #334155;
margin-bottom: 8px;
}
.toc-brand h2 {
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .08em;
color: #94a3b8;
}
.toc-brand p {
font-size: 11px;
color: #475569;
margin-top: 3px;
}
.toc-section {
padding: 8px 20px 4px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .1em;
color: #475569;
}
.toc a {
display: block;
padding: 6px 20px 6px 28px;
color: #94a3b8;
text-decoration: none;
font-size: 13px;
border-left: 3px solid transparent;
transition: all .15s;
}
.toc a:hover, .toc a.active {
color: #f8fafc;
background: #263348;
border-left-color: var(--primary);
}
.toc a.h2-link {
padding-left: 20px;
font-weight: 600;
font-size: 13.5px;
color: #e2e8f0;
}
/* ── Main content ── */
.content {
flex: 1;
padding: 48px 56px;
max-width: 1100px;
}
/* ── Hero ── */
.hero {
background: linear-gradient(135deg, var(--dark) 0%, #0f172a 100%);
color: white;
border-radius: var(--radius);
padding: 36px 40px;
margin-bottom: 48px;
}
.hero h1 { font-size: 28px; font-weight: 800; }
.hero p { margin-top: 8px; color: #94a3b8; font-size: 15px; }
.hero-meta {
display: flex;
gap: 24px;
margin-top: 20px;
flex-wrap: wrap;
}
.hero-meta span {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #64748b;
background: #1e293b;
padding: 4px 12px;
border-radius: 20px;
}
.hero-meta span b { color: #94a3b8; }
/* ── Sections ── */
.module {
background: white;
border-radius: var(--radius);
box-shadow: 0 1px 3px rgba(0,0,0,.06);
margin-bottom: 40px;
overflow: hidden;
}
.module-header {
padding: 20px 28px;
display: flex;
align-items: center;
gap: 14px;
border-bottom: 1px solid var(--border);
}
.module-header .icon {
width: 42px;
height: 42px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
flex-shrink: 0;
}
.icon-blue { background: #dbeafe; color: var(--primary); }
.icon-violet { background: #ede9fe; color: #7c3aed; }
.icon-cyan { background: #cffafe; color: var(--secondary); }
.icon-green { background: #dcfce7; color: var(--success); }
.icon-orange { background: #ffedd5; color: var(--warning); }
.module-header h2 {
font-size: 20px;
font-weight: 700;
color: var(--dark);
}
.module-header p {
font-size: 13px;
color: var(--muted);
margin-top: 2px;
}
.module-body { padding: 28px; }
h3 {
font-size: 15px;
font-weight: 700;
color: var(--dark);
text-transform: uppercase;
letter-spacing: .06em;
margin: 28px 0 14px;
padding-bottom: 6px;
border-bottom: 2px solid var(--border);
}
h3:first-child { margin-top: 0; }
h4 {
font-size: 14px;
font-weight: 700;
color: #475569;
margin: 20px 0 10px;
}
p { margin-bottom: 12px; }
/* ── Tables ── */
.table-wrap { overflow-x: auto; margin-bottom: 20px; }
table {
width: 100%;
border-collapse: collapse;
font-size: 13.5px;
}
thead tr { background: #f8fafc; }
th {
text-align: left;
padding: 10px 14px;
font-weight: 600;
color: #475569;
border-bottom: 2px solid var(--border);
white-space: nowrap;
}
td {
padding: 9px 14px;
border-bottom: 1px solid #f1f5f9;
vertical-align: top;
}
tr:last-child td { border-bottom: none; }
tr:hover td { background: #fafafa; }
td code, th code { font-family: 'SF Mono', Consolas, monospace; }
/* ── Code / Pre ── */
pre {
background: #0f172a;
color: #e2e8f0;
border-radius: var(--radius);
padding: 20px 24px;
font-family: 'SF Mono', Consolas, monospace;
font-size: 13px;
line-height: 1.65;
overflow-x: auto;
margin-bottom: 20px;
}
code {
background: #e2e8f0;
color: #1e293b;
padding: 1px 6px;
border-radius: 4px;
font-family: 'SF Mono', Consolas, monospace;
font-size: 12.5px;
}
pre code { background: none; color: inherit; padding: 0; font-size: 13px; }
/* ── Badges ── */
.badge {
display: inline-block;
padding: 2px 9px;
border-radius: 12px;
font-size: 11.5px;
font-weight: 600;
white-space: nowrap;
}
.badge-blue { background: #dbeafe; color: #1d4ed8; }
.badge-green { background: #dcfce7; color: #15803d; }
.badge-orange { background: #ffedd5; color: #c2410c; }
.badge-red { background: #fee2e2; color: #b91c1c; }
.badge-violet { background: #ede9fe; color: #6d28d9; }
.badge-gray { background: #f1f5f9; color: #475569; }
.badge-cyan { background: #cffafe; color: #0e7490; }
/* ── Info callout ── */
.callout {
border-left: 4px solid var(--primary);
background: #eff6ff;
border-radius: 0 var(--radius) var(--radius) 0;
padding: 12px 16px;
margin-bottom: 16px;
font-size: 13.5px;
}
.callout.warning { border-left-color: var(--warning); background: #fffbeb; }
.callout.success { border-left-color: var(--success); background: #f0fdf4; }
/* ── State flow ── */
.flow {
display: flex;
flex-wrap: wrap;
gap: 0;
align-items: center;
margin: 16px 0;
}
.flow-step {
background: white;
border: 2px solid var(--border);
border-radius: 8px;
padding: 10px 18px;
font-size: 13px;
font-weight: 600;
text-align: center;
min-width: 140px;
}
.flow-arrow {
font-size: 20px;
color: var(--muted);
padding: 0 6px;
}
.flow-step.s-prog { border-color: #94a3b8; color: #475569; }
.flow-step.s-conf { border-color: #06b6d4; color: #0e7490; }
.flow-step.s-cam { border-color: var(--primary); color: var(--primary); }
.flow-step.s-dom { border-color: var(--warning); color: var(--warning); }
.flow-step.s-comp { border-color: var(--success); color: var(--success); }
.flow-step.s-canc { border-color: var(--danger); color: var(--danger); }
.flow-step.s-repr { border-color: #7c3aed; color: #7c3aed; }
/* ── Roles grid ── */
.roles-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 20px; }
.role-card {
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 16px;
}
.role-card h4 { margin: 0 0 8px; font-size: 13.5px; }
.role-card ul { padding-left: 18px; font-size: 13px; color: var(--muted); }
.role-card ul li { margin-bottom: 4px; }
/* ── Features grid ── */
.features { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-bottom: 20px; }
.feature-item {
background: #f8fafc;
border-radius: 8px;
padding: 12px 14px;
font-size: 13px;
border: 1px solid var(--border);
}
.feature-item strong { display: block; color: var(--dark); margin-bottom: 2px; }
/* ── Step list ── */
.steps { counter-reset: step; list-style: none; padding: 0; }
.steps li {
counter-increment: step;
display: flex;
gap: 14px;
margin-bottom: 14px;
align-items: flex-start;
}
.steps li::before {
content: counter(step);
min-width: 26px;
height: 26px;
background: var(--primary);
color: white;
border-radius: 50%;
font-size: 12px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 2px;
}
.steps li > div { font-size: 13.5px; line-height: 1.6; }
.steps li > div strong { color: var(--dark); }
/* ── Manual de uso ── */
.man-section { margin-bottom: 36px; }
.man-step-block {
display: flex;
gap: 16px;
margin-bottom: 20px;
align-items: flex-start;
}
.man-step-num {
min-width: 34px;
height: 34px;
border-radius: 50%;
background: var(--primary);
color: white;
font-weight: 800;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 2px;
}
.man-step-body { flex: 1; }
.man-step-body h5 {
font-size: 14.5px;
font-weight: 700;
color: var(--dark);
margin-bottom: 6px;
}
.man-step-body p { font-size: 13.5px; margin-bottom: 6px; }
.man-tip {
background: #f0fdf4;
border-left: 4px solid var(--success);
border-radius: 0 var(--radius) var(--radius) 0;
padding: 10px 14px;
font-size: 13px;
margin: 8px 0 0;
color: #166534;
}
.man-warn {
background: #fffbeb;
border-left: 4px solid var(--warning);
border-radius: 0 var(--radius) var(--radius) 0;
padding: 10px 14px;
font-size: 13px;
margin: 8px 0 0;
color: #92400e;
}
.man-ui-tag {
display: inline-flex;
align-items: center;
gap: 5px;
background: #f1f5f9;
border: 1px solid #cbd5e1;
border-radius: 6px;
padding: 2px 10px;
font-size: 12.5px;
font-weight: 600;
color: #334155;
}
.state-pill {
display: inline-block;
padding: 3px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin: 2px;
}
.sp-prog { background:#f1f5f9; color:#475569; border:1px solid #cbd5e1; }
.sp-conf { background:#cffafe; color:#0e7490; border:1px solid #a5f3fc; }
.sp-cam { background:#dbeafe; color:#1d4ed8; border:1px solid #93c5fd; }
.sp-dom { background:#ffedd5; color:#c2410c; border:1px solid #fdba74; }
.sp-comp { background:#dcfce7; color:#15803d; border:1px solid #86efac; }
.sp-canc { background:#fee2e2; color:#b91c1c; border:1px solid #fca5a5; }
.role-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 700;
}
.rb-admin { background: #dbeafe; color: #1d4ed8; }
.rb-enf { background: #dcfce7; color: #15803d; }
.rb-pub { background: #f3e8ff; color: #6d28d9; }
/* ── Scroll top button ── */
#scrollTop {
position: fixed;
bottom: 28px;
right: 28px;
width: 40px;
height: 40px;
background: var(--dark);
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
font-size: 18px;
display: none;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0,0,0,.2);
z-index: 100;
}
#scrollTop.show { display: flex; }
/* ── Responsive ── */
@media (max-width: 860px) {
.toc { display: none; }
.content { padding: 24px 20px; }
}
</style>
</head>
<body>
<div class="layout">
<!-- ══════════════════ SIDEBAR TOC ══════════════════ -->
<nav class="toc">
<div class="toc-brand">
<h2>🔬 Lab System</h2>
<p>Documentación técnica — v1.0</p>
</div>
<div class="toc-section">Módulo 1 — Domicilios</div>
<a href="#dom-intro" class="h2-link">¿Qué es?</a>
<a href="#dom-archivos">Archivos involucrados</a>
<a href="#dom-bd">Base de datos</a>
<a href="#dom-bd-domicilios">&nbsp;&nbsp;↳ lab_domicilios</a>
<a href="#dom-bd-enfermeras">&nbsp;&nbsp;↳ lab_enfermeras</a>
<a href="#dom-bd-asignaciones">&nbsp;&nbsp;↳ lab_asignaciones</a>
<a href="#dom-bd-extras">&nbsp;&nbsp;↳ lab_servicios_extra</a>
<a href="#dom-bd-notas">&nbsp;&nbsp;↳ lab_domicilio_notas</a>
<a href="#dom-roles">Roles y permisos</a>
<a href="#dom-estados">Flujo de estados</a>
<a href="#dom-funciones">Funcionalidades</a>
<a href="#dom-api">API Endpoints</a>
<div class="toc-section">Módulo 2 — Formularios</div>
<a href="#form-intro" class="h2-link">¿Qué es?</a>
<div class="toc-section">📘 Manual de Uso</div>
<a href="#man-admin" class="h2-link">Admin — Domicilios</a>
<a href="#man-admin-crear">Crear domicilio</a>
<a href="#man-admin-asignar">Asignar enfermero</a>
<a href="#man-admin-estado">Cambiar estado</a>
<a href="#man-admin-pago">Registrar pago</a>
<a href="#man-admin-filtros">Filtros y exportar</a>
<a href="#man-enf" class="h2-link">Enfermero — Portal</a>
<a href="#man-enf-acceso">Acceder al portal</a>
<a href="#man-enf-avanzar">Avanzar un domicilio</a>
<a href="#man-enf-notas">Registrar notas</a>
<a href="#man-enf-extras">Servicios extra</a>
<a href="#man-form" class="h2-link">Admin — Formularios</a>
<a href="#man-form-crear">Crear plantilla</a>
<a href="#man-form-enviar">Enviar a paciente</a>
<a href="#man-form-revisar">Revisar respuestas</a>
<a href="#man-form-firmar">Firmar como profesional</a>
<a href="#man-form-verificar">Verificar autenticidad</a>
<a href="#form-archivos">Archivos involucrados</a>
<a href="#form-bd">Base de datos</a>
<a href="#form-bd-plantillas">&nbsp;&nbsp;↳ lab_formularios</a>
<a href="#form-bd-envios">&nbsp;&nbsp;↳ lab_form_envios</a>
<a href="#form-roles">Roles y permisos</a>
<a href="#form-flujo">Flujo completo</a>
<a href="#form-campos">Tipos de campos</a>
<a href="#form-firma-dig">Firma digital</a>
<a href="#form-enlace">Enlace y vigencia</a>
<a href="#form-pdf">PDF y visualización</a>
<a href="#form-sha">Sello SHA-256</a>
<a href="#form-firmapro">Firma del profesional</a>
<a href="#form-api">API Endpoints</a>
</nav>
<!-- ══════════════════ CONTENT ══════════════════ -->
<main class="content">
<!-- Hero -->
<div class="hero">
<h1>📋 Documentación del Sistema de Laboratorio</h1>
<p>Sistema Laboratorio Clínico — Módulos de Agendamiento y Formularios</p>
<div class="hero-meta">
<span>📅 <b>Última actualización:</b> 30/03/2026</span>
<span>🔢 <b>Versión:</b> 1.0</span>
<span>📦 <b>Módulos:</b> Domicilios · Formularios</span>
</div>
</div>
<!-- ╔═══════════════════════════════════════════╗
║ MÓDULO 1 — DOMICILIOS ║
╚═══════════════════════════════════════════╝ -->
<div class="module" id="dom-intro">
<div class="module-header">
<div class="icon icon-blue">🏠</div>
<div>
<h2>Módulo 1 — Agendamiento (Domicilios)</h2>
<p>Gestión de servicios de toma de muestras a domicilio</p>
</div>
</div>
<div class="module-body">
<h3>¿Qué es?</h3>
<p>
El módulo de agendamiento gestiona los <strong>servicios de toma de muestras a domicilio</strong>.
Permite crear, asignar, seguir y completar visitas médicas domiciliarias.
Los administradores gestionan la agenda desde <code>lab_domicilios.php</code>;
los enfermeros gestionan su propia agenda desde <code>enfermero_portal.php</code>.
</p>
<!-- ARCHIVOS -->
<h3 id="dom-archivos">Archivos involucrados</h3>
<div class="table-wrap">
<table>
<thead>
<tr><th>Archivo</th><th>Descripción</th></tr>
</thead>
<tbody>
<tr><td><code>lab_domicilios.php</code></td><td>Vista principal del admin — tabla con filtros, detalle del domicilio, formularios recibidos, exportar CSV</td></tr>
<tr><td><code>enfermero_portal.php</code></td><td>Portal exclusivo del enfermero — su agenda personal del día, ordenada por hora, con tarjetas colapsables</td></tr>
<tr><td><code>lab_enfermeras.php</code></td><td>CRUD del personal de enfermería</td></tr>
<tr><td><code>classes/lab/Domicilio.php</code></td><td>Clase ORM — crear, editar, cambiar estado, estadísticas</td></tr>
<tr><td><code>classes/lab/Enfermera.php</code></td><td>Clase ORM — CRUD, agenda por enfermero, carga de trabajo</td></tr>
<tr><td><code>classes/lab/Asignacion.php</code></td><td>Clase ORM — asignar / reasignar / liberar enfermero a domicilio</td></tr>
<tr><td><code>api/lab/get_domicilios.php</code></td><td>GET — lista de domicilios con filtros y estadísticas del día</td></tr>
<tr><td><code>api/lab/save_domicilio.php</code></td><td>POST — crear, actualizar o cambiar estado</td></tr>
<tr><td><code>api/lab/update_domicilio_enfermero.php</code></td><td>POST — el enfermero avanza el estado desde su portal</td></tr>
<tr><td><code>api/lab/my_agenda.php</code></td><td>GET — agenda del enfermero actualmente autenticado</td></tr>
<tr><td><code>api/lab/save_asignacion.php</code></td><td>POST — asignar o reasignar enfermero</td></tr>
<tr><td><code>api/lab/get_asignaciones.php</code></td><td>GET — asignaciones por fecha</td></tr>
<tr><td><code>api/lab/registrar_pago.php</code></td><td>POST — registrar pago de un domicilio</td></tr>
<tr><td><code>api/lab/save_servicio_extra.php</code></td><td>POST — agregar servicio realizado durante la visita</td></tr>
<tr><td><code>api/lab/get_notas_domicilio.php</code></td><td>GET — notas clínicas y libres del domicilio</td></tr>
<tr><td><code>api/lab/upload_nota_imagen.php</code></td><td>POST — subir imagen adjunta a una nota</td></tr>
<tr><td><code>api/lab/crear_desde_whatsapp.php</code></td><td>GET/POST — crear paciente u orden desde una conversación de WhatsApp activa</td></tr>
</tbody>
</table>
</div>
<!-- BASE DE DATOS -->
<h3 id="dom-bd">Base de datos</h3>
<h4 id="dom-bd-domicilios">Tabla <code>lab_domicilios</code></h4>
<p>Tabla principal. Cada fila es un servicio a domicilio.</p>
<div class="table-wrap">
<table>
<thead><tr><th>Campo</th><th>Tipo</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>id</code></td><td><span class="badge badge-blue">INT PK</span></td><td>Identificador único</td></tr>
<tr><td><code>paciente_id</code></td><td><span class="badge badge-orange">INT FK</span></td><td>Paciente al que se le realiza el servicio</td></tr>
<tr><td><code>orden_id</code></td><td><span class="badge badge-gray">INT FK NULL</span></td><td>Orden médica adjunta (opcional)</td></tr>
<tr><td><code>direccion</code></td><td>TEXT</td><td>Dirección completa de la visita</td></tr>
<tr><td><code>ciudad</code></td><td>VARCHAR(100)</td><td>Ciudad</td></tr>
<tr><td><code>barrio</code></td><td>VARCHAR(100)</td><td>Barrio</td></tr>
<tr><td><code>indicaciones_dir</code></td><td>TEXT</td><td>Referencias o indicaciones adicionales ("apto 302, tocar campanilla")</td></tr>
<tr><td><code>fecha_programada</code></td><td>DATE</td><td>Fecha de la visita</td></tr>
<tr><td><code>hora_programada</code></td><td>TIME</td><td>Hora de la visita</td></tr>
<tr><td><code>tipo_servicio</code></td><td>VARCHAR(100)</td><td>Tipo de servicio (toma de muestra, etc.)</td></tr>
<tr><td><code>tipo_cliente</code></td><td>ENUM</td><td><code>particular</code> / <code>seguro</code> / <code>eps</code></td></tr>
<tr><td><code>examenes_solicitados</code></td><td>TEXT</td><td>Lista de exámenes (cuando no hay orden médica)</td></tr>
<tr><td><code>seguro_nombre</code></td><td>VARCHAR</td><td>Nombre del seguro o EPS</td></tr>
<tr><td><code>autorizacion</code></td><td>VARCHAR</td><td>Número de autorización</td></tr>
<tr><td><code>valor_domicilio</code></td><td>DECIMAL</td><td>Valor del servicio <span class="badge badge-gray">nullable</span></td></tr>
<tr><td><code>valor_copago</code></td><td>DECIMAL</td><td>Copago a cargo del cliente <span class="badge badge-gray">nullable</span></td></tr>
<tr><td><code>copago_laboratorio</code></td><td>DECIMAL</td><td>Copago al laboratorio <span class="badge badge-gray">nullable</span></td></tr>
<tr><td><code>pago_estado</code></td><td>ENUM</td><td><code>pendiente</code> / <code>pagado</code> / <code>exento</code></td></tr>
<tr><td><code>pago_modo</code></td><td>ENUM</td><td><code>efectivo</code> / <code>transferencia</code> / <code>otro</code></td></tr>
<tr><td><code>pago_monto</code></td><td>DECIMAL</td><td>Monto pagado</td></tr>
<tr><td><code>pago_fecha</code></td><td>DATETIME</td><td>Fecha del pago</td></tr>
<tr><td><code>pago_notas</code></td><td>TEXT</td><td>Notas sobre el pago</td></tr>
<tr><td><code>estado</code></td><td>ENUM</td><td>Ver flujo de estados</td></tr>
<tr><td><code>motivo_cancelacion</code></td><td>TEXT</td><td>Motivo si fue cancelado (obligatorio al cancelar)</td></tr>
<tr><td><code>fecha_reprogramada</code></td><td>DATE</td><td>Nueva fecha si fue reprogramado</td></tr>
<tr><td><code>hora_llegada</code></td><td>TIME</td><td>Registrada automáticamente al iniciar la visita</td></tr>
<tr><td><code>hora_salida</code></td><td>TIME</td><td>Registrada automáticamente al completar</td></tr>
<tr><td><code>observaciones</code></td><td>TEXT</td><td>Observaciones del resultado de la visita</td></tr>
<tr><td><code>muestras_tomadas</code></td><td>TEXT</td><td>Lista de muestras obtenidas</td></tr>
<tr><td><code>notas_admin</code></td><td>TEXT</td><td>Notas internas del equipo administrativo</td></tr>
<tr><td><code>creado_por</code></td><td><span class="badge badge-orange">INT FK</span></td><td>Usuario que creó el registro</td></tr>
</tbody>
</table>
</div>
<h4 id="dom-bd-enfermeras">Tabla <code>lab_enfermeras</code></h4>
<p>Personal de enfermería disponible para asignación.</p>
<div class="table-wrap">
<table>
<thead><tr><th>Campos</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>id</code>, <code>numero_documento</code></td><td>PK e identificador del empleado</td></tr>
<tr><td><code>tipo_documento</code></td><td>CC / CE / TI / PA</td></tr>
<tr><td><code>nombre_completo</code>, <code>telefono</code>, <code>telefono_alt</code></td><td>Datos de contacto</td></tr>
<tr><td><code>email</code>, <code>zona</code>, <code>notas</code></td><td>Información adicional</td></tr>
<tr><td><code>is_active</code></td><td>Soft-delete del registro</td></tr>
</tbody>
</table>
</div>
<h4 id="dom-bd-asignaciones">Tabla <code>lab_asignaciones</code></h4>
<p>Asignación de enfermero a domicilio. Máximo un enfermero activo por domicilio (<code>UNIQUE KEY</code> en <code>domicilio_id</code>).</p>
<div class="table-wrap">
<table>
<thead><tr><th>Campo</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>id</code>, <code>domicilio_id</code>, <code>enfermera_id</code></td><td>PK y relaciones</td></tr>
<tr><td><code>asignada_por</code></td><td>Usuario admin que realizó la asignación</td></tr>
<tr><td><code>estado</code></td><td><code>asignada</code> / <code>confirmada</code> / <code>liberada</code> / <code>completada</code></td></tr>
<tr><td><code>notas</code></td><td>Observaciones de la asignación</td></tr>
</tbody>
</table>
</div>
<h4 id="dom-bd-extras">Tabla <code>lab_servicios_extra</code></h4>
<p>Servicios realizados por el enfermero durante la visita, adicionales a la orden original.</p>
<div class="table-wrap">
<table>
<thead><tr><th>Campo</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>tipo</code></td><td><code>inyeccion</code> / <code>cura</code> / <code>nebulizacion</code> / <code>toma_muestra</code> / <code>tension_arterial</code> / <code>glucometria</code> / <code>otro</code></td></tr>
<tr><td><code>descripcion</code>, <code>notas</code></td><td>Detalle del servicio</td></tr>
<tr><td><code>requiere_pago</code>, <code>valor</code></td><td>Si genera cobro adicional y su valor</td></tr>
<tr><td><code>realizado_por</code></td><td>FK al enfermero que lo realizó</td></tr>
</tbody>
</table>
</div>
<h4 id="dom-bd-notas">Tabla <code>lab_domicilio_notas</code></h4>
<p>Notas registradas por el enfermero durante la visita. Dos tipos:</p>
<div class="features" style="margin-bottom:14px">
<div class="feature-item">
<strong>🩺 clinica</strong>
Ficha clínica: antecedentes, medicamentos, acudiente (si es menor de edad).
</div>
<div class="feature-item">
<strong>📝 libre</strong>
Nota libre con título, cuerpo de texto enriquecido e imagen adjunta.
</div>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>Campos</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>id</code>, <code>domicilio_id</code>, <code>enfermera_id</code></td><td>PK y relaciones</td></tr>
<tr><td><code>tipo</code></td><td><code>clinica</code> / <code>libre</code></td></tr>
<tr><td><code>antecedentes</code>, <code>medicamentos</code></td><td>Sección clínica</td></tr>
<tr><td><code>acudiente_nombre</code>, <code>acudiente_documento</code></td><td>Datos del acudiente si el paciente es menor</td></tr>
<tr><td><code>titulo</code>, <code>cuerpo</code>, <code>imagen_path</code></td><td>Sección de nota libre</td></tr>
</tbody>
</table>
</div>
<!-- ROLES -->
<h3 id="dom-roles">Roles y permisos</h3>
<div class="roles-grid">
<div class="role-card">
<h4>🛡️ Admin</h4>
<ul>
<li>Crear, editar y ver todos los domicilios</li>
<li>Asignar / reasignar enfermeros</li>
<li>Registrar pagos</li>
<li>Exportar Excel</li>
<li>Ver informe completo con notas</li>
<li>Ver agenda de cualquier enfermero con <code>?eid=X</code></li>
</ul>
</div>
<div class="role-card">
<h4>👩‍⚕️ Enfermero</h4>
<ul>
<li>Solo ve su propia agenda en <code>enfermero_portal.php</code></li>
<li>Avanza el estado de sus domicilios asignados</li>
<li>Agrega servicios extra</li>
<li>Registra notas clínicas y libres</li>
<li>Visualiza órdenes médicas adjuntas</li>
</ul>
</div>
</div>
<div class="callout warning">
<strong>⚠️ Redirección automática:</strong> si el usuario autenticado tiene rol <code>enfermero</code>,
<code>lab_domicilios.php</code> lo redirige inmediatamente a <code>enfermero_portal.php</code>.
</div>
<!-- FLUJO DE ESTADOS -->
<h3 id="dom-estados">Flujo de estados</h3>
<div class="flow">
<div class="flow-step s-prog">programado</div>
<div class="flow-arrow"></div>
<div class="flow-step s-conf">confirmado</div>
<div class="flow-arrow"></div>
<div class="flow-step s-cam">en_camino</div>
<div class="flow-arrow"></div>
<div class="flow-step s-dom">en_domicilio</div>
<div class="flow-arrow"></div>
<div class="flow-step s-comp">completado</div>
</div>
<div style="display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap">
<div class="flow-step s-canc" style="min-width:auto;padding:6px 14px;font-size:12px">⛔ cancelado</div>
<div class="flow-step s-repr" style="min-width:auto;padding:6px 14px;font-size:12px">🔄 reprogramado</div>
<span style="font-size:12px;color:var(--muted);align-self:center">← Posibles desde cualquier estado activo</span>
</div>
<p><strong>Notas automáticas según estado:</strong></p>
<div class="table-wrap">
<table>
<thead><tr><th>Evento</th><th>Acción automática</th></tr></thead>
<tbody>
<tr><td>Transición a <code>en_domicilio</code></td><td>Se registra <code>hora_llegada</code> automáticamente</td></tr>
<tr><td>Transición a <code>completado</code></td><td>Se registra <code>hora_salida</code> automáticamente</td></tr>
<tr><td>Transición a <code>cancelado</code></td><td>Campo <code>motivo_cancelacion</code> obligatorio</td></tr>
<tr><td>Transición a <code>reprogramado</code></td><td>Campo <code>fecha_reprogramada</code> obligatorio</td></tr>
</tbody>
</table>
</div>
<p><strong>Transiciones permitidas al enfermero</strong> (validadas en <code>update_domicilio_enfermero.php</code>):</p>
<div class="table-wrap">
<table>
<thead><tr><th>Estado actual</th><th>Estados posibles</th></tr></thead>
<tbody>
<tr><td><span class="badge badge-gray">programado</span></td><td><span class="badge badge-cyan">confirmado</span></td></tr>
<tr><td><span class="badge badge-cyan">confirmado</span></td><td><span class="badge badge-blue">en_camino</span> · <span class="badge badge-red">cancelado</span></td></tr>
<tr><td><span class="badge badge-blue">en_camino</span></td><td><span class="badge badge-orange">en_domicilio</span> · <span class="badge badge-red">cancelado</span></td></tr>
<tr><td><span class="badge badge-orange">en_domicilio</span></td><td><span class="badge badge-green">completado</span> · <span class="badge badge-red">cancelado</span></td></tr>
</tbody>
</table>
</div>
<div class="callout">
El <strong>admin</strong> puede cambiar a cualquier estado directamente, incluyendo cancelar desde cualquier punto.
</div>
<!-- FUNCIONALIDADES -->
<h3 id="dom-funciones">Funcionalidades</h3>
<div class="features">
<div class="feature-item"><strong>🔍 Filtros</strong>Por fecha, estado y enfermero. Botón "Hoy" para filtro rápido.</div>
<div class="feature-item"><strong>📊 Resumen del día</strong>Conteo de domicilios por estado en la parte superior.</div>
<div class="feature-item"><strong>📋 Panel de detalle</strong>Abre al hacer clic: info completa, notas clínicas e informe imprimible.</div>
<div class="feature-item"><strong>👥 Asignar enfermero</strong>Modal con lista del personal disponible. Incluye reasignación.</div>
<div class="feature-item"><strong>⚠️ Sin asignar</strong>Badge con conteo de domicilios sin enfermero asignado.</div>
<div class="feature-item"><strong>💰 Registrar pago</strong>Modal para marcar cobro con modalidad y monto.</div>
<div class="feature-item"><strong> Servicios extra</strong>El enfermero los agrega desde su portal durante la visita.</div>
<div class="feature-item"><strong>🩺 Notas del enfermero</strong>Ficha clínica y notas libres con imagen adjunta.</div>
<div class="feature-item"><strong>📄 Informe imprimible</strong>Vista imprimible con datos del paciente, ficha clínica y notas.</div>
<div class="feature-item"><strong>📁 Exportar CSV / Excel</strong>Incluye columnas de notas del enfermero.</div>
<div class="feature-item"><strong>📝 Formularios recibidos</strong>Pestaña que muestra formularios enviados con filtros.</div>
<div class="feature-item"><strong>📱 Portal del enfermero</strong>Tarjetas colapsables, separadas en Activos y Finalizados.</div>
</div>
<!-- API -->
<h3 id="dom-api">API Endpoints</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Endpoint</th><th>Método</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>api/lab/get_domicilios.php</code></td><td><span class="badge badge-blue">GET</span></td><td>Lista con filtros. <code>?id=X</code> para uno solo con detalle completo.</td></tr>
<tr><td><code>api/lab/save_domicilio.php</code></td><td><span class="badge badge-green">POST</span></td><td>Crear, editar o cambiar estado. Con <code>solo_estado=true</code> solo cambia el estado.</td></tr>
<tr><td><code>api/lab/update_domicilio_enfermero.php</code></td><td><span class="badge badge-green">POST</span></td><td>El enfermero avanza el estado de su domicilio.</td></tr>
<tr><td><code>api/lab/my_agenda.php</code></td><td><span class="badge badge-blue">GET</span></td><td>Agenda del enfermero autenticado con servicios extra.</td></tr>
<tr><td><code>api/lab/save_asignacion.php</code></td><td><span class="badge badge-green">POST</span></td><td>Asignar o reasignar enfermero a domicilio.</td></tr>
<tr><td><code>api/lab/get_asignaciones.php</code></td><td><span class="badge badge-blue">GET</span></td><td>Asignaciones por fecha.</td></tr>
<tr><td><code>api/lab/registrar_pago.php</code></td><td><span class="badge badge-green">POST</span></td><td>Registrar pago con monto y modalidad.</td></tr>
<tr><td><code>api/lab/save_servicio_extra.php</code></td><td><span class="badge badge-green">POST</span></td><td>Agregar servicio realizado durante la visita.</td></tr>
<tr><td><code>api/lab/get_notas_domicilio.php</code></td><td><span class="badge badge-blue">GET</span></td><td>Notas del domicilio (clínicas y libres).</td></tr>
<tr><td><code>api/lab/upload_nota_imagen.php</code></td><td><span class="badge badge-green">POST</span></td><td>Subir imagen adjunta a una nota libre.</td></tr>
<tr><td><code>api/lab/crear_desde_whatsapp.php</code></td><td><span class="badge badge-cyan">GET/POST</span></td><td>Crear paciente u orden desde una conversación de WhatsApp activa.</td></tr>
</tbody>
</table>
</div>
</div><!-- /module-body -->
</div><!-- /module -->
<!-- ╔═══════════════════════════════════════════╗
║ MÓDULO 2 — FORMULARIOS ║
╚═══════════════════════════════════════════╝ -->
<div class="module" id="form-intro">
<div class="module-header">
<div class="icon icon-violet">📝</div>
<div>
<h2>Módulo 2 — Formularios</h2>
<p>Builder visual, envío por WhatsApp, firma digital y sello SHA-256</p>
</div>
</div>
<div class="module-body">
<h3>¿Qué es?</h3>
<p>
El módulo de formularios permite crear <strong>plantillas de documentos</strong>
(consentimientos, historias clínicas, autorizaciones, encuestas) mediante un builder visual,
enviarlas a los pacientes por WhatsApp y recopilar sus respuestas con firma digital.
El documento firmado genera un <strong>sello de integridad SHA-256</strong> que puede verificarse públicamente.
</p>
<!-- ARCHIVOS -->
<h3 id="form-archivos">Archivos involucrados</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Archivo</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>lab_formularios.php</code></td><td>Vista principal — lista de plantillas y registro de envíos</td></tr>
<tr><td><code>lab_formulario_builder.php</code></td><td>Editor visual drag &amp; drop (ventana separada, solo admin)</td></tr>
<tr><td><code>form_cliente.php</code></td><td>Página pública — el paciente llena y firma sin iniciar sesión</td></tr>
<tr><td><code>ver_formulario_enviado.php</code></td><td>Vista del documento firmado — acceso por ID (admin/enfermero) o token (cliente)</td></tr>
<tr><td><code>verificar_formulario.php</code></td><td>Verificación pública de autenticidad por hash SHA-256</td></tr>
<tr><td><code>classes/lab/Formulario.php</code></td><td>Clase ORM — CRUD de plantillas, crear envíos, guardar respuestas, generar hash</td></tr>
<tr><td><code>api/lab/get_formularios.php</code></td><td>GET — lista plantillas o envíos</td></tr>
<tr><td><code>api/lab/save_formulario.php</code></td><td>POST — crear, editar y eliminar plantillas (solo admin)</td></tr>
<tr><td><code>api/lab/send_formulario.php</code></td><td>POST — crear instancia de envío, devolver URL pública y mensaje WhatsApp</td></tr>
<tr><td><code>api/lab/submit_formulario.php</code></td><td>GET/POST — cargar el formulario por token / guardar la respuesta del cliente</td></tr>
<tr><td><code>api/lab/firmar_profesional.php</code></td><td>POST — guardar firma del profesional (requiere sesión activa)</td></tr>
</tbody>
</table>
</div>
<!-- BASE DE DATOS -->
<h3 id="form-bd">Base de datos</h3>
<h4 id="form-bd-plantillas">Tabla <code>lab_formularios</code></h4>
<p>Plantillas de documentos creadas desde el builder.</p>
<div class="table-wrap">
<table>
<thead><tr><th>Campo</th><th>Tipo</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>id</code></td><td><span class="badge badge-blue">INT PK</span></td><td>Identificador único</td></tr>
<tr><td><code>nombre</code></td><td>VARCHAR(150)</td><td>Nombre de la plantilla</td></tr>
<tr><td><code>descripcion</code></td><td>TEXT</td><td>Descripción visible al cliente</td></tr>
<tr><td><code>categoria</code></td><td>ENUM</td><td><code>consentimiento</code> / <code>historia_clinica</code> / <code>autorizacion</code> / <code>encuesta</code> / <code>otro</code></td></tr>
<tr><td><code>esquema</code></td><td>LONGTEXT</td><td>JSON con el array de campos del formulario</td></tr>
<tr><td><code>permite_firma</code></td><td>TINYINT(1)</td><td>El formulario tiene sección de firma global</td></tr>
<tr><td><code>requiere_firma</code></td><td>TINYINT(1)</td><td>La firma global es obligatoria</td></tr>
<tr><td><code>firma_modos</code></td><td>VARCHAR(50)</td><td><code>canvas</code>, <code>foto</code> o <code>canvas,foto</code> (separados por coma)</td></tr>
<tr><td><code>version</code></td><td>SMALLINT</td><td>Se incrementa automáticamente al editar el esquema</td></tr>
<tr><td><code>is_active</code></td><td>TINYINT(1)</td><td>Soft-delete</td></tr>
<tr><td><code>creado_por</code></td><td><span class="badge badge-orange">INT FK</span></td><td>Usuario que creó la plantilla</td></tr>
<tr><td><code>doc_encabezado</code></td><td>VARCHAR</td><td>Override del nombre de empresa en el documento</td></tr>
<tr><td><code>doc_subtitulo</code></td><td>VARCHAR</td><td>Override del subtítulo en el documento</td></tr>
<tr><td><code>doc_logo_base64</code></td><td>LONGTEXT</td><td>Override del logo en el documento</td></tr>
<tr><td><code>doc_color</code></td><td>VARCHAR(20)</td><td>Override del color del encabezado del documento</td></tr>
<tr><td><code>doc_pie_pagina</code></td><td>TEXT</td><td>Override del pie de página</td></tr>
</tbody>
</table>
</div>
<h4 id="form-bd-envios">Tabla <code>lab_form_envios</code></h4>
<p>Cada fila es una instancia enviada a un paciente.</p>
<div class="table-wrap">
<table>
<thead><tr><th>Campo</th><th>Tipo</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>id</code></td><td><span class="badge badge-blue">INT PK</span></td><td>Identificador único</td></tr>
<tr><td><code>formulario_id</code></td><td><span class="badge badge-orange">INT FK</span></td><td>Plantilla enviada</td></tr>
<tr><td><code>paciente_id</code></td><td><span class="badge badge-gray">INT FK NULL</span></td><td>Paciente asociado</td></tr>
<tr><td><code>domicilio_id</code></td><td><span class="badge badge-gray">INT FK NULL</span></td><td>Domicilio asociado (opcional)</td></tr>
<tr><td><code>token</code></td><td>CHAR(64) UNIQUE</td><td>Token público — 64 chars hex (acceso sin sesión)</td></tr>
<tr><td><code>datos_prefilled</code></td><td>LONGTEXT</td><td>JSON con datos pre-llenados al enviar (incluye <code>__paciente.*</code>)</td></tr>
<tr><td><code>datos_cliente</code></td><td>LONGTEXT</td><td>JSON con las respuestas completadas por el cliente</td></tr>
<tr><td><code>firma_svg</code></td><td>LONGTEXT</td><td>Firma del paciente (PNG base64 — canvas o foto)</td></tr>
<tr><td><code>ip_cliente</code></td><td>VARCHAR(45)</td><td>IP del cliente al enviar el formulario</td></tr>
<tr><td><code>user_agent</code></td><td>VARCHAR(512)</td><td>Navegador del cliente</td></tr>
<tr><td><code>estado</code></td><td>ENUM</td><td><code>pendiente</code> / <code>completado</code> / <code>firmado</code> / <code>expirado</code></td></tr>
<tr><td><code>enviado_por</code></td><td><span class="badge badge-orange">INT FK</span></td><td>Usuario que generó el enlace</td></tr>
<tr><td><code>enviado_via</code></td><td>ENUM</td><td><code>whatsapp</code> / <code>email</code> / <code>link</code></td></tr>
<tr><td><code>expira_en</code></td><td>DATETIME NULL</td><td>Siempre <code>NULL</code> — el enlace no expira</td></tr>
<tr><td><code>completado_en</code></td><td>DATETIME</td><td>Fecha y hora en que el cliente completó el formulario</td></tr>
<tr><td><code>hash_verificacion</code></td><td>CHAR(64)</td><td>Sello de integridad SHA-256 del documento</td></tr>
</tbody>
</table>
</div>
<!-- ROLES -->
<h3 id="form-roles">Roles y permisos</h3>
<div class="roles-grid">
<div class="role-card">
<h4>🛡️ Admin</h4>
<ul>
<li>Crear, editar y eliminar plantillas en el builder</li>
<li>Enviar formularios a cualquier paciente</li>
<li>Ver todos los envíos</li>
<li>Descargar PDF de cualquier formulario</li>
</ul>
</div>
<div class="role-card">
<h4>👩‍⚕️ Enfermero</h4>
<ul>
<li>Enviar formularios existentes a sus pacientes</li>
<li>Ver solo sus propios envíos (<code>enviado_por = su user_id</code>)</li>
<li>No puede crear ni editar plantillas</li>
<li>Puede firmar como profesional en formularios que él mismo envió</li>
</ul>
</div>
<div class="role-card">
<h4>🌐 Cliente (público)</h4>
<ul>
<li>Accede a <code>form_cliente.php?t=TOKEN</code> sin autenticación</li>
<li>Llena y firma el formulario</li>
<li>Puede volver al mismo enlace para ver el documento firmado</li>
<li>Puede descargarlo como PDF</li>
</ul>
</div>
</div>
<!-- FLUJO COMPLETO -->
<h3 id="form-flujo">Flujo completo</h3>
<ol class="steps">
<li>
<div>
<strong>Admin crea la plantilla</strong><br>
Abre <code>lab_formulario_builder.php</code> (ventana nueva), arrastra campos al canvas,
configura diseño del documento (logo, color, encabezado, pie de página)
y guarda → POST <code>api/lab/save_formulario.php</code>.
</div>
</li>
<li>
<div>
<strong>Admin o Enfermero envía el formulario</strong><br>
Desde <code>lab_formularios.php</code> → botón "Enviar" → selecciona paciente →
elige canal (WhatsApp o solo link) → POST <code>api/lab/send_formulario.php</code>.
Se genera un token de 64 chars (<code>bin2hex(random_bytes(32))</code>)
y se crea la fila en <code>lab_form_envios</code> con <code>estado = pendiente</code>.
</div>
</li>
<li>
<div>
<strong>Cliente recibe el enlace</strong><br>
Abre <code>form_cliente.php?t=TOKEN</code> → GET <code>api/lab/submit_formulario.php?t=TOKEN</code>
carga los datos. Si ya fue firmado → muestra pantalla de solo lectura con link al PDF.
</div>
</li>
<li>
<div>
<strong>Cliente llena el formulario</strong><br>
Campos "linked" llegan pre-llenados con datos del paciente (readonly si tienen valor, editables si están vacíos).
Los campos <code>firma_profesional</code> muestran aviso <em>"uso exclusivo del profesional"</em>.
El cliente dibuja su firma o adjunta una foto.
</div>
</li>
<li>
<div>
<strong>Cliente envía</strong><br>
POST <code>api/lab/submit_formulario.php</code> → se genera hash SHA-256
(contenido + firma + ID + token + timestamp) →
estado cambia a <code>firmado</code> (con firma) o <code>completado</code> (sin firma).
Pantalla de éxito con hash visible y botón para descargar el PDF.
</div>
</li>
<li>
<div>
<strong>Profesional firma (si el formulario lo requiere)</strong><br>
Admin/Enfermero abre <code>ver_formulario_enviado.php?id=X</code> con sesión activa,
dibuja su firma en el canvas del campo <code>firma_profesional</code>
→ POST <code>api/lab/firmar_profesional.php</code>.
</div>
</li>
<li>
<div>
<strong>Admin/Enfermero revisa el resultado</strong><br>
<code>lab_formularios.php</code> → pestaña "Envíos" → icono "Ver respuesta" →
<code>ver_formulario_enviado.php?id=X</code> — documento HTML imprimible.
</div>
</li>
<li>
<div>
<strong>Verificación pública de integridad</strong><br>
<code>verificar_formulario.php?h=HASH_SHA256</code> → busca en <code>hash_verificacion</code>
→ muestra nombre del formulario, paciente, fecha, estado y si el sello es válido.
</div>
</li>
</ol>
<!-- TIPOS DE CAMPOS -->
<h3 id="form-campos">Tipos de campos del Builder</h3>
<h4>Campos de entrada</h4>
<div class="table-wrap">
<table>
<thead><tr><th>Tipo</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>texto</code></td><td>Campo de texto corto de una sola línea</td></tr>
<tr><td><code>textarea</code></td><td>Área de texto largo (varias líneas)</td></tr>
<tr><td><code>numero</code></td><td>Campo numérico</td></tr>
<tr><td><code>fecha</code></td><td>Selector de fecha</td></tr>
<tr><td><code>hora</code></td><td>Selector de hora</td></tr>
<tr><td><code>select</code></td><td>Lista desplegable con opciones configurables</td></tr>
<tr><td><code>radio</code></td><td>Selección única con opciones configurables</td></tr>
<tr><td><code>checkbox</code></td><td>Selección múltiple con opciones configurables</td></tr>
<tr><td><code>lista_marcable</code></td><td>Lista de ítems numerados con checkboxes</td></tr>
</tbody>
</table>
</div>
<h4>Campos de firma</h4>
<div class="table-wrap">
<table>
<thead><tr><th>Tipo</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>firma</code></td><td>Firma del <strong>paciente</strong> — visible y editable en <code>form_cliente.php</code></td></tr>
<tr><td><code>firma_profesional</code></td><td>Firma del <strong>profesional</strong> — bloqueada para el cliente; solo editable desde el panel admin/enfermero</td></tr>
</tbody>
</table>
</div>
<h4>Campos de contenido</h4>
<div class="table-wrap">
<table>
<thead><tr><th>Tipo</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>separador</code></td><td>Separador visual o título de sección</td></tr>
<tr><td><code>parrafo</code></td><td>Bloque de texto estático (pre-formatado o flujo libre)</td></tr>
<tr><td><code>parrafo_inline</code></td><td>Párrafo con marcadores <code>{nombre_completo}</code>, <code>{telefono}</code>, etc. que se convierten en espacios editables si el valor está vacío</td></tr>
</tbody>
</table>
</div>
<h4>Campos vinculados al paciente (<code>tipo: linked</code>)</h4>
<p>Se auto-rellenan con los datos del paciente al momento de enviar. Si el valor existe → campo de solo lectura. Si está vacío → el cliente puede completarlo.</p>
<div class="table-wrap">
<table>
<thead><tr><th><code>linked_key</code></th><th>Dato que extrae</th></tr></thead>
<tbody>
<tr><td><code>nombre_completo</code></td><td>Nombre completo del paciente</td></tr>
<tr><td><code>numero_documento</code></td><td>Número de documento</td></tr>
<tr><td><code>tipo_documento</code></td><td>Tipo de documento</td></tr>
<tr><td><code>fecha_nacimiento</code></td><td>Fecha de nacimiento</td></tr>
<tr><td><code>telefono</code></td><td>Teléfono</td></tr>
<tr><td><code>email</code></td><td>Correo electrónico</td></tr>
<tr><td><code>eps</code></td><td>EPS o aseguradora</td></tr>
<tr><td><code>direccion</code></td><td>Dirección</td></tr>
</tbody>
</table>
</div>
<!-- FIRMA DIGITAL -->
<h3 id="form-firma-dig">Firma digital</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Modo</th><th>Funcionamiento</th></tr></thead>
<tbody>
<tr>
<td><span class="badge badge-violet">canvas</span></td>
<td>El cliente dibuja su firma con el dedo o el mouse. Se captura con <code>canvas.toDataURL('image/png')</code>.</td>
</tr>
<tr>
<td><span class="badge badge-orange">foto</span></td>
<td>El cliente sube una imagen desde su cámara o galería (<code>&lt;input accept="image/*" capture="environment"&gt;</code>). Se convierte a base64 con <code>FileReader</code>.</td>
</tr>
</tbody>
</table>
</div>
<div class="callout">
Ambos modos pueden estar activos simultáneamente en la misma plantilla configurando <code>firma_modos = "canvas,foto"</code>.
</div>
<p>
<strong>Firma global vs. firma por campo:</strong><br>
Si el esquema <strong>no incluye</strong> campos tipo <code>firma</code>, se muestra una sección de firma global al pie del formulario.
Si el esquema <strong>incluye</strong> campos <code>firma</code>, cada uno tiene su propio widget canvas en la posición configurada.
</p>
<!-- ENLACE Y VIGENCIA -->
<h3 id="form-enlace">Enlace público y vigencia</h3>
<div class="features">
<div class="feature-item"><strong>🔗 URL pública</strong><code>form_cliente.php?t=TOKEN</code></div>
<div class="feature-item"><strong>🔑 Token</strong>64 chars hex — <code>bin2hex(random_bytes(32))</code></div>
<div class="feature-item"><strong>🔓 Sin sesión</strong>No requiere cuenta ni iniciar sesión</div>
<div class="feature-item"><strong>♾️ Sin vencimiento</strong><code>expira_en</code> siempre es <code>NULL</code></div>
<div class="feature-item"><strong>🔒 Bloqueo por estado</strong>Si ya fue firmado → solo lectura, no permite modificar</div>
<div class="feature-item"><strong>🔁 Idempotencia</strong>Si el cliente reintenta enviar, devuelve éxito sin duplicar</div>
</div>
<!-- PDF -->
<h3 id="form-pdf">PDF y visualización del documento</h3>
<div class="callout success">
<strong>✅ Sin librerías de PDF:</strong> el documento es la página <code>ver_formulario_enviado.php</code>
con estilos <code>@media print</code>. El usuario imprime o guarda como PDF directamente desde el navegador.
</div>
<p><strong>Contenido del documento impreso:</strong></p>
<ul style="padding-left:20px;font-size:13.5px;margin-bottom:16px;line-height:2">
<li>Encabezado con logo, nombre, subtítulo, datos de contacto y color corporativo</li>
<li>Datos del paciente (nombre, documento, fecha de nacimiento, teléfono, EPS)</li>
<li>Respuestas del formulario campo por campo en el orden del esquema</li>
<li>Imagen de la firma del paciente</li>
<li>Firma del profesional (si fue completada)</li>
<li>Sello SHA-256 con link para verificar autenticidad</li>
<li>Pie de página con fecha de generación e ID del documento</li>
</ul>
<p><strong>Formas de acceder al documento:</strong></p>
<div class="table-wrap">
<table>
<thead><tr><th>URL</th><th>Quién puede acceder</th></tr></thead>
<tbody>
<tr><td><code>ver_formulario_enviado.php?id=X</code></td><td>Admin (cualquier formulario) o Enfermero (solo los que él envió). Requiere sesión.</td></tr>
<tr><td><code>ver_formulario_enviado.php?t=TOKEN</code></td><td>Cliente u cualquier persona con el enlace. Sin sesión. Solo si el estado es <code>firmado</code> o <code>completado</code>.</td></tr>
</tbody>
</table>
</div>
<!-- SHA-256 -->
<h3 id="form-sha">Sello de integridad SHA-256</h3>
<p>Al guardar la respuesta del cliente, el sistema genera un hash SHA-256 que vincula de forma única el contenido del formulario con la firma y el momento en que se completó.</p>
<p><strong>Construcción del hash</strong> (en <code>Formulario::guardarRespuesta()</code>):</p>
<pre><code>SHA-256 de:
JSON de los datos del cliente
+ firma SVG/PNG del paciente
+ ID interno del envío
+ token del enlace
+ timestamp del momento de registro</code></pre>
<div class="callout">
<strong>¿Para qué sirve?</strong> Cualquier persona con el hash puede ir a
<code>verificar_formulario.php?h=HASH</code> para confirmar que el documento existe,
el nombre del formulario y del paciente, la fecha en que fue completado y el estado actual.
Si el documento fue alterado, el hash no coincidirá y la verificación fallará.
</div>
<!-- FIRMA DEL PROFESIONAL -->
<h3 id="form-firmapro">Firma del profesional</h3>
<ol class="steps">
<li>
<div>Al diseñar la plantilla en el builder se agrega un campo <code>tipo: firma_profesional</code> en la posición deseada.</div>
</li>
<li>
<div>Cuando el cliente llena el formulario, ese campo muestra solo un aviso: <em>"Uso exclusivo del profesional de salud"</em>. El cliente no puede interactuar con él.</div>
</li>
<li>
<div>Una vez que el cliente ha completado y enviado, el admin o enfermero abre <code>ver_formulario_enviado.php?id=X</code> con sesión activa y verá el canvas de firma en esa posición.</div>
</li>
<li>
<div>El profesional dibuja su firma y hace clic en "Guardar firma" → POST <code>api/lab/firmar_profesional.php</code>.</div>
</li>
</ol>
<p><strong>Validaciones en el servidor:</strong></p>
<ul style="padding-left:20px;font-size:13.5px;margin-bottom:16px;line-height:2">
<li>Requiere sesión activa (<code>isUserLoggedIn()</code>)</li>
<li>Si el usuario es enfermero, solo puede firmar en formularios que él mismo envió</li>
<li>Valida que el <code>campo_id</code> corresponde a un campo <code>tipo: firma_profesional</code> en el esquema</li>
<li>Valida que la imagen enviada sea un data URI de imagen válido (prevención XSS/inyección)</li>
</ul>
<div class="callout warning">
Si el formulario se accede via <code>?t=TOKEN</code> (cliente público), el canvas <strong>no aparece</strong>.
En su lugar se muestra un aviso <em>"Pendiente de firma del profesional"</em>
(solo visible en pantalla, no en el PDF impreso).
</div>
<!-- API -->
<h3 id="form-api">API Endpoints</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Endpoint</th><th>Método</th><th>Autenticación</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>api/lab/get_formularios.php</code></td><td><span class="badge badge-blue">GET</span></td><td><span class="badge badge-orange">Sesión</span></td><td>Lista plantillas. <code>?id=X</code> para una sola. <code>?envios=1</code> para lista de envíos.</td></tr>
<tr><td><code>api/lab/save_formulario.php</code></td><td><span class="badge badge-green">POST</span></td><td><span class="badge badge-red">Admin</span></td><td>Crear, editar o eliminar una plantilla.</td></tr>
<tr><td><code>api/lab/send_formulario.php</code></td><td><span class="badge badge-green">POST</span></td><td><span class="badge badge-orange">Sesión</span></td><td>Crear instancia de envío. Devuelve URL pública y mensaje para WhatsApp.</td></tr>
<tr><td><code>api/lab/submit_formulario.php</code></td><td><span class="badge badge-blue">GET</span></td><td><span class="badge badge-green">Público</span></td><td>Cargar el formulario por token (sin sesión).</td></tr>
<tr><td><code>api/lab/submit_formulario.php</code></td><td><span class="badge badge-green">POST</span></td><td><span class="badge badge-green">Público</span></td><td>Guardar la respuesta y firma del cliente.</td></tr>
<tr><td><code>api/lab/firmar_profesional.php</code></td><td><span class="badge badge-green">POST</span></td><td><span class="badge badge-orange">Sesión</span></td><td>Guardar la firma del profesional en un campo <code>firma_profesional</code>.</td></tr>
</tbody>
</table>
</div>
</div><!-- /module-body -->
</div><!-- /module -->
<p style="text-align:center;color:var(--muted);font-size:12px;margin-top:8px;padding-bottom:32px">
Documentación generada para uso interno del equipo · 30/03/2026
</p>
<!-- ╔═══════════════════════════════════════════╗
║ MANUAL DE USO ║
╚═══════════════════════════════════════════╝ -->
<!-- ── ADMIN: DOMICILIOS ── -->
<div class="module" id="man-admin">
<div class="module-header">
<div class="icon icon-blue">🛡️</div>
<div>
<h2>Manual de Uso — Administrador · Domicilios</h2>
<p><span class="role-badge rb-admin">🛡️ Admin</span> &nbsp; Gestión completa desde <code>lab_domicilios.php</code></p>
</div>
</div>
<div class="module-body">
<!-- Crear domicilio -->
<h3 id="man-admin-crear">Crear un nuevo domicilio</h3>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Abrir el formulario</h5>
<p>Haz clic en el botón <span class="man-ui-tag"> Nuevo Domicilio</span> ubicado en la esquina superior derecha de la pantalla. Se abrirá un modal con el formulario de creación.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Buscar y seleccionar el paciente</h5>
<p>En el campo <em>Paciente</em>, empieza a escribir el nombre o documento del paciente. Aparecerá un listado de sugerencias; haz clic en el paciente correcto para seleccionarlo.</p>
<div class="man-tip">✅ El paciente debe existir previamente en el sistema. Si no aparece, créalo primero desde <code>lab_pacientes.php</code>.</div>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">3</div>
<div class="man-step-body">
<h5>Llenar los datos del servicio</h5>
<p>Completa los campos requeridos (<span style="color:var(--danger)">*</span>):</p>
<ul style="padding-left:18px;font-size:13.5px;line-height:2">
<li><strong>Dirección completa</strong> — incluye barrio e indicaciones adicionales si aplica</li>
<li><strong>Fecha programada</strong> — se pre-llena con la fecha del filtro activo</li>
<li><strong>Tipo de servicio</strong> — ej: "toma de muestras", "inyección"</li>
<li><strong>Tipo de cliente</strong> — si estás en <em>Seguro / EPS</em>, aparecerá el campo del nombre del seguro</li>
<li><strong>Valores de cobro</strong> — valor domicilio y/o copago (dejar vacío si no aplica)</li>
</ul>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">4</div>
<div class="man-step-body">
<h5>Adjuntar orden médica (opcional)</h5>
<p>Si tienes una imagen o PDF con la orden del médico, haz clic en <span class="man-ui-tag">📎 Adjuntar orden</span>. Se acepta JPG, PNG, PDF y DOC hasta 10 MB.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">5</div>
<div class="man-step-body">
<h5>Guardar</h5>
<p>Haz clic en <span class="man-ui-tag">💾 Guardar</span>. El domicilio aparecerá en la tabla con estado <span class="state-pill sp-prog">programado</span>.</p>
</div>
</div>
<!-- Asignar enfermero -->
<h3 id="man-admin-asignar">Asignar un enfermero</h3>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Seleccionar el domicilio</h5>
<p>Haz clic en la fila del domicilio en la tabla. Se abrirá el panel de detalle a la derecha. Si el domicilio no tiene enfermero, verás un aviso en naranja: <em>"Sin enfermero asignado"</em>.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Abrir el modal de asignación</h5>
<p>Haz clic en <span class="man-ui-tag">👤 Asignar</span> (o <span class="man-ui-tag">Reasignar</span> si ya tiene uno). Se abrirá un modal con la lista del personal disponible.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">3</div>
<div class="man-step-body">
<h5>Seleccionar y confirmar</h5>
<p>En el desplegable elige el enfermero. Verás cuántos domicilios tiene asignados hoy y su usuario de acceso al portal. Haz clic en <span class="man-ui-tag">✔ Confirmar asignación</span>.</p>
<div class="man-tip">✅ Una vez asignado, el enfermero verá este domicilio en su portal al ingresar.</div>
</div>
</div>
<!-- Cambiar estado -->
<h3 id="man-admin-estado">Cambiar el estado de un domicilio</h3>
<p>Los botones de avance de estado aparecen en el panel de detalle del domicilio seleccionado:</p>
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px;align-items:center">
<span class="state-pill sp-prog">programado</span>
<span style="color:var(--muted)"></span>
<span class="state-pill sp-conf">Confirmar</span>
<span style="color:var(--muted)"></span>
<span class="state-pill sp-cam">En camino</span>
<span style="color:var(--muted)"></span>
<span class="state-pill sp-dom">En domicilio</span>
<span style="color:var(--muted)"></span>
<span class="state-pill sp-comp">Completar</span>
</div>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Seleccionar el domicilio y hacer clic en el botón de estado</h5>
<p>En el panel de detalle aparecerán uno o dos botones según el estado actual. Haz clic en el que corresponda y confirma en el diálogo que aparece.</p>
</div>
</div>
<div class="man-warn" style="margin-bottom:16px">
⚠️ Al cambiar a <span class="state-pill sp-canc">cancelado</span> el sistema pedirá un motivo de cancelación. Este campo es obligatorio.
</div>
<div class="man-tip" style="margin-bottom:0">
✅ Los estados <strong>en_domicilio</strong> (hora llegada) y <strong>completado</strong> (hora salida) registran la hora automáticamente si el enfermero realiza la transición desde su portal.
</div>
<!-- Registrar pago -->
<h3 id="man-admin-pago">Registrar un pago</h3>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Domicilio con valores de cobro</h5>
<p>En el panel de detalle, si el domicilio tiene <em>Valor domicilio</em> o <em>Copago</em> asignados, verás la sección de cobro con un badge <span class="state-pill sp-prog">⏳ Pago pendiente</span>.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Abrir el registro de pago</h5>
<p>Haz clic en el botón <span class="man-ui-tag">💳 Registrar pago</span> dentro del bloque de cobro.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">3</div>
<div class="man-step-body">
<h5>Completar los datos del pago</h5>
<p>Selecciona la <strong>modalidad</strong> (💵 Efectivo / 📲 Transferencia / 📋 Otro), ingresa el <strong>monto recibido</strong> y agrega notas si es necesario. Haz clic en <span class="man-ui-tag">Confirmar pago</span>.</p>
<div class="man-tip">✅ Si el servicio es para un cliente con seguro y no genera cobro directo, selecciona <strong>Exento</strong> desde las opciones de pago_estado.</div>
</div>
</div>
<!-- Filtros y exportar -->
<h3 id="man-admin-filtros">Filtros y exportar a Excel</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Control</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><span class="man-ui-tag">📅 Fecha</span></td><td>Filtra los domicilios por fecha programada. Se carga con la fecha actual al abrir la pantalla.</td></tr>
<tr><td><span class="man-ui-tag">Estado</span></td><td>Filtra por estado del domicilio. Deja en blanco para ver todos.</td></tr>
<tr><td><span class="man-ui-tag">Enfermero</span></td><td>Filtra solo los domicilios asignados a ese enfermero.</td></tr>
<tr><td><span class="man-ui-tag">📅 Hoy</span></td><td>Restablece el filtro de fecha a la fecha de hoy.</td></tr>
<tr><td><span class="man-ui-tag">📁 Excel</span></td><td>Exporta todos los domicilios que coincidan con los filtros activos, incluyendo notas del enfermero.</td></tr>
</tbody>
</table>
</div>
<div class="man-tip">✅ Los cambios en los filtros se aplican automáticamente sin necesidad de hacer clic en un botón de buscar.</div>
</div>
</div>
<!-- ── ENFERMERO: PORTAL ── -->
<div class="module" id="man-enf">
<div class="module-header">
<div class="icon icon-green">👩‍⚕️</div>
<div>
<h2>Manual de Uso — Enfermero · Portal</h2>
<p><span class="role-badge rb-enf">👩‍⚕️ Enfermero</span> &nbsp; Gestión de agenda personal desde <code>enfermero_portal.php</code></p>
</div>
</div>
<div class="module-body">
<!-- Acceder -->
<h3 id="man-enf-acceso">Acceder al portal</h3>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Iniciar sesión</h5>
<p>Ve a <code>login.php</code> e ingresa con el usuario y contraseña que el administrador te asignó. El sistema te redirigirá automáticamente a <code>enfermero_portal.php</code>.</p>
<div class="man-tip">✅ Si intentas acceder a otra sección del sistema, serás redirigido de vuelta a tu portal automáticamente.</div>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Ver tu agenda del día</h5>
<p>Encontrarás dos bloques: <strong>Activos</strong> (domicilios pendientes de hoy) y <strong>Finalizados</strong> (completados o cancelados). Cada domicilio aparece como una tarjeta colapsable ordenada por hora.</p>
</div>
</div>
<!-- Avanzar estado -->
<h3 id="man-enf-avanzar">Avanzar el estado de un domicilio</h3>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Expandir la tarjeta</h5>
<p>Haz clic en la tarjeta del domicilio para expandirla y ver el detalle completo: paciente, dirección, hora, orden médica adjunta y botones de acción.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Usar el botón de estado correspondiente</h5>
<div class="table-wrap" style="margin-top:8px">
<table>
<thead><tr><th>Estado actual</th><th>Botón disponible</th><th>¿Qué ocurre?</th></tr></thead>
<tbody>
<tr><td><span class="state-pill sp-prog">programado</span></td><td><span class="man-ui-tag">✔ Confirmar</span></td><td>Confirmas que realizarás la visita</td></tr>
<tr><td><span class="state-pill sp-conf">confirmado</span></td><td><span class="man-ui-tag">🚗 En camino</span></td><td>Indicas que saliste hacia el domicilio</td></tr>
<tr><td><span class="state-pill sp-cam">en_camino</span></td><td><span class="man-ui-tag">🏠 En domicilio</span></td><td>Indicas que llegaste — se registra la <strong>hora de llegada</strong> automáticamente</td></tr>
<tr><td><span class="state-pill sp-dom">en_domicilio</span></td><td><span class="man-ui-tag">✅ Completar</span></td><td>Finalizas la visita — se registra la <strong>hora de salida</strong> automáticamente</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="man-warn">
⚠️ Si necesitas cancelar un domicilio, deberás ingresar el motivo. Consulta con el administrador si tienes dudas sobre cuándo cancelar.
</div>
<!-- Registrar notas -->
<h3 id="man-enf-notas">Registrar notas clínicas y libres</h3>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Ficha clínica del paciente</h5>
<p>Dentro de la tarjeta del domicilio, busca la sección <strong>Ficha clínica</strong>. Puedes registrar:</p>
<ul style="padding-left:18px;font-size:13.5px;line-height:2">
<li><strong>Antecedentes</strong> — patologías previas del paciente</li>
<li><strong>Medicamentos</strong> — medicamentos que toma actualmente</li>
<li><strong>Acudiente</strong> — nombre y documento del acudiente si el paciente es menor de edad</li>
</ul>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Nota libre</h5>
<p>Haz clic en <span class="man-ui-tag"> Agregar nota</span>. Puedes escribir un título, el cuerpo de la nota y adjuntar una imagen (por ejemplo, una foto del estado del paciente o de las muestras tomadas).</p>
<div class="man-tip">✅ Las notas quedan visibles para el administrador en el panel de detalle y en el informe imprimible del domicilio.</div>
</div>
</div>
<!-- Servicios extra -->
<h3 id="man-enf-extras">Agregar servicios extra</h3>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>¿Cuándo usar esto?</h5>
<p>Si durante la visita realizas servicios adicionales a los de la orden original (ej: una inyección, una curación, toma de tensión arterial), puedes registrarlos para que queden en el historial.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Agregar el servicio</h5>
<p>En la tarjeta del domicilio, haz clic en <span class="man-ui-tag"> Servicio extra</span>. Selecciona el tipo, agrega una descripción y, si genera cobro adicional, marca la opción <em>Requiere pago</em> e ingresa el valor.</p>
</div>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>Tipo disponible</th><th>Descripción</th></tr></thead>
<tbody>
<tr><td><code>inyeccion</code></td><td>Administración de medicamento inyectable</td></tr>
<tr><td><code>cura</code></td><td>Curación de herida o procedimiento de limpieza</td></tr>
<tr><td><code>nebulizacion</code></td><td>Terapia de nebulización</td></tr>
<tr><td><code>toma_muestra</code></td><td>Toma de muestra adicional</td></tr>
<tr><td><code>tension_arterial</code></td><td>Medición de tensión arterial</td></tr>
<tr><td><code>glucometria</code></td><td>Medición de glucosa en sangre</td></tr>
<tr><td><code>otro</code></td><td>Cualquier otro procedimiento (especificar en descripción)</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- ── ADMIN: FORMULARIOS ── -->
<div class="module" id="man-form">
<div class="module-header">
<div class="icon icon-violet">📋</div>
<div>
<h2>Manual de Uso — Administrador · Formularios</h2>
<p><span class="role-badge rb-admin">🛡️ Admin</span> <span class="role-badge rb-enf" style="margin-left:4px">👩‍⚕️ Enfermero (envío)</span> &nbsp; Desde <code>lab_formularios.php</code></p>
</div>
</div>
<div class="module-body">
<!-- Crear plantilla -->
<h3 id="man-form-crear">Crear una plantilla de formulario</h3>
<div class="callout" style="margin-bottom:16px">
<strong>Solo el Admin</strong> puede crear y editar plantillas. Los enfermeros solo pueden enviar formularios existentes.
</div>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Ir al builder</h5>
<p>Desde <code>lab_formularios.php</code>, haz clic en <span class="man-ui-tag"> Nueva plantilla</span>. Se abrirá el editor <code>lab_formulario_builder.php</code> en una ventana nueva.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Configurar el encabezado del documento</h5>
<p>En el panel derecho del builder puedes personalizar: nombre de la empresa, subtítulo, logo (imagen base64), color del encabezado y pie de página. Estos datos aparecerán en el PDF imprimible.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">3</div>
<div class="man-step-body">
<h5>Agregar campos al formulario</h5>
<p>Arrastra campos del panel izquierdo al canvas central. Tipos disponibles:</p>
<div style="display:flex;flex-wrap:wrap;gap:6px;margin:8px 0">
<span class="man-ui-tag">Texto</span>
<span class="man-ui-tag">Textarea</span>
<span class="man-ui-tag">Número</span>
<span class="man-ui-tag">Fecha / Hora</span>
<span class="man-ui-tag">Select</span>
<span class="man-ui-tag">Radio</span>
<span class="man-ui-tag">Checkbox</span>
<span class="man-ui-tag">Lista marcable</span>
<span class="man-ui-tag">Vinculado al paciente</span>
<span class="man-ui-tag">Párrafo inline</span>
<span class="man-ui-tag">Firma paciente</span>
<span class="man-ui-tag">Firma profesional</span>
</div>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">4</div>
<div class="man-step-body">
<h5>Configurar la firma</h5>
<p>En la sección <em>Firma</em> del builder puedes activar los modos permitidos: <span class="man-ui-tag">✍️ Canvas</span> (dibujo) y/o <span class="man-ui-tag">📷 Foto</span>. Si la firma es obligatoria, marca <em>Requerida</em>.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">5</div>
<div class="man-step-body">
<h5>Guardar la plantilla</h5>
<p>Haz clic en <span class="man-ui-tag">💾 Guardar</span>. La plantilla quedará disponible en <code>lab_formularios.php</code> para ser enviada a pacientes.</p>
<div class="man-tip">✅ Cada vez que edites el esquema de campos, la versión de la plantilla se incrementa automáticamente.</div>
</div>
</div>
<!-- Enviar a paciente -->
<h3 id="man-form-enviar">Enviar un formulario a un paciente</h3>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Seleccionar la plantilla y hacer clic en Enviar</h5>
<p>En <code>lab_formularios.php</code> busca la plantilla que deseas enviar y haz clic en el botón <span class="man-ui-tag">📤 Enviar</span>. Se abrirá un modal.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Buscar y seleccionar el paciente</h5>
<p>Empieza a escribir el nombre o documento del paciente. Al seleccionarlo, verás una vista previa de los campos que se pre-llenarán automáticamente con sus datos.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">3</div>
<div class="man-step-body">
<h5>Elegir el canal de envío</h5>
<p>Selecciona cómo compartirás el enlace:</p>
<ul style="padding-left:18px;font-size:13.5px;line-height:2">
<li><strong>WhatsApp</strong> — genera un mensaje pre-redactado con el enlace listo para enviar desde en la conversación del paciente</li>
<li><strong>Solo link</strong> — copia el enlace y compártelo como prefieras</li>
</ul>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">4</div>
<div class="man-step-body">
<h5>Confirmar el envío</h5>
<p>Haz clic en <span class="man-ui-tag">Generar enlace</span>. Se creará un token único para ese paciente. El enlace generado no tiene fecha de vencimiento y es de un solo uso (no permite modificar la respuesta una vez enviada).</p>
<div class="man-tip">✅ El enlace funciona sin que el paciente necesite crear cuenta ni iniciar sesión.</div>
</div>
</div>
<!-- Revisar respuestas -->
<h3 id="man-form-revisar">Revisar las respuestas de un formulario</h3>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Ir a la pestaña de envíos</h5>
<p>En <code>lab_formularios.php</code>, haz clic en la pestaña <span class="man-ui-tag">📋 Envíos</span>. Verás el historial de todos los formularios enviados con su estado actual.</p>
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:8px">
<span class="state-pill sp-prog">⏳ pendiente</span>
<span class="state-pill sp-comp">✅ completado</span>
<span class="state-pill sp-conf">✍️ firmado</span>
<span class="state-pill sp-canc">❌ expirado</span>
</div>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Ver la respuesta del paciente</h5>
<p>En formularios con estado <strong>completado</strong> o <strong>firmado</strong>, aparecerá el botón <span class="man-ui-tag">👁 Ver</span>. Este abrirá un modal con todas las respuestas campo por campo y la imagen de la firma.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">3</div>
<div class="man-step-body">
<h5>Descargar el PDF</h5>
<p>Haz clic en el ícono <span class="man-ui-tag">🖨️</span> junto al botón Ver, o en el botón <span class="man-ui-tag">Ver / Imprimir PDF</span> dentro del modal. Esto abre <code>ver_formulario_enviado.php</code> con el documento completo listo para imprimir o guardar como PDF.</p>
</div>
</div>
<!-- Firmar como profesional -->
<h3 id="man-form-firmar">Firmar como profesional</h3>
<div class="callout warning" style="margin-bottom:16px">
⚠️ Esta opción solo aplica cuando la plantilla tiene un campo <strong>firma_profesional</strong> en su diseño.
</div>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Abrir el formulario completado</h5>
<p>Ve a <code>ver_formulario_enviado.php?id=X</code> con tu sesión activa (admin o el enfermero que lo envió). Verás el documento con el canvas de firma del profesional en la posición configurada.</p>
<div class="man-tip">✅ Si el paciente aún no ha completado el formulario, el canvas de firma profesional no aparecerá hasta que lo complete.</div>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Dibujar la firma y guardar</h5>
<p>Dibuja tu firma en el canvas usando el mouse o la pantalla táctil. Haz clic en <span class="man-ui-tag">💾 Guardar firma</span>. La firma quedará vinculada al documento de forma permanente.</p>
</div>
</div>
<!-- Verificar autenticidad -->
<h3 id="man-form-verificar">Verificar la autenticidad de un documento</h3>
<div class="man-step-block">
<div class="man-step-num">1</div>
<div class="man-step-body">
<h5>Obtener el hash SHA-256 del documento</h5>
<p>Al pie del documento en <code>ver_formulario_enviado.php</code> encontrarás el sello de integridad — una cadena hexadecimal de 64 caracteres. Este hash identifica únicamente este documento.</p>
</div>
</div>
<div class="man-step-block">
<div class="man-step-num">2</div>
<div class="man-step-body">
<h5>Ir al verificador público</h5>
<p>Accede a <code>verificar_formulario.php?h=HASH_AQUI</code> o comparte ese enlace con la persona que necesita verificar la autenticidad. El sistema mostrará:</p>
<ul style="padding-left:18px;font-size:13.5px;line-height:2">
<li>Nombre del formulario y del paciente</li>
<li>Fecha en que fue completado</li>
<li>Estado actual del documento</li>
<li>Confirmación de que el sello es válido ✅ o inválido ❌</li>
</ul>
<div class="man-tip">✅ Esta verificación es pública — cualquier persona con el hash puede usarla sin necesidad de tener cuenta en el sistema.</div>
</div>
</div>
</div>
</div>
<p style="text-align:center;color:var(--muted);font-size:12px;margin-top:8px;padding-bottom:32px">
Manual de uso — Sistema de Laboratorio · 30/03/2026
</p>
</main><!-- /content -->
</div><!-- /layout -->
<button id="scrollTop" onclick="window.scrollTo({top:0,behavior:'smooth'})" title="Volver arriba"></button>
<script>
// Scroll top button
const btn = document.getElementById('scrollTop');
window.addEventListener('scroll', () => {
btn.classList.toggle('show', window.scrollY > 400);
});
// Active TOC link highlighting
const sections = document.querySelectorAll('[id]');
const links = document.querySelectorAll('.toc a[href^="#"]');
const observer = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
links.forEach(l => l.classList.remove('active'));
const active = document.querySelector(`.toc a[href="#${e.target.id}"]`);
if (active) active.classList.add('active');
}
});
}, { rootMargin: '-20% 0px -70% 0px' });
sections.forEach(s => observer.observe(s));
</script>
</body>
</html>