Files
whatsapp/lab_domicilios.php
T
2026-03-10 18:42:51 -05:00

517 lines
27 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';
$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">
<li><a href="lab_dashboard.php" class="nav-link"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
<li><a href="lab_ordenes.php" class="nav-link"><i class="fas fa-file-medical"></i> Órdenes</a></li>
<li><a href="lab_pacientes.php" class="nav-link"><i class="fas fa-users"></i> Pacientes</a></li>
<li><a href="lab_domicilios.php" class="nav-link active"><i class="fas fa-house-medical"></i> Domicilios</a></li>
<li><a href="lab_enfermeras.php" class="nav-link"><i class="fas fa-user-nurse"></i> Enfermeras</a></li>
<li><a href="lab_formularios.php" class="nav-link"><i class="fas fa-wpforms"></i> Formularios</a></li>
<li><a href="lab_reportes.php" class="nav-link"><i class="fas fa-chart-bar"></i> Reportes</a></li>
<li><a href="lab_configuracion.php" class="nav-link"><i class="fas fa-sliders-h"></i> Configuración</a></li>
<li><a href="lab_usuarios.php" class="nav-link"><i class="fas fa-users-cog"></i> Usuarios</a></li>
<li class="sidebar-divider"></li>
<li><a href="index.php" class="nav-link"><i class="fas fa-arrow-left"></i> Volver al Bot</a></li>
<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><i class="fas fa-house-medical text-primary"></i> Domicilios</h1>
<small class="text-muted"><?= htmlspecialchars($adminNombre) ?></small>
</div>
<button class="btn btn-primary btn-sm" onclick="abrirFormulario()">
<i class="fas fa-plus me-1"></i> Nuevo Domicilio
</button>
</header>
<div class="container-fluid py-3">
<!-- 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="">Todas las enfermeras</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>
<!-- 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>Hora</th><th>Paciente</th><th>Enfermera</th><th>Estado</th></tr>
</thead>
<tbody id="tabla-body">
<tr><td colspan="4" 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>
<button class="btn-close" onclick="cerrarDetalle()"></button>
</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>
</main>
<!-- Modal Formulario -->
<div class="modal fade" id="modalDomicilio" tabindex="-1">
<div class="modal-dialog modal-lg">
<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" id="dom-orden-id" value="<?= $ordenIdParam ?>">
<div class="row g-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">
</div>
<div class="col-md-12">
<label class="form-label">Dirección <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 de la dirección</label>
<input type="text" class="form-control" name="indicaciones_dir" id="dom-indic" placeholder="Ej: apto 302, tocar campanilla">
</div>
<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 class="col-12">
<label class="form-label">Notas</label>
<textarea class="form-control" name="notas_admin" id="dom-notas" rows="2"></textarea>
</div>
</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>
<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');
const COLOR_DOM = { programado:'secondary', confirmado:'info', en_camino:'primary', en_domicilio:'warning', completado:'success', cancelado:'danger', reprogramado:'dark' };
let domSeleccionado = <?= $domIdParam ?: 'null' ?>;
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="4" 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>${dom.hora_programada||'—'}</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></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 enfermera',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>';
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;
// 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>
<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>
<!-- Enfermera asignada -->
<div class="mb-3">
<label class="form-label small text-muted text-uppercase">Enfermera</label>
${dom.enfermera_nombre
? `<div class="d-flex align-items-center gap-2">
<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>
</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 enfermera asignada</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 -->
${dom.orden_id ? `
<div class="mb-3">
<label class="form-label small text-muted text-uppercase">Orden médica</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>` : ''}
<!-- 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) ? `
<div class="row mt-3">
<div class="col-12">
<div class="d-flex gap-3 flex-wrap p-2 rounded" style="background:#d1fae5;border:1px solid #6ee7b7">
<span class="small fw-semibold text-success"><i class="fas fa-cash-register me-1"></i>Cobro al cliente:</span>
${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>` : ''}
${ (dom.valor_domicilio && dom.valor_copago) ? `<span class="small fw-bold text-success">Total: $${(Number(dom.valor_domicilio)+Number(dom.valor_copago)).toLocaleString('es-CO')}</span>` : ''}
</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>` : ''}
`;
}
function cerrarDetalle() {
domSeleccionado = null;
document.querySelectorAll('.dom-row').forEach(r => r.classList.remove('table-active'));
document.getElementById('detail-id').textContent = '';
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>';
}
// ── Asignación rápida ──────────────────────────────────────────────────────
async function mostrarAsignacion(domId) {
const re = await fetch('api/lab/get_enfermeras.php');
const de = await re.json();
const opts = (de.data||[]).map(e => `<option value="${e.id}">${esc(e.nombre_completo)} (${e.domicilios_hoy||0} hoy)</option>`).join('');
if (!confirm('¿Asignar enfermera?')) return;
const sel = prompt(`Selecciona el ID de la enfermera:\n${(de.data||[]).map(e=>`${e.id}: ${e.nombre_completo}`).join('\n')}`);
if (!sel) 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: parseInt(sel) }),
});
const dd = await rr.json();
mostrarToast(dd.success ? dd.message : (dd.error||'Error'), dd.success?'success':'danger');
if (dd.success) { 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') ?>';
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; ?>
}
async function guardarDomicilio() {
const form = document.getElementById('form-domicilio');
if (!form.checkValidity()) { form.reportValidity(); return; }
if (!document.getElementById('dom-paciente-id').value) {
alert('Debes seleccionar un paciente'); return;
}
const datos = Object.fromEntries(new FormData(form).entries());
if (!datos.id) delete datos.id;
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');
cargarLista(paginaActual);
} 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();
}
// ── Utils ──────────────────────────────────────────────────────────────────
const esc = s => String(s||'').replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]));
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);
}
</script>
</body>
</html>