Files
whatsapp/lab_domicilios.php
T

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 => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[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>
&nbsp;·&nbsp;
${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>