1454 lines
76 KiB
PHP
1454 lines
76 KiB
PHP
<?php
|
|
/**
|
|
* Gestión de Domicilios — Módulo Administrativo de Laboratorio
|
|
*/
|
|
require_once 'config/config.php';
|
|
|
|
if (!isUserLoggedIn()) {
|
|
header('Location: login.php');
|
|
exit;
|
|
}
|
|
if (isEnfermero()) { header('Location: enfermero_portal.php'); exit; }
|
|
$adminNombre = $_SESSION['admin_user']['full_name'] ?? $_SESSION['admin_user']['username'] ?? 'Admin';
|
|
$puedeEscribir = hasModuleWrite('lab_domicilios');
|
|
$ordenIdParam = (int)($_GET['orden_id'] ?? 0);
|
|
$pacIdParam = (int)($_GET['paciente_id']?? 0);
|
|
$domIdParam = (int)($_GET['id'] ?? 0);
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Domicilios — Módulo Lab</title>
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link href="./assets/css/styles.css?v=12" rel="stylesheet">
|
|
<style>
|
|
.dom-row { cursor:pointer; }
|
|
.estado-badge { font-size:.72rem; }
|
|
.agenda-card { border-left: 4px solid #dee2e6; padding: 8px 12px; margin-bottom: 8px; border-radius: 0 8px 8px 0; }
|
|
.agenda-card.programado { border-left-color: #6c757d; }
|
|
.agenda-card.en_camino { border-left-color: #0d6efd; }
|
|
.agenda-card.completado { border-left-color: #198754; }
|
|
.agenda-card.cancelado { border-left-color: #dc3545; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<nav class="sidebar">
|
|
<div class="sidebar-header"><h4><i class="fas fa-flask"></i> Módulo Lab</h4></div>
|
|
<ul class="sidebar-menu">
|
|
<?php if (hasModule('lab_dashboard')): ?>
|
|
<li><a href="lab_dashboard.php" class="nav-link"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
|
|
<?php endif; ?>
|
|
<?php if (hasModule('lab_ordenes')): ?>
|
|
<li><a href="lab_ordenes.php" class="nav-link"><i class="fas fa-file-medical"></i> Órdenes</a></li>
|
|
<?php endif; ?>
|
|
<?php if (hasModule('lab_pacientes')): ?>
|
|
<li><a href="lab_pacientes.php" class="nav-link"><i class="fas fa-users"></i> Pacientes</a></li>
|
|
<?php endif; ?>
|
|
<li><a href="lab_domicilios.php" class="nav-link active"><i class="fas fa-house-medical"></i> Domicilios</a></li>
|
|
<?php if (hasModule('lab_enfermeras')): ?>
|
|
<li><a href="lab_enfermeras.php" class="nav-link"><i class="fas fa-user-nurse"></i> Enfermeros</a></li>
|
|
<?php endif; ?>
|
|
<?php if (hasModule('lab_formularios')): ?>
|
|
<li><a href="lab_formularios.php" class="nav-link"><i class="fas fa-wpforms"></i> Formularios</a></li>
|
|
<?php endif; ?>
|
|
<?php if (hasModule('lab_reportes')): ?>
|
|
<li><a href="lab_reportes.php" class="nav-link"><i class="fas fa-chart-bar"></i> Reportes</a></li>
|
|
<?php endif; ?>
|
|
<?php if (hasModule('lab_configuracion')): ?>
|
|
<li><a href="lab_configuracion.php" class="nav-link"><i class="fas fa-sliders-h"></i> Configuración</a></li>
|
|
<?php endif; ?>
|
|
<?php if (hasModule('lab_usuarios')): ?>
|
|
<li><a href="lab_usuarios.php" class="nav-link"><i class="fas fa-users-cog"></i> Usuarios</a></li>
|
|
<?php endif; ?>
|
|
<li class="sidebar-divider"></li>
|
|
<?php if (hasModule('lab_dashboard')): ?>
|
|
<li><a href="index.php" class="nav-link"><i class="fas fa-arrow-left"></i> Volver al Bot</a></li>
|
|
<?php endif; ?>
|
|
<li><a href="logout.php" class="nav-link logout-link" onclick="return confirm('¿Cerrar sesión?')"><i class="fas fa-sign-out-alt"></i> Cerrar Sesión</a></li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<main class="main-content">
|
|
<header class="content-header d-flex align-items-center justify-content-between">
|
|
<div>
|
|
<h1 id="header-titulo"><i class="fas fa-house-medical text-primary"></i> Domicilios</h1>
|
|
<small class="text-muted"><?= htmlspecialchars($adminNombre) ?></small>
|
|
</div>
|
|
<?php if ($puedeEscribir): ?>
|
|
<button class="btn btn-primary btn-sm" id="btn-nuevo-domicilio" onclick="abrirFormulario()">
|
|
<i class="fas fa-plus me-1"></i> Nuevo Domicilio
|
|
</button>
|
|
<?php endif; ?>
|
|
</header>
|
|
|
|
<!-- Tabs -->
|
|
<div class="container-fluid pt-2 pb-0">
|
|
<ul class="nav nav-tabs border-bottom-0">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" id="tab-btn-domicilios" href="#" onclick="switchTab('domicilios',this);return false;">
|
|
<i class="fas fa-house-medical me-1"></i>Domicilios
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" id="tab-btn-formularios" href="#" onclick="switchTab('formularios',this);return false;">
|
|
<i class="fas fa-wpforms me-1"></i>Formularios recibidos
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="container-fluid py-3" id="seccion-domicilios">
|
|
<!-- Filtros -->
|
|
<div class="row g-2 mb-3">
|
|
<div class="col-md-2">
|
|
<input type="date" id="filtro-fecha" class="form-control form-control-sm"
|
|
value="<?= date('Y-m-d') ?>" onchange="cargarLista()">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select id="filtro-estado" class="form-select form-select-sm" onchange="cargarLista()">
|
|
<option value="">Todos los estados</option>
|
|
<option>programado</option><option>confirmado</option><option>en_camino</option>
|
|
<option>en_domicilio</option><option>completado</option><option>cancelado</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select id="filtro-enfermera" class="form-select form-select-sm" onchange="cargarLista()">
|
|
<option value="">Todos los enfermeros</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button class="btn btn-sm btn-secondary w-100" onclick="verHoy()">
|
|
<i class="fas fa-calendar-day me-1"></i> Hoy
|
|
</button>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button class="btn btn-sm btn-success w-100" onclick="exportarDomicilios()">
|
|
<i class="fas fa-file-excel me-1"></i> Excel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resumen del día -->
|
|
<div class="row g-2 mb-3" id="resumen-hoy"></div>
|
|
|
|
<div class="row g-3">
|
|
<!-- Tabla -->
|
|
<div class="col-lg-5">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr><th>Fecha</th><th>Hora</th><th>Paciente</th><th>Enfermero</th><th>Estado</th></tr>
|
|
</thead>
|
|
<tbody id="tabla-body">
|
|
<tr><td colspan="5" class="text-center py-4 text-muted"><i class="fas fa-spinner fa-spin"></i></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="d-flex align-items-center justify-content-between px-3 py-2 border-top" id="paginacion"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Detalle -->
|
|
<div class="col-lg-7">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-white border-0 d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0 fw-semibold">Detalle <span id="detail-id" class="text-muted"></span></h6>
|
|
<div class="d-flex gap-2">
|
|
<button class="btn btn-sm btn-outline-success d-none" id="btn-informe-dom"
|
|
onclick="verInformeDom()">
|
|
<i class="fas fa-file-medical me-1"></i>Informe
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-primary d-none" id="btn-editar-dom"
|
|
onclick="editarDomicilio(domSeleccionado)">
|
|
<i class="fas fa-edit me-1"></i>Editar
|
|
</button>
|
|
<button class="btn-close" onclick="cerrarDetalle()"></button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body" id="detail-body">
|
|
<div class="text-center py-5 text-muted">
|
|
<i class="fas fa-arrow-left me-2"></i>Selecciona un domicilio
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════ SECCIÓN FORMULARIOS RECIBIDOS ═══════════ -->
|
|
<div class="container-fluid py-3" id="seccion-formularios" style="display:none">
|
|
<!-- Filtros -->
|
|
<div class="row g-2 mb-3">
|
|
<div class="col-md-3">
|
|
<select id="ff-plantilla" class="form-select form-select-sm" onchange="cargarFormulariosEnviados()">
|
|
<option value="">Todos los formularios</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<select id="ff-estado" class="form-select form-select-sm" onchange="cargarFormulariosEnviados()">
|
|
<option value="">Todos los estados</option>
|
|
<option value="pendiente">⏳ Pendiente</option>
|
|
<option value="completado">✅ Completado</option>
|
|
<option value="firmado">✍️ Firmado</option>
|
|
<option value="expirado">❌ Expirado</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<input type="date" id="ff-fecha-desde" class="form-control form-control-sm"
|
|
placeholder="Desde" onchange="cargarFormulariosEnviados()">
|
|
</div>
|
|
<div class="col-md-1">
|
|
<button class="btn btn-sm btn-secondary w-100" onclick="document.getElementById('ff-fecha-desde').value='';cargarFormulariosEnviados()">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Fecha</th>
|
|
<th>Paciente</th>
|
|
<th>Formulario</th>
|
|
<th>Enviado por</th>
|
|
<th>Estado</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="ff-tbody">
|
|
<tr><td colspan="6" class="text-center py-4 text-muted"><i class="fas fa-spinner fa-spin"></i></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="px-3 py-2 border-top text-muted small" id="ff-total"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Modal Formulario (Nuevo / Editar) -->
|
|
<div class="modal fade" id="modalDomicilio" tabindex="-1">
|
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="fas fa-house-medical me-2"></i><span id="modal-titulo">Nuevo Domicilio</span></h5>
|
|
<button class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="form-domicilio">
|
|
<input type="hidden" name="id" id="dom-id">
|
|
<input type="hidden" name="orden_id_url" id="dom-orden-id" value="<?= $ordenIdParam ?>">
|
|
|
|
<!-- ── Paciente ── -->
|
|
<h6 class="text-muted text-uppercase small fw-semibold border-bottom pb-1 mb-3">
|
|
<i class="fas fa-user me-1"></i> Paciente y servicio
|
|
</h6>
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-8">
|
|
<label class="form-label">Paciente <span class="text-danger">*</span></label>
|
|
<input type="hidden" name="paciente_id" id="dom-paciente-id" value="<?= $pacIdParam ?>">
|
|
<input type="text" class="form-control" id="dom-paciente-nombre" placeholder="Buscar paciente…" autocomplete="off">
|
|
<div class="list-group mt-1 shadow-sm" id="pac-suggest" style="position:absolute;z-index:1000;max-height:200px;overflow-y:auto;display:none"></div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Tipo de servicio</label>
|
|
<input type="text" class="form-control" name="tipo_servicio" id="dom-tipo" placeholder="Ej: toma de muestras">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Tipo de cliente</label>
|
|
<select class="form-select" name="tipo_cliente" id="dom-tipo-cliente" onchange="toggleSeguro(this.value)">
|
|
<option value="particular">Particular</option>
|
|
<option value="seguro">Seguro / EPS</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4" id="bloque-seguro" style="display:none">
|
|
<label class="form-label">Nombre del seguro / EPS</label>
|
|
<input type="text" class="form-control" name="seguro_nombre" id="dom-seguro-nombre" placeholder="Ej: Sura, Nueva EPS…">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">N° de autorización</label>
|
|
<input type="text" class="form-control" name="autorizacion" id="dom-autorizacion" placeholder="Número de autorización">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Dirección ── -->
|
|
<h6 class="text-muted text-uppercase small fw-semibold border-bottom pb-1 mb-3">
|
|
<i class="fas fa-map-marker-alt me-1"></i> Dirección
|
|
</h6>
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-12">
|
|
<label class="form-label">Dirección completa <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" name="direccion" id="dom-dir" required>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Ciudad</label>
|
|
<input type="text" class="form-control" name="ciudad" id="dom-ciudad">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Barrio</label>
|
|
<input type="text" class="form-control" name="barrio" id="dom-barrio">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Indicaciones adicionales</label>
|
|
<input type="text" class="form-control" name="indicaciones_dir" id="dom-indic" placeholder="Ej: apto 302, tocar campanilla">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Fecha / hora ── -->
|
|
<h6 class="text-muted text-uppercase small fw-semibold border-bottom pb-1 mb-3">
|
|
<i class="fas fa-calendar-alt me-1"></i> Programación
|
|
</h6>
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-4">
|
|
<label class="form-label">Fecha programada <span class="text-danger">*</span></label>
|
|
<input type="date" class="form-control" name="fecha_programada" id="dom-fecha" required>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Hora</label>
|
|
<input type="time" class="form-control" name="hora_programada" id="dom-hora">
|
|
</div>
|
|
</div>
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-4">
|
|
<label class="form-label"><i class="fas fa-sign-in-alt text-success me-1"></i>Hora llegada</label>
|
|
<input type="time" class="form-control" name="hora_llegada" id="dom-hora-llegada">
|
|
<div class="form-text">Registrada automáticamente al iniciar el domicilio</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label"><i class="fas fa-sign-out-alt text-danger me-1"></i>Hora salida</label>
|
|
<input type="time" class="form-control" name="hora_salida" id="dom-hora-salida">
|
|
<div class="form-text">Registrada automáticamente al completar</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Cobros ── -->
|
|
<h6 class="text-muted text-uppercase small fw-semibold border-bottom pb-1 mb-3">
|
|
<i class="fas fa-cash-register me-1"></i> Cobros
|
|
</h6>
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-4">
|
|
<label class="form-label">Valor domicilio</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text">$</span>
|
|
<input type="number" class="form-control" name="valor_domicilio" id="dom-valor-dom"
|
|
min="0" step="1" placeholder="0">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Copago cliente</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text">$</span>
|
|
<input type="number" class="form-control" name="valor_copago" id="dom-valor-cop"
|
|
min="0" step="1" placeholder="0">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Copago laboratorio</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text">$</span>
|
|
<input type="number" class="form-control" name="copago_laboratorio" id="dom-copago-lab"
|
|
min="0" step="1" placeholder="0">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Orden Médica ── -->
|
|
<h6 class="text-muted text-uppercase small fw-semibold border-bottom pb-1 mb-3">
|
|
<i class="fas fa-file-medical me-1"></i> Orden Médica Adjunta
|
|
</h6>
|
|
<div class="mb-3">
|
|
<input type="hidden" name="orden_id" id="dom-orden-id-form">
|
|
<!-- Preview archivo actual o pendiente -->
|
|
<div id="dom-orden-preview" class="d-flex align-items-center gap-3 p-2 rounded border bg-light mb-2 d-none">
|
|
<div id="dom-orden-thumb-wrap">
|
|
<img id="dom-orden-img" src="" alt="Orden" style="max-height:80px;max-width:100px;object-fit:contain;border-radius:4px;display:none">
|
|
<div id="dom-orden-icon" class="d-none">
|
|
<i class="fas fa-file-alt fa-3x text-secondary"></i>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1 overflow-hidden">
|
|
<div id="dom-orden-fname" class="small fw-semibold text-truncate"></div>
|
|
<div id="dom-orden-fsize" class="small text-muted"></div>
|
|
</div>
|
|
</div>
|
|
<div id="dom-orden-empty" class="small text-muted mb-2">
|
|
<i class="fas fa-paperclip me-1"></i> Sin orden adjunta
|
|
</div>
|
|
<div class="d-flex gap-2 flex-wrap align-items-center">
|
|
<label class="btn btn-sm btn-outline-primary mb-0" for="dom-orden-input">
|
|
<i class="fas fa-upload me-1"></i>
|
|
<span id="dom-orden-btn-lbl">Adjuntar orden</span>
|
|
</label>
|
|
<button type="button" class="btn btn-sm btn-outline-danger d-none" id="dom-orden-quitar"
|
|
onclick="domQuitarOrden()">
|
|
<i class="fas fa-times me-1"></i> Quitar
|
|
</button>
|
|
</div>
|
|
<input type="file" id="dom-orden-input" class="d-none"
|
|
accept="image/jpeg,image/png,image/gif,image/webp,application/pdf,.pdf,.doc,.docx"
|
|
onchange="domSeleccionarOrdenFile(this)">
|
|
<small class="text-muted d-block mt-1">Imagen, PDF o DOC — máx. 10 MB</small>
|
|
</div>
|
|
|
|
<!-- ── Notas ── -->
|
|
<div class="mb-2">
|
|
<label class="form-label">Notas internas</label>
|
|
<textarea class="form-control" name="notas_admin" id="dom-notas" rows="2"
|
|
placeholder="Observaciones para el equipo…"></textarea>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="guardarDomicilio()"><i class="fas fa-save me-1"></i> Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Ver Respuesta de Formulario -->
|
|
<div class="modal fade" id="modalRespuestaForm" tabindex="-1">
|
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="fas fa-wpforms me-2"></i>Formulario diligenciado</h5>
|
|
<button class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body" id="resp-form-body">
|
|
<div class="text-center py-3"><i class="fas fa-spinner fa-spin"></i></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<a href="#" id="btn-resp-pdf" target="_blank" class="btn btn-outline-secondary btn-sm">
|
|
<i class="fas fa-print me-1"></i>Ver / Imprimir PDF
|
|
</a>
|
|
<button class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cerrar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Modal Informe Domicilio -->
|
|
<div class="modal fade" id="modalInformeDom" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="fas fa-file-medical me-2 text-success"></i>Informe de domicilio</h5>
|
|
<div class="d-flex gap-2">
|
|
<button class="btn btn-sm btn-outline-secondary" onclick="window.print()">
|
|
<i class="fas fa-print me-1"></i>Imprimir
|
|
</button>
|
|
<button class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
</div>
|
|
<div class="modal-body" id="informe-body">
|
|
<!-- Datos del paciente -->
|
|
<h6 class="text-muted text-uppercase mb-2"><i class="fas fa-user me-1"></i>Datos del paciente</h6>
|
|
<dl class="row small mb-3">
|
|
<dt class="col-4">Nombre</dt> <dd class="col-8 fw-semibold" id="inf-nombre"></dd>
|
|
<dt class="col-4">Tipo doc.</dt> <dd class="col-8" id="inf-tipodoc"></dd>
|
|
<dt class="col-4">Nº documento</dt> <dd class="col-8" id="inf-numdoc"></dd>
|
|
<dt class="col-4">Teléfono</dt> <dd class="col-8" id="inf-telefono"></dd>
|
|
<dt class="col-4">Correo</dt> <dd class="col-8" id="inf-correo"></dd>
|
|
<dt class="col-4">Dirección</dt> <dd class="col-8" id="inf-direccion"></dd>
|
|
<dt class="col-4">Seguro/tipo</dt> <dd class="col-8" id="inf-seguro"></dd>
|
|
<dt class="col-4">Fecha servicio</dt><dd class="col-8" id="inf-fecha"></dd>
|
|
<dt class="col-4">Exámenes</dt> <dd class="col-8" id="inf-examenes"></dd>
|
|
</dl>
|
|
<hr>
|
|
<!-- Ficha clínica -->
|
|
<h6 class="text-muted text-uppercase mb-2"><i class="fas fa-clipboard-list me-1"></i>Ficha clínica</h6>
|
|
<dl class="row small mb-3">
|
|
<dt class="col-4">Antecedentes</dt> <dd class="col-8" id="inf-antecedentes"></dd>
|
|
<dt class="col-4">Medicamentos</dt> <dd class="col-8" id="inf-medicamentos"></dd>
|
|
<dt class="col-4 d-none" id="inf-fila-acudiente">Acudiente</dt>
|
|
<dd class="col-8 d-none" id="inf-acudiente"></dd>
|
|
</dl>
|
|
<hr>
|
|
<!-- Notas libres -->
|
|
<h6 class="text-muted text-uppercase mb-2"><i class="fas fa-sticky-note me-1"></i>Notas del enfermero</h6>
|
|
<div id="inf-notas-libres"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal Asignar / Reasignar Enfermero -->
|
|
<div class="modal fade" id="modalAsignarEnf" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"><i class="fas fa-user-nurse me-2"></i>Asignar Enfermero</h5>
|
|
<button class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="hidden" id="asig-dom-id">
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">Selecciona el enfermero</label>
|
|
<select class="form-select" id="asig-sel-enfermero">
|
|
<option value="">— Cargando... —</option>
|
|
</select>
|
|
</div>
|
|
<div id="asig-info-enf" class="alert alert-info py-2 small d-none"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
<button class="btn btn-primary" onclick="confirmarAsignacion()">
|
|
<i class="fas fa-check me-1"></i>Confirmar asignación
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
const modal = new bootstrap.Modal('#modalDomicilio');
|
|
let _modalRespForm = null;
|
|
let _modalAsignarEnf = null;
|
|
|
|
// ── Estado orden en el modal ───────────────────────────────────────────────
|
|
let _domOrderFile = null; // File pendiente de subir
|
|
let _domCurrentLocalFile = null; // local_file actual de la orden
|
|
let _domRemoveOrder = false; // el usuario quiere desvincularse de la orden
|
|
|
|
function _domResetOrden() {
|
|
_domOrderFile = null;
|
|
_domCurrentLocalFile = null;
|
|
_domRemoveOrder = false;
|
|
document.getElementById('dom-orden-id-form').value = '';
|
|
document.getElementById('dom-orden-input').value = '';
|
|
_domRenderOrdenPreview();
|
|
}
|
|
|
|
function _domRenderOrdenPreview() {
|
|
const preview = document.getElementById('dom-orden-preview');
|
|
const empty = document.getElementById('dom-orden-empty');
|
|
const img = document.getElementById('dom-orden-img');
|
|
const icon = document.getElementById('dom-orden-icon');
|
|
const fname = document.getElementById('dom-orden-fname');
|
|
const fsize = document.getElementById('dom-orden-fsize');
|
|
const btnQuitar = document.getElementById('dom-orden-quitar');
|
|
const btnLbl = document.getElementById('dom-orden-btn-lbl');
|
|
|
|
if (_domOrderFile) {
|
|
// Archivo local seleccionado aún no subido
|
|
const isImg = _domOrderFile.type.startsWith('image/');
|
|
if (isImg) {
|
|
img.src = URL.createObjectURL(_domOrderFile);
|
|
img.style.display = '';
|
|
icon.classList.add('d-none');
|
|
} else {
|
|
img.style.display = 'none';
|
|
icon.classList.remove('d-none');
|
|
}
|
|
fname.textContent = _domOrderFile.name;
|
|
fsize.textContent = (_domOrderFile.size / 1024).toFixed(1) + ' KB — pendiente de guardar';
|
|
preview.classList.remove('d-none');
|
|
empty.classList.add('d-none');
|
|
btnQuitar.classList.remove('d-none');
|
|
btnLbl.textContent = 'Cambiar archivo';
|
|
} else if (_domCurrentLocalFile && !_domRemoveOrder) {
|
|
// Archivo ya guardado en servidor
|
|
const isImg = /\.(jpe?g|png|gif|webp)$/i.test(_domCurrentLocalFile);
|
|
if (isImg) {
|
|
img.src = 'uploads/media/' + encodeURIComponent(_domCurrentLocalFile);
|
|
img.style.display = '';
|
|
icon.classList.add('d-none');
|
|
} else {
|
|
img.style.display = 'none';
|
|
icon.classList.remove('d-none');
|
|
}
|
|
fname.textContent = _domCurrentLocalFile;
|
|
fsize.textContent = 'Archivo guardado';
|
|
preview.classList.remove('d-none');
|
|
empty.classList.add('d-none');
|
|
btnQuitar.classList.remove('d-none');
|
|
btnLbl.textContent = 'Cambiar archivo';
|
|
} else {
|
|
preview.classList.add('d-none');
|
|
empty.classList.remove('d-none');
|
|
btnQuitar.classList.add('d-none');
|
|
btnLbl.textContent = 'Adjuntar orden';
|
|
}
|
|
}
|
|
|
|
function domSeleccionarOrdenFile(input) {
|
|
const file = input.files[0];
|
|
if (!file) return;
|
|
if (file.size > 10 * 1024 * 1024) {
|
|
alert('El archivo supera los 10 MB permitidos');
|
|
input.value = '';
|
|
return;
|
|
}
|
|
_domOrderFile = file;
|
|
_domRemoveOrder = false;
|
|
_domRenderOrdenPreview();
|
|
}
|
|
|
|
function domQuitarOrden() {
|
|
_domOrderFile = null;
|
|
_domRemoveOrder = true;
|
|
document.getElementById('dom-orden-id-form').value = '';
|
|
document.getElementById('dom-orden-input').value = '';
|
|
_domRenderOrdenPreview();
|
|
}
|
|
const COLOR_DOM = { programado:'secondary', confirmado:'info', en_camino:'primary', en_domicilio:'warning', completado:'success', cancelado:'danger', reprogramado:'dark' };
|
|
const PUEDE_ESCRIBIR = <?= $puedeEscribir ? 'true' : 'false' ?>;
|
|
let domSeleccionado = <?= $domIdParam ?: 'null' ?>;
|
|
let _domActual = null; // objeto completo del domicilio activo en el panel de detalle
|
|
let paginaActual = 1;
|
|
let _listaToken = 0; // anti race-condition
|
|
|
|
// ── Init ───────────────────────────────────────────────────────────────────
|
|
(async function init() {
|
|
await cargarEnfermeras();
|
|
await cargarLista();
|
|
if (domSeleccionado) verDomicilio(domSeleccionado);
|
|
if (<?= $ordenIdParam ?> && <?= $pacIdParam ?>) abrirFormulario();
|
|
})();
|
|
|
|
async function cargarEnfermeras() {
|
|
const r = await fetch('api/lab/get_enfermeras.php');
|
|
const d = await r.json();
|
|
const sel = document.getElementById('filtro-enfermera');
|
|
(d.data||[]).forEach(e => {
|
|
sel.insertAdjacentHTML('beforeend', `<option value="${e.id}">${esc(e.nombre_completo)}</option>`);
|
|
});
|
|
}
|
|
|
|
// ── Lista ──────────────────────────────────────────────────────────────────
|
|
async function cargarLista(pag = 1) {
|
|
paginaActual = pag;
|
|
const miToken = ++_listaToken; // capturar token antes del await
|
|
const params = new URLSearchParams({
|
|
fecha: document.getElementById('filtro-fecha').value || '',
|
|
estado: document.getElementById('filtro-estado').value,
|
|
enfermera_id: document.getElementById('filtro-enfermera').value,
|
|
page: pag, limit: 25,
|
|
});
|
|
|
|
const r = await fetch(`api/lab/get_domicilios.php?${params}`);
|
|
const d = await r.json();
|
|
|
|
if (miToken !== _listaToken) return; // respuesta desactualizada, ignorar
|
|
|
|
renderResumen(d.estadisticas_hoy, d.domicilios?.sin_asignar ?? d.sin_asignar_hoy);
|
|
|
|
const tbody = document.getElementById('tabla-body');
|
|
if (!d.data?.length) {
|
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-4 text-muted">Sin domicilios</td></tr>';
|
|
document.getElementById('paginacion').innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = d.data.map(dom => `
|
|
<tr class="dom-row ${domSeleccionado===dom.id?'table-active':''}" id="row-${dom.id}" onclick="verDomicilio(${dom.id})">
|
|
<td class="text-nowrap small">${dom.fecha_programada ? dom.fecha_programada.slice(5).replace('-','/') : '—'}</td>
|
|
<td>${dom.hora_programada ? dom.hora_programada.slice(0,5) : '—'}</td>
|
|
<td>
|
|
<div class="fw-semibold">${esc(dom.paciente_nombre)}</div>
|
|
<small class="text-muted">${esc(dom.barrio||'')}</small>
|
|
</td>
|
|
<td>${dom.enfermera_nombre ? `<small>${esc(dom.enfermera_nombre)}</small>` : '<span class="text-danger small"><i class="fas fa-exclamation-triangle me-1"></i>Sin asignar</span>'}</td>
|
|
<td>
|
|
<span class="badge bg-${COLOR_DOM[dom.estado]||'secondary'} estado-badge">${esc(dom.estado)}</span>
|
|
${(dom.pago_estado === 'pagado')
|
|
? '<span class="badge rounded-pill ms-1" style="background:#d1fae5;color:#065f46;border:1px solid #34d399">✅ Pagado</span>'
|
|
: (dom.pago_estado === 'exento'
|
|
? '<span class="badge rounded-pill ms-1" style="background:#e0e7ff;color:#3730a3;border:1px solid #818cf8">🔵 Exento</span>'
|
|
: ((parseFloat(dom.valor_domicilio)||0)+(parseFloat(dom.valor_copago)||0) > 0
|
|
? '<span class="badge rounded-pill ms-1" style="background:#fff3cd;color:#664d03;border:1px solid #ffc107">⏳ Cobro</span>'
|
|
: ''))}
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
const el = document.getElementById('paginacion');
|
|
if (d.paginas > 1) {
|
|
el.innerHTML = `
|
|
<small class="text-muted">${d.total} domicilio(s)</small>
|
|
<div class="btn-group btn-group-sm">
|
|
${pag>1?`<button class="btn btn-outline-secondary" onclick="cargarLista(${pag-1})"><i class="fas fa-chevron-left"></i></button>`:''}
|
|
<button class="btn btn-secondary disabled">${pag}/${d.paginas}</button>
|
|
${pag<d.paginas?`<button class="btn btn-outline-secondary" onclick="cargarLista(${pag+1})"><i class="fas fa-chevron-right"></i></button>`:''}
|
|
</div>`;
|
|
} else {
|
|
el.innerHTML = `<small class="text-muted">${d.total} domicilio(s)</small>`;
|
|
}
|
|
}
|
|
|
|
function renderResumen(stats, sinAsignar) {
|
|
if (!stats) return;
|
|
const items = [
|
|
{ label:'Programados', val: stats.programado||0, color:'secondary' },
|
|
{ label:'En camino', val: stats.en_camino||0, color:'primary' },
|
|
{ label:'Completados', val: stats.completado||0, color:'success' },
|
|
{ label:'Sin enfermero',val: sinAsignar||0, color:'danger' },
|
|
];
|
|
document.getElementById('resumen-hoy').innerHTML = items.map(i =>
|
|
`<div class="col-6 col-md-3">
|
|
<div class="card border-0 shadow-sm text-center py-2">
|
|
<div class="fs-4 fw-bold text-${i.color}">${i.val}</div>
|
|
<div class="text-muted small">${i.label}</div>
|
|
</div>
|
|
</div>`
|
|
).join('');
|
|
}
|
|
|
|
// ── Detalle ────────────────────────────────────────────────────────────────
|
|
async function verDomicilio(id) {
|
|
domSeleccionado = id;
|
|
document.querySelectorAll('.dom-row').forEach(r => r.classList.remove('table-active'));
|
|
const rowEl = document.getElementById(`row-${id}`);
|
|
if (rowEl) rowEl.classList.add('table-active');
|
|
|
|
document.getElementById('detail-id').textContent = `#${id}`;
|
|
document.getElementById('detail-body').innerHTML = '<div class="text-center py-3"><i class="fas fa-spinner fa-spin"></i></div>';
|
|
if (PUEDE_ESCRIBIR) document.getElementById('btn-editar-dom').classList.remove('d-none');
|
|
document.getElementById('btn-informe-dom').classList.remove('d-none');
|
|
|
|
const r = await fetch(`api/lab/get_domicilios.php?id=${id}&no_stats=1`);
|
|
const d = await r.json();
|
|
const dom = d.domicilio;
|
|
if (!dom) return;
|
|
_domActual = dom;
|
|
|
|
// Botones de estado
|
|
const SIGUIENTES = {
|
|
programado: [['confirmado','Confirmar','info'],['cancelado','Cancelar','danger']],
|
|
confirmado: [['en_camino','En camino','primary'],['cancelado','Cancelar','danger']],
|
|
en_camino: [['en_domicilio','En domicilio','warning']],
|
|
en_domicilio:[['completado','Completar','success']],
|
|
};
|
|
const btns = (SIGUIENTES[dom.estado]||[]).map(([e,lbl,col]) =>
|
|
`<button class="btn btn-sm btn-${col}" onclick="cambiarEstado(${dom.id},'${e}')">
|
|
<i class="fas fa-arrow-right me-1"></i>${lbl}
|
|
</button>`
|
|
).join('');
|
|
|
|
document.getElementById('detail-body').innerHTML = `
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<dl class="row small">
|
|
<dt class="col-5 text-muted">Paciente</dt><dd class="col-7 fw-semibold">${esc(dom.paciente_nombre)}</dd>
|
|
<dt class="col-5 text-muted">Teléfono</dt><dd class="col-7">${esc(dom.paciente_telefono||'—')}</dd>
|
|
<dt class="col-5 text-muted">Dirección</dt><dd class="col-7">${esc(dom.direccion)}</dd>
|
|
<dt class="col-5 text-muted">Barrio</dt><dd class="col-7">${esc(dom.barrio||'—')}</dd>
|
|
<dt class="col-5 text-muted">Indicaciones</dt><dd class="col-7">${esc(dom.indicaciones_dir||'—')}</dd>
|
|
<dt class="col-5 text-muted">Fecha</dt><dd class="col-7">${esc(dom.fecha_programada)} ${esc(dom.hora_programada||'')}</dd>
|
|
${(dom.hora_llegada || dom.hora_salida) ? `
|
|
<dt class="col-5 text-muted">Hora llegada</dt><dd class="col-7 text-success fw-semibold">${dom.hora_llegada ? dom.hora_llegada.slice(0,5) : '—'}</dd>
|
|
<dt class="col-5 text-muted">Hora salida</dt><dd class="col-7 text-danger fw-semibold">${dom.hora_salida ? dom.hora_salida.slice(0,5) : '—'}</dd>
|
|
${dom.hora_llegada && dom.hora_salida ? `<dt class="col-5 text-muted">Duración</dt><dd class="col-7 fw-semibold text-info">${calcDuracion(dom.hora_llegada, dom.hora_salida)}</dd>` : ''}` : ''}
|
|
<dt class="col-5 text-muted">Servicio</dt><dd class="col-7">${esc(dom.tipo_servicio||'—')}</dd>
|
|
<dt class="col-5 text-muted">Tipo cliente</dt><dd class="col-7">
|
|
${dom.tipo_cliente === 'seguro'
|
|
? `<span class="badge bg-info text-dark"><i class="fas fa-shield-alt me-1"></i>Seguro${dom.seguro_nombre ? ' — '+esc(dom.seguro_nombre) : ''}</span>`
|
|
: `<span class="badge bg-secondary"><i class="fas fa-wallet me-1"></i>Particular</span>`}
|
|
</dd>
|
|
${dom.autorizacion ? `<dt class="col-5 text-muted">Autorización</dt><dd class="col-7">${esc(dom.autorizacion)}</dd>` : ''}
|
|
${dom.copago_laboratorio ? `<dt class="col-5 text-muted">Copago lab.</dt><dd class="col-7">$${Number(dom.copago_laboratorio).toLocaleString('es-CO')}</dd>` : ''}
|
|
</dl>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label small text-muted text-uppercase">Estado actual</label><br>
|
|
<span class="badge bg-${COLOR_DOM[dom.estado]||'secondary'} fs-6">${esc(dom.estado)}</span>
|
|
</div>
|
|
<!-- Enfermero asignado -->
|
|
<div class="mb-3">
|
|
<label class="form-label small text-muted text-uppercase">Enfermero</label>
|
|
${dom.enfermera_nombre
|
|
? `<div class="d-flex align-items-center gap-2 flex-wrap">
|
|
<span><i class="fas fa-user-nurse text-success me-1"></i>${esc(dom.enfermera_nombre)}</span>
|
|
<button class="btn btn-xs btn-outline-secondary py-0 px-2" onclick="mostrarAsignacion(${dom.id})">Reasignar</button>
|
|
${dom.enfermera_lab_id ? `<a href="enfermero_portal.php?eid=${dom.enfermera_lab_id}" target="_blank" class="btn btn-xs btn-outline-info py-0 px-2"><i class="fas fa-eye me-1"></i>Ver portal</a>` : ''}
|
|
</div>`
|
|
: `<div class="alert alert-warning py-2 small d-flex align-items-center justify-content-between mb-0">
|
|
<span><i class="fas fa-exclamation-triangle me-2"></i>Sin enfermero asignado</span>
|
|
<button class="btn btn-sm btn-warning" onclick="mostrarAsignacion(${dom.id})"><i class="fas fa-user-plus me-1"></i>Asignar</button>
|
|
</div>`}
|
|
</div>
|
|
<!-- Orden médica -->
|
|
${renderOrdenesDetail(dom)}
|
|
<!-- Botones de flujo -->
|
|
<div class="d-flex gap-2 flex-wrap">${btns}</div>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-3">
|
|
<div class="col-12">
|
|
<label class="form-label small text-muted text-uppercase">Exámenes de la orden</label>
|
|
<div class="bg-light p-2 rounded small">${esc(dom.examenes_solicitados||'—')}</div>
|
|
</div>
|
|
</div>
|
|
${(dom.valor_domicilio || dom.valor_copago) ? (() => {
|
|
const totalC = (parseFloat(dom.valor_domicilio)||0)+(parseFloat(dom.valor_copago)||0);
|
|
const PAGO_INFO = {
|
|
pendiente: { bg:'#fff3cd', color:'#664d03', border:'#ffc107', icon:'⏳', txt:'Pago pendiente' },
|
|
pagado: { bg:'#d1fae5', color:'#065f46', border:'#34d399', icon:'✅', txt:'Pagado' },
|
|
exento: { bg:'#e0e7ff', color:'#3730a3', border:'#818cf8', icon:'🔵', txt:'Exento' },
|
|
};
|
|
const pEst = dom.pago_estado || 'pendiente';
|
|
const pInfo = PAGO_INFO[pEst] || PAGO_INFO.pendiente;
|
|
const MODO_LABEL = { efectivo:'💵 Efectivo', transferencia:'📲 Transferencia', otro:'📋 Otro' };
|
|
return `
|
|
<div class="row mt-3">
|
|
<div class="col-12">
|
|
<div class="p-2 rounded" style="background:#d1fae5;border:1px solid #6ee7b7">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="small fw-semibold text-success"><i class="fas fa-cash-register me-1"></i>Cobro al cliente</span>
|
|
<span class="badge rounded-pill small" style="background:${pInfo.bg};color:${pInfo.color};border:1px solid ${pInfo.border}">${pInfo.icon} ${pInfo.txt}</span>
|
|
</div>
|
|
<div class="d-flex gap-3 flex-wrap">
|
|
${dom.valor_domicilio ? `<span class="small">Domicilio: <strong>$${Number(dom.valor_domicilio).toLocaleString('es-CO')}</strong></span>` : ''}
|
|
${dom.valor_copago ? `<span class="small">Copago: <strong>$${Number(dom.valor_copago).toLocaleString('es-CO')}</strong></span>` : ''}
|
|
${totalC > 0 ? `<span class="small fw-bold text-success">Total: $${totalC.toLocaleString('es-CO')}</span>` : ''}
|
|
</div>
|
|
${dom.pago_estado === 'pagado' ? `
|
|
<div class="small mt-1 text-success">
|
|
${MODO_LABEL[dom.pago_modo] || (dom.pago_modo||'')}
|
|
${dom.pago_monto ? ` · $${Number(dom.pago_monto).toLocaleString('es-CO')} cobrado` : ''}
|
|
${dom.pago_notas ? ` · <em>${esc(dom.pago_notas)}</em>` : ''}
|
|
</div>` : ''}
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
})() : ''}
|
|
${dom.notas_admin ? `
|
|
<div class="row mt-2">
|
|
<div class="col-12">
|
|
<label class="form-label small text-muted text-uppercase">Notas internas</label>
|
|
<div class="bg-light p-2 rounded small">${esc(dom.notas_admin)}</div>
|
|
</div>
|
|
</div>` : ''}
|
|
`;
|
|
cargarNotasDetalle(id);
|
|
}
|
|
|
|
function cerrarDetalle() {
|
|
domSeleccionado = null;
|
|
_domActual = null;
|
|
document.querySelectorAll('.dom-row').forEach(r => r.classList.remove('table-active'));
|
|
document.getElementById('detail-id').textContent = '';
|
|
document.getElementById('btn-editar-dom').classList.add('d-none');
|
|
document.getElementById('btn-informe-dom').classList.add('d-none');
|
|
document.getElementById('detail-body').innerHTML = '<div class="text-center py-5 text-muted"><i class="fas fa-arrow-left me-2"></i>Selecciona un domicilio</div>';
|
|
}
|
|
|
|
// ── Notas del enfermero (read-only en detalle) ────────────────────────────
|
|
async function cargarNotasDetalle(domId) {
|
|
try {
|
|
const r = await fetch(`api/lab/get_notas_domicilio.php?domicilio_id=${domId}`);
|
|
const d = await r.json();
|
|
if (!d.success) return;
|
|
const notas = d.data || [];
|
|
const c = notas.find(n => n.tipo === 'clinica') || {};
|
|
const libres = notas.filter(n => n.tipo === 'libre');
|
|
const hayFicha = c.antecedentes || c.medicamentos || c.acudiente_nombre;
|
|
const hayLibres = libres.length > 0;
|
|
if (!hayFicha && !hayLibres) return;
|
|
|
|
let html = `<div class="row mt-3" id="notas-enfermero-bloque">
|
|
<div class="col-12">
|
|
<hr class="my-2">
|
|
<label class="form-label small text-muted text-uppercase">
|
|
<i class="fas fa-notes-medical me-1"></i>Notas del enfermero
|
|
</label>`;
|
|
|
|
if (hayFicha) {
|
|
html += `<div class="p-2 rounded mb-2" style="background:#fff7ed;border:1px solid #fdba74">
|
|
<div class="fw-semibold small mb-1" style="color:#9a3412">
|
|
<i class="fas fa-clipboard-list me-1"></i>Ficha clínica
|
|
</div>`;
|
|
if (c.antecedentes) html += `<div class="small mb-1"><strong>Antecedentes:</strong> ${esc(c.antecedentes)}</div>`;
|
|
if (c.medicamentos) html += `<div class="small mb-1"><strong>Medicamentos:</strong> ${esc(c.medicamentos)}</div>`;
|
|
if (c.acudiente_nombre) html += `<div class="small mb-1"><strong>Acudiente:</strong> ${esc(c.acudiente_nombre)}${c.acudiente_documento ? ' · ' + esc(c.acudiente_documento) : ''}</div>`;
|
|
html += `</div>`;
|
|
}
|
|
|
|
libres.forEach(n => {
|
|
html += `<div class="p-2 rounded mb-2" style="background:#f8fafc;border:1px solid #e2e8f0">
|
|
<div class="fw-semibold small">${esc(n.titulo||'Sin título')}</div>
|
|
<div class="small mt-1" style="white-space:pre-line">${n.cuerpo||''}</div>
|
|
${n.imagen_path ? `<img src="uploads/media/${esc(n.imagen_path)}" class="img-fluid rounded mt-1" style="max-height:160px;cursor:zoom-in" onclick="window.open('uploads/media/${esc(n.imagen_path)}','_blank')">` : ''}
|
|
<div class="text-muted mt-1" style="font-size:.7rem">${esc(n.created_at||'')}</div>
|
|
</div>`;
|
|
});
|
|
|
|
html += `</div></div>`;
|
|
document.getElementById('detail-body').insertAdjacentHTML('beforeend', html);
|
|
} catch(e) { /* silencioso */ }
|
|
}
|
|
|
|
// ── Informe del domicilio ─────────────────────────────────────────────────
|
|
async function verInformeDom() {
|
|
if (!_domActual) return;
|
|
const dom = _domActual;
|
|
|
|
// Llenar datos básicos del paciente
|
|
document.getElementById('inf-nombre').textContent = dom.paciente_nombre || '—';
|
|
document.getElementById('inf-tipodoc').textContent = dom.paciente_tipo_documento || '—';
|
|
document.getElementById('inf-numdoc').textContent = dom.paciente_numero_documento || '—';
|
|
document.getElementById('inf-telefono').textContent= dom.paciente_telefono || '—';
|
|
document.getElementById('inf-correo').textContent = dom.paciente_email || '—';
|
|
document.getElementById('inf-direccion').textContent = (dom.direccion||'') + (dom.barrio ? ', ' + dom.barrio : '');
|
|
document.getElementById('inf-seguro').textContent = dom.tipo_cliente === 'seguro'
|
|
? ('Seguro' + (dom.seguro_nombre ? ' — ' + dom.seguro_nombre : ''))
|
|
: 'Particular';
|
|
document.getElementById('inf-examenes').textContent = dom.examenes_solicitados || '—';
|
|
document.getElementById('inf-fecha').textContent = (dom.fecha_programada||'') + ' ' + (dom.hora_programada||'');
|
|
|
|
// Limpiar campos clínicos mientras cargamos
|
|
['inf-antecedentes','inf-medicamentos','inf-acudiente','inf-notas-libres'].forEach(id => {
|
|
const el = document.getElementById(id);
|
|
if (el) el.innerHTML = '<em class="text-muted">Cargando...</em>';
|
|
});
|
|
|
|
const _modalInforme = bootstrap.Modal.getOrCreateInstance('#modalInformeDom');
|
|
_modalInforme.show();
|
|
|
|
try {
|
|
const r = await fetch(`api/lab/get_notas_domicilio.php?domicilio_id=${dom.id}`);
|
|
const d = await r.json();
|
|
const notas = d.success ? (d.data || []) : [];
|
|
const c = notas.find(n => n.tipo === 'clinica') || {};
|
|
const libres = notas.filter(n => n.tipo === 'libre');
|
|
|
|
document.getElementById('inf-antecedentes').textContent = c.antecedentes || '—';
|
|
document.getElementById('inf-medicamentos').textContent = c.medicamentos || '—';
|
|
|
|
if (c.acudiente_nombre) {
|
|
document.getElementById('inf-acudiente').innerHTML =
|
|
`<strong>${esc(c.acudiente_nombre)}</strong> · ${esc(c.acudiente_documento||'')}`;
|
|
document.getElementById('inf-fila-acudiente').classList.remove('d-none');
|
|
} else {
|
|
document.getElementById('inf-fila-acudiente').classList.add('d-none');
|
|
document.getElementById('inf-acudiente').textContent = '';
|
|
}
|
|
|
|
if (libres.length) {
|
|
document.getElementById('inf-notas-libres').innerHTML = libres.map(n =>
|
|
`<div class="mb-2 p-2 rounded" style="background:#f8fafc;border:1px solid #e2e8f0">
|
|
<div class="fw-semibold small">${esc(n.titulo||'Sin título')}</div>
|
|
<div class="small mt-1" style="white-space:pre-line">${n.cuerpo||''}</div>
|
|
${n.imagen_path ? `<img src="uploads/media/${esc(n.imagen_path)}" class="img-fluid rounded mt-1" style="max-height:120px;cursor:zoom-in" onclick="window.open('uploads/media/${esc(n.imagen_path)}','_blank')">` : ''}
|
|
</div>`
|
|
).join('');
|
|
} else {
|
|
document.getElementById('inf-notas-libres').textContent = '—';
|
|
}
|
|
} catch(e) {
|
|
document.getElementById('inf-antecedentes').textContent = '—';
|
|
document.getElementById('inf-medicamentos').textContent = '—';
|
|
document.getElementById('inf-notas-libres').textContent = '—';
|
|
}
|
|
}
|
|
// ──────────────────────────────────────────────────────────────────────────
|
|
|
|
// ── Asignación rápida ──────────────────────────────────────────────────────
|
|
async function mostrarAsignacion(domId) {
|
|
document.getElementById('asig-dom-id').value = domId;
|
|
const sel = document.getElementById('asig-sel-enfermero');
|
|
sel.innerHTML = '<option value="">— Cargando... —</option>';
|
|
document.getElementById('asig-info-enf').classList.add('d-none');
|
|
|
|
if (!_modalAsignarEnf) _modalAsignarEnf = new bootstrap.Modal('#modalAsignarEnf');
|
|
_modalAsignarEnf.show();
|
|
|
|
const re = await fetch('api/lab/get_enfermeras.php');
|
|
const de = await re.json();
|
|
sel.innerHTML = '<option value="">— Selecciona enfermero —</option>'
|
|
+ (de.data||[]).map(e =>
|
|
`<option value="${e.id}" data-user="${esc(e.username_acceso||'')}">${esc(e.nombre_completo)} (${e.domicilios_hoy||0} hoy)</option>`
|
|
).join('');
|
|
|
|
sel.addEventListener('change', function onSelChange() {
|
|
const opt = this.options[this.selectedIndex];
|
|
const user = opt?.dataset?.user;
|
|
const info = document.getElementById('asig-info-enf');
|
|
if (this.value && user) {
|
|
info.textContent = `Usuario de acceso al portal: ${user}`;
|
|
info.classList.remove('d-none');
|
|
} else {
|
|
info.classList.add('d-none');
|
|
}
|
|
sel.removeEventListener('change', onSelChange);
|
|
sel.addEventListener('change', function() {
|
|
const o = this.options[this.selectedIndex];
|
|
const u = o?.dataset?.user;
|
|
const inf = document.getElementById('asig-info-enf');
|
|
if (this.value && u) { inf.textContent = `Usuario de acceso al portal: ${u}`; inf.classList.remove('d-none'); }
|
|
else inf.classList.add('d-none');
|
|
});
|
|
});
|
|
}
|
|
|
|
async function confirmarAsignacion() {
|
|
const domId = parseInt(document.getElementById('asig-dom-id').value);
|
|
const enfId = parseInt(document.getElementById('asig-sel-enfermero').value);
|
|
if (!enfId) { mostrarToast('Selecciona un enfermero', 'warning'); return; }
|
|
|
|
const rr = await fetch('api/lab/save_asignacion.php', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({ domicilio_id: domId, enfermera_id: enfId }),
|
|
});
|
|
const dd = await rr.json();
|
|
mostrarToast(dd.success ? dd.message : (dd.error||'Error'), dd.success ? 'success' : 'danger');
|
|
if (dd.success) {
|
|
_modalAsignarEnf.hide();
|
|
cargarLista(paginaActual);
|
|
verDomicilio(domId);
|
|
}
|
|
}
|
|
|
|
// ── Cambio de estado ───────────────────────────────────────────────────────
|
|
async function cambiarEstado(id, estado) {
|
|
if (!confirm(`¿Cambiar estado a "${estado}"?`)) return;
|
|
const r = await fetch('api/lab/save_domicilio.php', {
|
|
method:'POST',
|
|
headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify({ id, solo_estado:true, nuevo_estado:estado }),
|
|
});
|
|
const d = await r.json();
|
|
mostrarToast(d.success ? d.message : (d.error||'Error'), d.success?'success':'danger');
|
|
if (d.success) { cargarLista(paginaActual); verDomicilio(id); }
|
|
}
|
|
|
|
// ── Formulario nuevo ───────────────────────────────────────────────────────
|
|
function abrirFormulario() {
|
|
document.getElementById('form-domicilio').reset();
|
|
document.getElementById('dom-id').value = '';
|
|
document.getElementById('dom-fecha').value = document.getElementById('filtro-fecha').value || '<?= date('Y-m-d') ?>';
|
|
document.getElementById('dom-tipo-cliente').value = 'particular';
|
|
toggleSeguro('particular');
|
|
_domResetOrden();
|
|
document.getElementById('modal-titulo').textContent = 'Nuevo Domicilio';
|
|
modal.show();
|
|
|
|
// Autocompletar paciente desde parámetros URL
|
|
<?php if ($pacIdParam): ?>
|
|
document.getElementById('dom-paciente-id').value = '<?= $pacIdParam ?>';
|
|
document.getElementById('dom-paciente-nombre').value = 'Paciente #<?= $pacIdParam ?>';
|
|
<?php endif; ?>
|
|
}
|
|
|
|
// ── Editar domicilio existente ─────────────────────────────────────────────
|
|
async function editarDomicilio(id) {
|
|
if (!id) return;
|
|
const r = await fetch(`api/lab/get_domicilios.php?id=${id}&no_stats=1`);
|
|
const d = await r.json();
|
|
const dom = d.domicilio;
|
|
if (!dom) { mostrarToast('No se pudo cargar el domicilio', 'danger'); return; }
|
|
|
|
document.getElementById('modal-titulo').textContent = `Editar Domicilio #${dom.id}`;
|
|
document.getElementById('dom-id').value = dom.id;
|
|
document.getElementById('dom-orden-id').value = dom.orden_id || '';
|
|
document.getElementById('dom-paciente-id').value = dom.paciente_id;
|
|
document.getElementById('dom-paciente-nombre').value = dom.paciente_nombre || '';
|
|
document.getElementById('dom-tipo').value = dom.tipo_servicio || '';
|
|
document.getElementById('dom-dir').value = dom.direccion || '';
|
|
document.getElementById('dom-ciudad').value = dom.ciudad || '';
|
|
document.getElementById('dom-barrio').value = dom.barrio || '';
|
|
document.getElementById('dom-indic').value = dom.indicaciones_dir || '';
|
|
document.getElementById('dom-fecha').value = dom.fecha_programada || '';
|
|
document.getElementById('dom-hora').value = (dom.hora_programada || '').slice(0, 5);
|
|
document.getElementById('dom-hora-llegada').value = (dom.hora_llegada || '').slice(0, 5);
|
|
document.getElementById('dom-hora-salida').value = (dom.hora_salida || '').slice(0, 5);
|
|
document.getElementById('dom-tipo-cliente').value = dom.tipo_cliente || 'particular';
|
|
toggleSeguro(dom.tipo_cliente || 'particular');
|
|
document.getElementById('dom-seguro-nombre').value = dom.seguro_nombre || '';
|
|
document.getElementById('dom-autorizacion').value = dom.autorizacion || '';
|
|
document.getElementById('dom-valor-dom').value = dom.valor_domicilio || '';
|
|
document.getElementById('dom-valor-cop').value = dom.valor_copago || '';
|
|
document.getElementById('dom-copago-lab').value = dom.copago_laboratorio || '';
|
|
document.getElementById('dom-notas').value = dom.notas_admin || '';
|
|
|
|
// Orden médica adjunta
|
|
_domResetOrden();
|
|
document.getElementById('dom-orden-id-form').value = dom.orden_id || '';
|
|
_domCurrentLocalFile = dom.orden_local_file || null;
|
|
_domRenderOrdenPreview();
|
|
|
|
modal.show();
|
|
}
|
|
|
|
// Mostrar/ocultar campo seguro según tipo_cliente
|
|
function toggleSeguro(val) {
|
|
document.getElementById('bloque-seguro').style.display = val === 'seguro' ? '' : 'none';
|
|
}
|
|
|
|
async function guardarDomicilio() {
|
|
const form = document.getElementById('form-domicilio');
|
|
if (!form.checkValidity()) { form.reportValidity(); return; }
|
|
const pacienteId = document.getElementById('dom-paciente-id').value;
|
|
if (!pacienteId) { alert('Debes seleccionar un paciente'); return; }
|
|
|
|
const datos = Object.fromEntries(new FormData(form).entries());
|
|
if (!datos.id) delete datos.id;
|
|
// Tomar orden_id del campo oculto real (no el parámetro URL)
|
|
datos.orden_id = document.getElementById('dom-orden-id-form').value || null;
|
|
delete datos.orden_id_url;
|
|
|
|
// ── Gestión del archivo de orden médica ──────────────────────────────
|
|
if (_domRemoveOrder) {
|
|
datos.orden_id = null;
|
|
} else if (_domOrderFile) {
|
|
// Subir el archivo primero
|
|
const fd = new FormData();
|
|
fd.append('file', _domOrderFile);
|
|
const upR = await fetch('api/lab/upload_orden.php', { method:'POST', body: fd });
|
|
const upD = await upR.json();
|
|
if (!upD.success) {
|
|
mostrarToast('Error subiendo el archivo: ' + (upD.error || 'desconocido'), 'danger');
|
|
return;
|
|
}
|
|
// Actualizar o crear la orden médica
|
|
const orId = document.getElementById('dom-orden-id-form').value;
|
|
const orPayload = orId
|
|
? { id: parseInt(orId), local_file: upD.local_file }
|
|
: { paciente_id: parseInt(pacienteId), local_file: upD.local_file };
|
|
const orR = await fetch('api/lab/save_orden.php', {
|
|
method:'POST', headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify(orPayload),
|
|
});
|
|
const orD = await orR.json();
|
|
if (!orD.success) {
|
|
mostrarToast('Error guardando la orden: ' + (orD.error || 'desconocido'), 'danger');
|
|
return;
|
|
}
|
|
datos.orden_id = orD.id ?? orD.data?.id ?? (parseInt(orId) || null);
|
|
}
|
|
// ── Guardar domicilio ────────────────────────────────────────────────
|
|
if (!datos.orden_id) delete datos.orden_id;
|
|
|
|
const r = await fetch('api/lab/save_domicilio.php', {
|
|
method:'POST', headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify(datos),
|
|
});
|
|
const d = await r.json();
|
|
if (d.success) {
|
|
modal.hide();
|
|
mostrarToast(d.message, 'success');
|
|
const editadoId = datos.id ? parseInt(datos.id) : (d.id || null);
|
|
_domResetOrden();
|
|
await cargarLista(paginaActual);
|
|
if (editadoId) verDomicilio(editadoId);
|
|
} else {
|
|
mostrarToast(d.error||'Error', 'danger');
|
|
}
|
|
}
|
|
|
|
// Autocompletado de paciente
|
|
let pacTimer;
|
|
document.getElementById('dom-paciente-nombre').addEventListener('input', function() {
|
|
clearTimeout(pacTimer);
|
|
const v = this.value.trim();
|
|
if (v.length < 2) { document.getElementById('pac-suggest').style.display='none'; return; }
|
|
pacTimer = setTimeout(async () => {
|
|
const r = await fetch(`api/lab/get_pacientes.php?busqueda=${encodeURIComponent(v)}&limit=8`);
|
|
const d = await r.json();
|
|
const list = document.getElementById('pac-suggest');
|
|
if (!d.data?.length) { list.style.display='none'; return; }
|
|
list.innerHTML = d.data.map(p =>
|
|
`<button type="button" class="list-group-item list-group-item-action small py-1"
|
|
onclick="selPaciente(${p.id},'${esc(p.nombre_completo)}')">${esc(p.nombre_completo)} — ${esc(p.numero_documento||p.telefono||'')}</button>`
|
|
).join('');
|
|
list.style.display = 'block';
|
|
}, 300);
|
|
});
|
|
|
|
function selPaciente(id, nombre) {
|
|
document.getElementById('dom-paciente-id').value = id;
|
|
document.getElementById('dom-paciente-nombre').value= nombre;
|
|
document.getElementById('pac-suggest').style.display = 'none';
|
|
}
|
|
|
|
function verHoy() {
|
|
document.getElementById('filtro-fecha').value = '<?= date('Y-m-d') ?>';
|
|
cargarLista();
|
|
}
|
|
|
|
function exportarDomicilios() {
|
|
const fecha = document.getElementById('filtro-fecha').value;
|
|
const estado = document.getElementById('filtro-estado').value;
|
|
const enfId = document.getElementById('filtro-enfermera').value;
|
|
let url = `lab_reportes.php?export=domicilios`;
|
|
if (fecha) url += `&fecha=${encodeURIComponent(fecha)}`;
|
|
if (estado) url += `&estado=${encodeURIComponent(estado)}`;
|
|
if (enfId) url += `&enfermera_id=${encodeURIComponent(enfId)}`;
|
|
window.open(url, '_blank');
|
|
}
|
|
|
|
// ── Utils ──────────────────────────────────────────────────────────────────
|
|
const esc = s => String(s||'').replace(/[<>&"]/g, c => ({'<':'<','>':'>','&':'&','"':'"'}[c]));
|
|
function calcDuracion(hl, hs) {
|
|
if (!hl || !hs) return '—';
|
|
const [h1, m1] = hl.slice(0,5).split(':').map(Number);
|
|
const [h2, m2] = hs.slice(0,5).split(':').map(Number);
|
|
const mins = (h2 * 60 + m2) - (h1 * 60 + m1);
|
|
if (mins <= 0) return '—';
|
|
return mins >= 60 ? `${Math.floor(mins/60)}h ${mins%60}min` : `${mins} min`;
|
|
}
|
|
|
|
/**
|
|
* Renderiza el bloque de órdenes médicas (multi-archivo) en el detalle de domicilio.
|
|
*/
|
|
/**
|
|
* Resuelve la URL directa para mostrar un archivo de orden médica.
|
|
* Equivalente al _resolveImg de lab_ordenes.php — NO usa encodeURIComponent
|
|
* para no romper las / del subdirectorio (ej. 2026/02/archivo.jpg).
|
|
*/
|
|
function _resolveOrdenUrl(f) {
|
|
if (!f) return '';
|
|
if (/^https?:\/\//i.test(f)) return f;
|
|
if (f.startsWith('uploads/')) return f;
|
|
return `uploads/media/${f}`;
|
|
}
|
|
|
|
function renderOrdenesDetail(dom) {
|
|
const archivos = dom.archivos_ordenes || [];
|
|
if (!archivos.length && !dom.orden_id) return '';
|
|
// Sin archivos adjuntos pero sí orden_id — solo link a la ficha
|
|
if (!archivos.length) {
|
|
return `<div class="mb-3">
|
|
<label class="form-label small text-muted text-uppercase">Órdenes médicas</label>
|
|
<a href="lab_ordenes.php?id=${dom.orden_id}" class="d-block small">
|
|
<i class="fas fa-file-medical me-1"></i>Ver orden #${dom.orden_id}
|
|
</a>
|
|
</div>`;
|
|
}
|
|
const items = archivos.map(a => {
|
|
const lf = a.local_file || '';
|
|
const isImg = /\.(jpe?g|png|gif|webp)$/i.test(lf);
|
|
const imgUrl = _resolveOrdenUrl(lf); // URL directa para <img>
|
|
const descUrl = lf ? `api/lab/descargar_orden.php?file=${encodeURIComponent(lf)}` : ''; // descarga
|
|
const ficha = a.orden_id
|
|
? `<a href="lab_ordenes.php?id=${a.orden_id}" class="d-block small text-muted mt-1">
|
|
<i class="fas fa-external-link-alt me-1"></i>Ficha orden #${a.orden_id}
|
|
</a>`
|
|
: '';
|
|
if (lf && isImg) return `
|
|
<div class="mb-2">
|
|
<img src="${imgUrl}" alt="Orden médica"
|
|
style="max-width:100%;max-height:200px;object-fit:contain;border-radius:6px;border:1px solid #dee2e6;display:block;cursor:zoom-in"
|
|
onclick="verImagenDom('${imgUrl}')"
|
|
onerror="this.style.display='none'">
|
|
<div class="d-flex gap-2 mt-1">
|
|
<a href="${imgUrl}" target="_blank" class="btn btn-sm btn-outline-secondary flex-fill">
|
|
<i class="fas fa-expand me-1"></i>Ver completa
|
|
</a>
|
|
<a href="${descUrl}" target="_blank" class="btn btn-sm btn-outline-secondary flex-fill">
|
|
<i class="fas fa-download me-1"></i>Descargar
|
|
</a>
|
|
</div>
|
|
${ficha}
|
|
</div>`;
|
|
if (lf) return `
|
|
<div class="mb-2">
|
|
<a href="${descUrl}" target="_blank"
|
|
class="btn btn-sm btn-outline-secondary w-100 mb-1">
|
|
<i class="fas fa-file-download me-1"></i>Abrir / Descargar archivo
|
|
</a>
|
|
${ficha}
|
|
</div>`;
|
|
return `<div class="mb-2">${ficha}</div>`;
|
|
}).join('');
|
|
return `<div class="mb-3">
|
|
<label class="form-label small text-muted text-uppercase">Órdenes médicas${archivos.length > 1 ? ` (${archivos.length})` : ''}</label>
|
|
${items}
|
|
</div>`;
|
|
}
|
|
function mostrarToast(msg, tipo='success') {
|
|
const div = document.createElement('div');
|
|
div.className = `alert alert-${tipo} alert-dismissible position-fixed bottom-0 end-0 m-3`;
|
|
div.style.zIndex = 9999;
|
|
div.innerHTML = `${esc(msg)}<button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
|
|
document.body.appendChild(div);
|
|
setTimeout(() => div.remove(), 4000);
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
// TABS
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
function switchTab(tab, linkEl) {
|
|
// Actualizar estilos de tabs
|
|
document.querySelectorAll('.nav-tabs .nav-link').forEach(a => a.classList.remove('active'));
|
|
linkEl.classList.add('active');
|
|
|
|
const esFormularios = tab === 'formularios';
|
|
document.getElementById('seccion-domicilios').style.display = esFormularios ? 'none' : '';
|
|
document.getElementById('seccion-formularios').style.display = esFormularios ? '' : 'none';
|
|
document.getElementById('btn-nuevo-domicilio').style.display = esFormularios ? 'none' : '';
|
|
|
|
if (esFormularios) {
|
|
cargarPlantillasFF();
|
|
cargarFormulariosEnviados();
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
// FORMULARIOS RECIBIDOS
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
let _ffData = [];
|
|
let _ffPlantillasCargadas = false;
|
|
|
|
async function cargarPlantillasFF() {
|
|
if (_ffPlantillasCargadas) return;
|
|
_ffPlantillasCargadas = true;
|
|
const r = await fetch('api/lab/get_formularios.php');
|
|
const d = await r.json();
|
|
const sel = document.getElementById('ff-plantilla');
|
|
(d.data || []).forEach(f => {
|
|
sel.insertAdjacentHTML('beforeend', `<option value="${f.id}">${esc(f.nombre)}</option>`);
|
|
});
|
|
}
|
|
|
|
async function cargarFormulariosEnviados() {
|
|
const tbody = document.getElementById('ff-tbody');
|
|
tbody.innerHTML = '<tr><td colspan="6" class="text-center py-3 text-muted"><i class="fas fa-spinner fa-spin"></i></td></tr>';
|
|
|
|
const params = new URLSearchParams({ envios: 1 });
|
|
const estado = document.getElementById('ff-estado').value;
|
|
const plantilla = document.getElementById('ff-plantilla').value;
|
|
const fechaDesde = document.getElementById('ff-fecha-desde').value;
|
|
if (estado) params.set('estado', estado);
|
|
if (plantilla) params.set('formulario_id', plantilla);
|
|
if (fechaDesde) params.set('fecha_desde', fechaDesde);
|
|
|
|
const r = await fetch(`api/lab/get_formularios.php?${params}`);
|
|
const d = await r.json();
|
|
_ffData = d.data || [];
|
|
|
|
document.getElementById('ff-total').textContent = `${_ffData.length} registro(s)`;
|
|
|
|
if (!_ffData.length) {
|
|
tbody.innerHTML = '<tr><td colspan="6" class="text-center py-4 text-muted">Sin registros</td></tr>';
|
|
return;
|
|
}
|
|
|
|
const ESTADO_BADGE = {
|
|
pendiente: 'badge bg-warning text-dark',
|
|
completado: 'badge bg-success',
|
|
firmado: 'badge bg-primary',
|
|
expirado: 'badge bg-secondary',
|
|
};
|
|
const ESTADO_ICON = { pendiente:'⏳', completado:'✅', firmado:'✍️', expirado:'❌' };
|
|
|
|
tbody.innerHTML = _ffData.map(e => {
|
|
const fecha = (e.created_at || '').slice(0, 16).replace('T', ' ');
|
|
const estadoBadge = ESTADO_BADGE[e.estado] || 'badge bg-secondary';
|
|
const estadoIcon = ESTADO_ICON[e.estado] || '—';
|
|
const puedeVer = e.estado === 'completado' || e.estado === 'firmado';
|
|
return `<tr>
|
|
<td class="small text-nowrap">${esc(fecha)}</td>
|
|
<td class="small">${esc(e.paciente_nombre || '—')}</td>
|
|
<td class="small">${esc(e.form_nombre)}</td>
|
|
<td class="small">${esc(e.enviado_por_nombre || '—')}</td>
|
|
<td><span class="${estadoBadge}">${estadoIcon} ${esc(e.estado)}</span></td>
|
|
<td>
|
|
${puedeVer
|
|
? `<button class="btn btn-xs btn-outline-primary py-0 px-2 me-1" onclick="verRespuestaFF(${e.id})">
|
|
<i class="fas fa-eye me-1"></i>Ver
|
|
</button>
|
|
<a href="ver_formulario_enviado.php?id=${e.id}" target="_blank"
|
|
class="btn btn-xs btn-outline-secondary py-0 px-2">
|
|
<i class="fas fa-print"></i>
|
|
</a>`
|
|
: '—'}
|
|
</td>
|
|
</tr>`;
|
|
}).join('');
|
|
}
|
|
|
|
function verRespuestaFF(envioId) {
|
|
const e = _ffData.find(x => x.id == envioId);
|
|
if (!e) return;
|
|
|
|
const cliente = JSON.parse(e.datos_cliente || '{}');
|
|
const prefill = JSON.parse(e.datos_prefilled || '{}');
|
|
const todos = { ...prefill, ...cliente };
|
|
const esquema = JSON.parse(e.esquema || '[]');
|
|
|
|
const labels = {};
|
|
esquema.forEach(c => { if (c.id) labels[c.id] = c.label || c.id; });
|
|
|
|
let html = `<h6 class="fw-bold mb-1">${esc(e.form_nombre)}</h6>
|
|
<p class="small text-muted mb-3">
|
|
Paciente: <strong>${esc(e.paciente_nombre || '—')}</strong>
|
|
·
|
|
${e.completado_en ? 'Completado: ' + esc(e.completado_en.slice(0,16).replace('T',' ')) : 'Pendiente'}
|
|
</p><hr class="my-2">`;
|
|
|
|
const ordenados = esquema.filter(c =>
|
|
c.tipo !== 'separador' && c.tipo !== 'firma' && todos[c.id] !== undefined
|
|
);
|
|
|
|
if (ordenados.length) {
|
|
ordenados.forEach(c => {
|
|
const v = todos[c.id];
|
|
const display = Array.isArray(v) ? v.join(', ') : String(v ?? '—');
|
|
html += `<div class="row mb-2">
|
|
<div class="col-5 text-muted small">${esc(c.label || c.id)}</div>
|
|
<div class="col-7 small fw-semibold">${esc(display)}</div>
|
|
</div>`;
|
|
});
|
|
} else {
|
|
Object.entries(todos).forEach(([k, v]) => {
|
|
if (k.startsWith('__')) return;
|
|
const display = Array.isArray(v) ? v.join(', ') : String(v ?? '—');
|
|
html += `<div class="row mb-2">
|
|
<div class="col-5 text-muted small">${esc(labels[k] || k)}</div>
|
|
<div class="col-7 small fw-semibold">${esc(display)}</div>
|
|
</div>`;
|
|
});
|
|
}
|
|
|
|
if (e.firma_svg) {
|
|
html += `<hr class="my-3"><p class="small fw-semibold text-muted">Firma digital:</p>
|
|
<img src="${e.firma_svg}" class="border rounded p-2"
|
|
style="max-width:260px;max-height:160px;display:block">`;
|
|
}
|
|
|
|
document.getElementById('resp-form-body').innerHTML = html;
|
|
document.getElementById('btn-resp-pdf').href = `ver_formulario_enviado.php?id=${envioId}`;
|
|
if (!_modalRespForm) _modalRespForm = new bootstrap.Modal('#modalRespuestaForm');
|
|
_modalRespForm.show();
|
|
}
|
|
</script>
|
|
<script src="assets/js/lab-sidebar.js"></script>
|
|
|
|
<!-- Modal visor de imagen -->
|
|
<div class="modal fade" id="modalImagenDom" tabindex="-1">
|
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
<div class="modal-content bg-dark">
|
|
<div class="modal-header border-0 py-2">
|
|
<button class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body text-center p-0">
|
|
<img id="img-visor-dom" src="" class="img-fluid" style="max-height:80vh;object-fit:contain">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
const _modalImgDom = new bootstrap.Modal('#modalImagenDom');
|
|
function verImagenDom(src) {
|
|
document.getElementById('img-visor-dom').src = src;
|
|
_modalImgDom.show();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|