up
This commit is contained in:
@@ -0,0 +1,706 @@
|
||||
<?php
|
||||
/**
|
||||
* Puesto de Recepción — Módulo Turnero
|
||||
* Requiere login + módulo turnero.
|
||||
*/
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
if (!isUserLoggedIn()) {
|
||||
header('Location: ' . BASE_URL . 'login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Cargar catálogos para la ficha
|
||||
try {
|
||||
$pdo = Database::getInstance()->getConnection();
|
||||
|
||||
$lugares = $pdo->query(
|
||||
"SELECT id, nombre FROM turnero_lugares WHERE activo = 1 ORDER BY sort_order ASC"
|
||||
)->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$examenes = $pdo->query(
|
||||
"SELECT id, codigo, nombre, categoria FROM exam_tipos WHERE activo = 1 ORDER BY categoria, nombre"
|
||||
)->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Agrupar exámenes por categoría
|
||||
$examenesAgrupados = [];
|
||||
foreach ($examenes as $ex) {
|
||||
$cat = $ex['categoria'] ?: 'General';
|
||||
$examenesAgrupados[$cat][] = $ex;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$lugares = [];
|
||||
$examenesAgrupados = [];
|
||||
}
|
||||
|
||||
$adminNombre = $_SESSION['admin_user']['full_name'] ?? $_SESSION['admin_user']['username'] ?? 'Operador';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Recepción — Turnero</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="<?= BASE_URL ?>assets/css/styles.css?v=12" rel="stylesheet">
|
||||
<style>
|
||||
/* ── Layout de dos columnas ── */
|
||||
.rec-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
height: calc(100vh - 56px);
|
||||
overflow: hidden;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.rec-layout { grid-template-columns: 1fr; }
|
||||
.rec-cola { max-height: 260px; }
|
||||
}
|
||||
|
||||
/* ── Columna cola ── */
|
||||
.rec-cola {
|
||||
background: #f8fafc;
|
||||
border-right: 1px solid #e2e8f0;
|
||||
display: flex; flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rec-cola-header {
|
||||
padding: 1rem 1.2rem .6rem;
|
||||
background: #fff; border-bottom: 1px solid #e2e8f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cola-items { flex: 1; overflow-y: auto; padding: .5rem; }
|
||||
.cola-card {
|
||||
background: #fff; border: 1px solid #e2e8f0;
|
||||
border-radius: 10px; padding: .65rem .9rem;
|
||||
margin-bottom: .4rem; cursor: default;
|
||||
display: flex; align-items: center; gap: .6rem;
|
||||
transition: box-shadow .15s;
|
||||
}
|
||||
.cola-card.activo { border-color: #3b82f6; box-shadow: 0 0 0 2px #bfdbfe; }
|
||||
.cola-card .prio-dot {
|
||||
width: 36px; height: 36px; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.1rem; font-weight: 800; color: #fff; flex-shrink: 0;
|
||||
}
|
||||
.cola-card .turno-info { flex: 1; min-width: 0; }
|
||||
.cola-card .turno-info .cod { font-size: 1rem; font-weight: 700; }
|
||||
.cola-card .turno-info .pac { font-size: .78rem; color: #64748b;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.cola-vacia { text-align: center; padding: 3rem 1rem; color: #94a3b8; }
|
||||
|
||||
/* ── Columna ficha ── */
|
||||
.rec-ficha {
|
||||
overflow-y: auto; padding: 1.5rem;
|
||||
background: #fff;
|
||||
}
|
||||
.ficha-placeholder {
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
height: 100%; color: #94a3b8; text-align: center;
|
||||
}
|
||||
|
||||
/* ── Secciones de la ficha ── */
|
||||
.ficha-section {
|
||||
background: #f8fafc; border: 1px solid #e2e8f0;
|
||||
border-radius: 12px; padding: 1.2rem; margin-bottom: 1rem;
|
||||
}
|
||||
.ficha-section h6 { color: #475569; font-size: .78rem;
|
||||
text-transform: uppercase; letter-spacing: .08em; margin-bottom: .8rem; }
|
||||
|
||||
/* ── Búsqueda de paciente ── */
|
||||
.pac-resultado {
|
||||
border: 1px solid #e2e8f0; border-radius: 8px; padding: .5rem .75rem;
|
||||
cursor: pointer; margin-top: .25rem; transition: background .12s;
|
||||
}
|
||||
.pac-resultado:hover { background: #f1f5f9; }
|
||||
.pac-resultado .pac-nombre { font-weight: 600; font-size: .9rem; }
|
||||
.pac-resultado .pac-doc { font-size: .78rem; color: #64748b; }
|
||||
.pac-seleccionado {
|
||||
background: #eff6ff; border: 1px solid #93c5fd;
|
||||
border-radius: 8px; padding: .65rem .9rem;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
}
|
||||
|
||||
/* ── Checkboxes de exámenes ── */
|
||||
.exam-group-title { font-size: .75rem; color: #94a3b8;
|
||||
text-transform: uppercase; letter-spacing: .08em; margin: .6rem 0 .3rem; }
|
||||
.exam-check-item { display: flex; align-items: center; gap: .5rem;
|
||||
padding: .3rem .4rem; border-radius: 6px; cursor: pointer;
|
||||
transition: background .1s; font-size: .88rem; }
|
||||
.exam-check-item:hover { background: #f1f5f9; }
|
||||
.exam-check-item input[type=checkbox] { cursor: pointer; }
|
||||
|
||||
/* ── Consentimientos ── */
|
||||
.consent-item {
|
||||
display: flex; align-items: center; gap: .6rem;
|
||||
padding: .4rem .6rem; border-radius: 8px;
|
||||
font-size: .85rem; margin-bottom: .3rem;
|
||||
}
|
||||
.consent-item.firmado { background: #f0fdf4; color: #166534; }
|
||||
.consent-item.pendiente{ background: #fffbeb; color: #92400e; }
|
||||
.consent-item.enviado { background: #eff6ff; color: #1e40af; }
|
||||
|
||||
/* ── Barra de acciones ── */
|
||||
.ficha-acciones {
|
||||
position: sticky; bottom: 0;
|
||||
background: #fff; border-top: 1px solid #e2e8f0;
|
||||
padding: 1rem 0 .25rem; margin-top: 1rem;
|
||||
display: flex; gap: .5rem; flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ── Badge de turno activo en topbar ── */
|
||||
.badge-turno-activo {
|
||||
background: #eff6ff; color: #1d4ed8;
|
||||
border: 1px solid #bfdbfe; border-radius: 8px;
|
||||
padding: .25rem .75rem; font-size: .82rem; font-weight: 700;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
$SIDEBAR_TITLE = 'Turnero';
|
||||
$SIDEBAR_ICON = 'fas fa-ticket-alt';
|
||||
require_once __DIR__ . '/../../../shared/components/sidebar.php';
|
||||
?>
|
||||
|
||||
<main class="main-content" style="padding:0">
|
||||
|
||||
<!-- ── Top bar ──────────────────────────────────────────── -->
|
||||
<div class="d-flex align-items-center justify-content-between px-3 py-2 border-bottom bg-white"
|
||||
style="position:sticky;top:0;z-index:100">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="fas fa-ticket-alt text-primary"></i>
|
||||
<strong>Recepción</strong>
|
||||
<span id="badge-turno-activo" class="badge-turno-activo d-none">
|
||||
Turno: <span id="badge-codigo">—</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="text-muted small"><?= htmlspecialchars($adminNombre) ?></span>
|
||||
<button class="btn btn-primary btn-sm" id="btn-llamar" onclick="llamarSiguiente()">
|
||||
<i class="fas fa-bell me-1"></i>Llamar siguiente
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rec-layout">
|
||||
|
||||
<!-- ══ Columna izquierda: cola ════════════════════════ -->
|
||||
<div class="rec-cola">
|
||||
<div class="rec-cola-header">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<span class="fw-semibold text-secondary small">
|
||||
<i class="fas fa-list-ol me-1"></i>EN ESPERA
|
||||
</span>
|
||||
<span class="badge bg-primary rounded-pill" id="badge-count">0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cola-items" id="cola-items">
|
||||
<div class="cola-vacia">
|
||||
<i class="fas fa-hourglass-start fa-2x mb-2 d-block"></i>
|
||||
Cola vacía
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ Columna derecha: ficha del turno ═══════════════ -->
|
||||
<div class="rec-ficha" id="rec-ficha">
|
||||
<div class="ficha-placeholder" id="ficha-placeholder">
|
||||
<i class="fas fa-ticket-alt fa-3x mb-3" style="color:#cbd5e1"></i>
|
||||
<p class="mb-0 fw-semibold">Sin turno activo</p>
|
||||
<small class="text-muted">Haga clic en "Llamar siguiente" o seleccione un turno de la cola</small>
|
||||
</div>
|
||||
|
||||
<!-- Ficha de turno (oculta hasta llamar) -->
|
||||
<div id="ficha-turno" class="d-none">
|
||||
|
||||
<!-- Encabezado del turno -->
|
||||
<div class="d-flex align-items-center gap-3 mb-3">
|
||||
<div id="ficha-prio-dot" class="prio-dot"
|
||||
style="width:52px;height:52px;border-radius:12px;display:flex;align-items:center;
|
||||
justify-content:center;font-size:1.6rem;font-weight:900;color:#fff;background:#3b82f6">
|
||||
—
|
||||
</div>
|
||||
<div>
|
||||
<div class="h4 mb-0 fw-bold" id="ficha-codigo">—</div>
|
||||
<div class="text-muted small" id="ficha-prio-nombre">—</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Sección 1: Paciente ── -->
|
||||
<div class="ficha-section">
|
||||
<h6><i class="fas fa-user me-1"></i>Paciente</h6>
|
||||
|
||||
<!-- Si el kiosko capturó un nombre -->
|
||||
<div id="bloque-nombre-kiosko" class="d-none mb-2">
|
||||
<small class="text-muted">Nombre del kiosko:</small>
|
||||
<div class="fw-semibold" id="lbl-nombre-kiosko"></div>
|
||||
</div>
|
||||
|
||||
<div id="bloque-pac-no-vinculado">
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" id="inp-buscar-pac"
|
||||
class="form-control form-control-sm"
|
||||
placeholder="Buscar por nombre o documento…"
|
||||
autocomplete="off">
|
||||
<button class="btn btn-outline-secondary btn-sm" onclick="buscarPaciente()" type="button">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="lista-pacientes-res"></div>
|
||||
</div>
|
||||
|
||||
<div id="bloque-pac-seleccionado" class="d-none">
|
||||
<div class="pac-seleccionado">
|
||||
<div>
|
||||
<div class="fw-semibold" id="lbl-pac-nombre">—</div>
|
||||
<div class="text-muted small" id="lbl-pac-doc">—</div>
|
||||
<div class="text-muted small" id="lbl-pac-cel"></div>
|
||||
</div>
|
||||
<button class="btn btn-link btn-sm text-secondary p-0" onclick="desvincularPaciente()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Sección 2: Lugar destino ── -->
|
||||
<div class="ficha-section">
|
||||
<h6><i class="fas fa-map-marker-alt me-1"></i>Lugar destino</h6>
|
||||
<select id="sel-lugar" class="form-select form-select-sm">
|
||||
<option value="">— Seleccione lugar —</option>
|
||||
<?php foreach ($lugares as $l): ?>
|
||||
<option value="<?= (int)$l['id'] ?>"><?= htmlspecialchars($l['nombre']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- ── Sección 3: Exámenes ── -->
|
||||
<div class="ficha-section">
|
||||
<h6><i class="fas fa-vial me-1"></i>Exámenes solicitados</h6>
|
||||
<div id="lista-examenes">
|
||||
<?php foreach ($examenesAgrupados as $cat => $items): ?>
|
||||
<div class="exam-group-title"><?= htmlspecialchars($cat) ?></div>
|
||||
<?php foreach ($items as $ex): ?>
|
||||
<label class="exam-check-item">
|
||||
<input type="checkbox" class="exam-chk"
|
||||
value="<?= (int)$ex['id'] ?>"
|
||||
data-nombre="<?= htmlspecialchars($ex['nombre']) ?>">
|
||||
<span><strong><?= htmlspecialchars($ex['codigo']) ?></strong>
|
||||
— <?= htmlspecialchars($ex['nombre']) ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="mt-2 d-flex gap-2 flex-wrap">
|
||||
<button class="btn btn-outline-secondary btn-sm" onclick="toggleTodosExamenes(false)">
|
||||
Limpiar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Sección 4: Pago ── -->
|
||||
<div class="ficha-section">
|
||||
<h6><i class="fas fa-dollar-sign me-1"></i>Cobro (opcional)</h6>
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
<input type="number" id="inp-total" class="form-control form-control-sm"
|
||||
placeholder="Total $" step="100" min="0">
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<select id="sel-pago" class="form-select form-select-sm">
|
||||
<option value="">— Forma de pago —</option>
|
||||
<option value="efectivo">Efectivo</option>
|
||||
<option value="transferencia">Transferencia</option>
|
||||
<option value="tarjeta">Tarjeta</option>
|
||||
<option value="eps">EPS / Convenio</option>
|
||||
<option value="cortesia">Cortesía</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="inp-obs" class="form-control form-control-sm mt-2"
|
||||
rows="2" placeholder="Observaciones…" maxlength="500"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- ── Sección 5: Consentimientos ── -->
|
||||
<div class="ficha-section" id="sec-consentimientos" style="display:none!important">
|
||||
<h6><i class="fas fa-file-signature me-1"></i>Consentimientos</h6>
|
||||
<div id="lista-consentimientos"></div>
|
||||
<button class="btn btn-outline-primary btn-sm mt-2"
|
||||
id="btn-reenviar-consent" onclick="enviarConsentimientos()">
|
||||
<i class="fas fa-paper-plane me-1"></i>Enviar / Reenviar por WhatsApp
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Acciones ── -->
|
||||
<div class="ficha-acciones">
|
||||
<button class="btn btn-success" id="btn-guardar" onclick="guardarSolicitud()">
|
||||
<i class="fas fa-save me-1"></i>Guardar solicitud
|
||||
</button>
|
||||
<button class="btn btn-primary d-none" id="btn-pasar-lugar" onclick="pasarALugar()">
|
||||
<i class="fas fa-arrow-right me-1"></i>Pasar a lugar
|
||||
</button>
|
||||
<button class="btn btn-warning d-none" id="btn-enviar-consent" onclick="enviarConsentimientos()">
|
||||
<i class="fas fa-paper-plane me-1"></i>Enviar consentimientos
|
||||
</button>
|
||||
<button class="btn btn-secondary ms-auto" onclick="marcarAusente()">
|
||||
<i class="fas fa-user-slash me-1"></i>Ausente
|
||||
</button>
|
||||
</div>
|
||||
</div><!-- /ficha-turno -->
|
||||
</div><!-- /rec-ficha -->
|
||||
|
||||
</div><!-- /rec-layout -->
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// ── Estado ────────────────────────────────────────────────────
|
||||
let turnoActivo = null;
|
||||
let pacienteActivo = null;
|
||||
let solicitudActiva = null;
|
||||
let consentimientos = [];
|
||||
let pollingColaId = null;
|
||||
|
||||
const API = '../api/';
|
||||
const API_PAC = '<?= BASE_URL ?>api/lab/get_pacientes.php';
|
||||
|
||||
// ── Arranque ──────────────────────────────────────────────────
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
cargarCola();
|
||||
pollingColaId = setInterval(cargarCola, 8000);
|
||||
document.getElementById('inp-buscar-pac')
|
||||
.addEventListener('keydown', e => { if (e.key === 'Enter') buscarPaciente(); });
|
||||
});
|
||||
|
||||
// ── Cola ──────────────────────────────────────────────────────
|
||||
async function cargarCola() {
|
||||
try {
|
||||
const res = await fetch(API + 'get_cola.php?area=recepcion');
|
||||
const json = await res.json();
|
||||
if (!json.ok) return;
|
||||
renderCola(json.data);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function renderCola(snap) {
|
||||
const lista = document.getElementById('cola-items');
|
||||
const espera = (snap.cola || []).filter(t => t.estado === 'espera');
|
||||
const badge = document.getElementById('badge-count');
|
||||
badge.textContent = espera.length;
|
||||
|
||||
if (espera.length === 0) {
|
||||
lista.innerHTML = `<div class="cola-vacia">
|
||||
<i class="fas fa-hourglass-start fa-2x mb-2 d-block"></i>Cola vacía</div>`;
|
||||
return;
|
||||
}
|
||||
lista.innerHTML = espera.map(t => `
|
||||
<div class="cola-card ${turnoActivo?.id === t.id ? 'activo' : ''}"
|
||||
onclick="seleccionarTurno(${t.id})">
|
||||
<div class="prio-dot" style="background:${t.prioridad_color}">${t.prioridad_codigo}</div>
|
||||
<div class="turno-info">
|
||||
<div class="cod">${escHtml(t.codigo)}</div>
|
||||
<div class="pac">${escHtml(t.paciente_nombre || 'Paciente')}</div>
|
||||
</div>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
// ── Llamar siguiente ──────────────────────────────────────────
|
||||
async function llamarSiguiente() {
|
||||
const btn = document.getElementById('btn-llamar');
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await fetch(API + 'llamar_turno.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ area: 'recepcion' }),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!json.ok) { mostrarError(json.error); return; }
|
||||
|
||||
if (!json.data.turno) {
|
||||
mostrarAviso('Cola vacía', 'No hay turnos en espera.');
|
||||
return;
|
||||
}
|
||||
abrirFicha(json.data.turno);
|
||||
cargarCola();
|
||||
} catch (err) {
|
||||
mostrarError(err.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Seleccionar turno de la cola (para ver ficha de uno ya llamado) ─
|
||||
async function seleccionarTurno(turnoId) {
|
||||
// No abrir si ya hay uno en recepción (confuso)
|
||||
if (turnoActivo && turnoActivo.estado === 'en_recepcion') return;
|
||||
}
|
||||
|
||||
// ── Abrir ficha ───────────────────────────────────────────────
|
||||
function abrirFicha(turno) {
|
||||
turnoActivo = turno;
|
||||
pacienteActivo = null;
|
||||
solicitudActiva = null;
|
||||
|
||||
document.getElementById('ficha-placeholder').classList.add('d-none');
|
||||
document.getElementById('ficha-turno').classList.remove('d-none');
|
||||
|
||||
// Datos del turno
|
||||
document.getElementById('ficha-codigo').textContent = turno.codigo;
|
||||
document.getElementById('ficha-prio-nombre').textContent = turno.prioridad_nombre || '';
|
||||
const dot = document.getElementById('ficha-prio-dot');
|
||||
dot.textContent = turno.prioridad_codigo || '—';
|
||||
dot.style.background = turno.prioridad_color || '#3b82f6';
|
||||
|
||||
// Badge top
|
||||
document.getElementById('badge-turno-activo').classList.remove('d-none');
|
||||
document.getElementById('badge-codigo').textContent = turno.codigo;
|
||||
|
||||
// Nombre del kiosko
|
||||
if (turno.paciente_nombre) {
|
||||
document.getElementById('bloque-nombre-kiosko').classList.remove('d-none');
|
||||
document.getElementById('lbl-nombre-kiosko').textContent = turno.paciente_nombre;
|
||||
document.getElementById('inp-buscar-pac').value = turno.paciente_nombre;
|
||||
}
|
||||
|
||||
// Reset secciones
|
||||
resetCheckboxes();
|
||||
document.getElementById('sec-consentimientos').style.removeProperty('display');
|
||||
document.getElementById('sec-consentimientos').style.display = 'none';
|
||||
document.getElementById('btn-pasar-lugar').classList.add('d-none');
|
||||
document.getElementById('btn-enviar-consent').classList.add('d-none');
|
||||
document.getElementById('btn-guardar').classList.remove('d-none');
|
||||
document.getElementById('btn-guardar').disabled = false;
|
||||
desvincularPaciente();
|
||||
|
||||
// Buscar consentimientos si ya tiene solicitud
|
||||
verificarSolicitudExistente(turno.id);
|
||||
}
|
||||
|
||||
async function verificarSolicitudExistente(turnoId) {
|
||||
try {
|
||||
const res = await fetch(API + 'get_cola.php?area=recepcion');
|
||||
// La solicitud se carga solo después de guardar
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// ── Buscador de paciente ──────────────────────────────────────
|
||||
async function buscarPaciente() {
|
||||
const q = document.getElementById('inp-buscar-pac').value.trim();
|
||||
if (q.length < 2) return;
|
||||
try {
|
||||
const res = await fetch(`${API_PAC}?busqueda=${encodeURIComponent(q)}&limit=8`);
|
||||
const json = await res.json();
|
||||
const lista = document.getElementById('lista-pacientes-res');
|
||||
const datos = json.data || json.registros || [];
|
||||
if (!datos.length) {
|
||||
lista.innerHTML = `<div class="text-muted small mt-1">Sin resultados.
|
||||
<a href="#" onclick="abrirNuevoPaciente('${escHtml(q)}');return false">Crear nuevo</a></div>`;
|
||||
return;
|
||||
}
|
||||
lista.innerHTML = datos.map(p => `
|
||||
<div class="pac-resultado" onclick="seleccionarPaciente(${JSON.stringify(p).replace(/"/g,'"')})">
|
||||
<div class="pac-nombre">${escHtml((p.full_name||p.nombre||'') + ' ' + (p.apellido||''))}</div>
|
||||
<div class="pac-doc">${escHtml(p.tipo_documento||'')} ${escHtml(p.documento||p.numero_documento||'')}
|
||||
${p.telefono||p.celular ? '· ' + escHtml(p.telefono||p.celular) : ''}</div>
|
||||
</div>`).join('');
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function seleccionarPaciente(pac) {
|
||||
pacienteActivo = pac;
|
||||
document.getElementById('bloque-pac-no-vinculado').classList.add('d-none');
|
||||
document.getElementById('bloque-pac-seleccionado').classList.remove('d-none');
|
||||
document.getElementById('lbl-pac-nombre').textContent =
|
||||
(pac.full_name || (pac.nombre||'') + ' ' + (pac.apellido||'')).trim();
|
||||
document.getElementById('lbl-pac-doc').textContent =
|
||||
(pac.tipo_documento||'') + ' ' + (pac.documento||pac.numero_documento||'');
|
||||
document.getElementById('lbl-pac-cel').textContent =
|
||||
pac.telefono || pac.celular || '';
|
||||
document.getElementById('lista-pacientes-res').innerHTML = '';
|
||||
}
|
||||
|
||||
function desvincularPaciente() {
|
||||
pacienteActivo = null;
|
||||
document.getElementById('bloque-pac-no-vinculado').classList.remove('d-none');
|
||||
document.getElementById('bloque-pac-seleccionado').classList.add('d-none');
|
||||
document.getElementById('lista-pacientes-res').innerHTML = '';
|
||||
document.getElementById('inp-buscar-pac').value = '';
|
||||
}
|
||||
|
||||
function abrirNuevoPaciente(nombre) {
|
||||
window.open('<?= BASE_URL ?>lab_pacientes.php?nuevo=1&nombre=' + encodeURIComponent(nombre), '_blank');
|
||||
}
|
||||
|
||||
// ── Guardar solicitud ─────────────────────────────────────────
|
||||
async function guardarSolicitud() {
|
||||
if (!turnoActivo) return;
|
||||
if (!pacienteActivo) { mostrarError('Seleccione un paciente antes de guardar.'); return; }
|
||||
|
||||
const lugarId = parseInt(document.getElementById('sel-lugar').value);
|
||||
if (!lugarId) { mostrarError('Seleccione el lugar destino.'); return; }
|
||||
|
||||
const examIds = Array.from(document.querySelectorAll('.exam-chk:checked')).map(c => parseInt(c.value));
|
||||
if (!examIds.length) { mostrarError('Seleccione al menos un examen.'); return; }
|
||||
|
||||
const btn = document.getElementById('btn-guardar');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Guardando…';
|
||||
|
||||
try {
|
||||
const res = await fetch(API + 'create_solicitud.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
turno_id: turnoActivo.id,
|
||||
paciente_id: pacienteActivo.id,
|
||||
lugar_id: lugarId,
|
||||
exam_tipo_ids: examIds,
|
||||
total_cobrado: parseFloat(document.getElementById('inp-total').value) || null,
|
||||
metodo_pago: document.getElementById('sel-pago').value || null,
|
||||
observaciones: document.getElementById('inp-obs').value.trim() || null,
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!json.ok) { mostrarError(json.error); return; }
|
||||
|
||||
solicitudActiva = json.data.solicitud;
|
||||
consentimientos = json.data.consentimientos_requeridos || [];
|
||||
|
||||
btn.innerHTML = '<i class="fas fa-check me-1"></i>Guardado';
|
||||
|
||||
// Mostrar sección de consentimientos si aplica
|
||||
if (consentimientos.length > 0) {
|
||||
renderConsentimientos(consentimientos);
|
||||
document.getElementById('sec-consentimientos').style.display = '';
|
||||
document.getElementById('btn-enviar-consent').classList.remove('d-none');
|
||||
}
|
||||
|
||||
document.getElementById('btn-pasar-lugar').classList.remove('d-none');
|
||||
document.getElementById('btn-guardar').classList.add('d-none');
|
||||
|
||||
} catch (err) {
|
||||
mostrarError(err.message);
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-save me-1"></i>Guardar solicitud';
|
||||
}
|
||||
}
|
||||
|
||||
function renderConsentimientos(lista) {
|
||||
document.getElementById('lista-consentimientos').innerHTML = lista.map(c => {
|
||||
const cls = c.estado === 'firmado' ? 'firmado'
|
||||
: c.estado === 'enviado' ? 'enviado'
|
||||
: 'pendiente';
|
||||
const ico = c.estado === 'firmado' ? 'fa-check-circle'
|
||||
: c.estado === 'enviado' ? 'fa-envelope'
|
||||
: 'fa-clock';
|
||||
return `<div class="consent-item ${cls}">
|
||||
<i class="fas ${ico}"></i>
|
||||
<span class="flex-1">${escHtml(c.formulario_nombre || 'Consentimiento')}</span>
|
||||
<span class="badge">${escHtml(c.estado)}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ── Enviar consentimientos ────────────────────────────────────
|
||||
async function enviarConsentimientos() {
|
||||
if (!turnoActivo || !solicitudActiva) return;
|
||||
const btn = document.getElementById('btn-enviar-consent');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Enviando…';
|
||||
try {
|
||||
const res = await fetch(API + 'send_consentimiento.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ turno_id: turnoActivo.id }),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!json.ok) { mostrarError(json.error); return; }
|
||||
|
||||
consentimientos = json.data.consentimientos || consentimientos;
|
||||
renderConsentimientos(consentimientos);
|
||||
btn.innerHTML = '<i class="fas fa-check me-1"></i>Enviado';
|
||||
} catch (err) {
|
||||
mostrarError(err.message);
|
||||
btn.innerHTML = '<i class="fas fa-paper-plane me-1"></i>Enviar consentimientos';
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pasar a lugar ─────────────────────────────────────────────
|
||||
async function pasarALugar() {
|
||||
if (!turnoActivo || !solicitudActiva) return;
|
||||
|
||||
const btn = document.getElementById('btn-pasar-lugar');
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const res = await fetch(API + 'cambiar_estado.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
turno_id: turnoActivo.id,
|
||||
nuevo_estado: 'en_espera_lugar',
|
||||
lugar_id: solicitudActiva.lugar_id,
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!json.ok) { mostrarError(json.error); btn.disabled = false; return; }
|
||||
|
||||
// Reiniciar ficha
|
||||
turnoActivo = null;
|
||||
solicitudActiva = null;
|
||||
document.getElementById('ficha-turno').classList.add('d-none');
|
||||
document.getElementById('ficha-placeholder').classList.remove('d-none');
|
||||
document.getElementById('badge-turno-activo').classList.add('d-none');
|
||||
cargarCola();
|
||||
|
||||
} catch (err) {
|
||||
mostrarError(err.message);
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Ausente ───────────────────────────────────────────────────
|
||||
async function marcarAusente() {
|
||||
if (!turnoActivo) return;
|
||||
if (!confirm(`¿Marcar turno ${turnoActivo.codigo} como AUSENTE?`)) return;
|
||||
|
||||
await fetch(API + 'cambiar_estado.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ turno_id: turnoActivo.id, nuevo_estado: 'ausente' }),
|
||||
});
|
||||
|
||||
turnoActivo = null;
|
||||
solicitudActiva = null;
|
||||
document.getElementById('ficha-turno').classList.add('d-none');
|
||||
document.getElementById('ficha-placeholder').classList.remove('d-none');
|
||||
document.getElementById('badge-turno-activo').classList.add('d-none');
|
||||
cargarCola();
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────
|
||||
function resetCheckboxes() {
|
||||
document.querySelectorAll('.exam-chk').forEach(c => c.checked = false);
|
||||
}
|
||||
function toggleTodosExamenes(val) {
|
||||
document.querySelectorAll('.exam-chk').forEach(c => c.checked = val);
|
||||
}
|
||||
|
||||
function escHtml(str) {
|
||||
const d = document.createElement('div');
|
||||
d.appendChild(document.createTextNode(String(str)));
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function mostrarError(msg) {
|
||||
alert('Error: ' + msg);
|
||||
}
|
||||
function mostrarAviso(titulo, msg) {
|
||||
alert(titulo + '\n' + msg);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user