Files
2026-04-18 23:41:01 -05:00

420 lines
20 KiB
PHP

<?php
/**
* modules/registro_exams/views/nueva_orden.php
* Formulario para crear una nueva orden de exámenes.
* URL: /erp.php?m=registro_exams&v=nueva_orden[&solicitud_id=N]
*/
require_once APP_ROOT . '/config/config.php';
// Pre-carga de solicitud del turnero (opcional)
$solicitudId = isset($_GET['solicitud_id']) ? (int) $_GET['solicitud_id'] : null;
$pacientePreId = null;
$pacientePreNom = '';
$pacientePreDoc = '';
if ($solicitudId) {
try {
$pdo = Database::getInstance()->getConnection();
$stmt = $pdo->prepare('
SELECT s.id AS sol_id,
p.id AS pac_id, p.nombre_completo, p.numero_documento
FROM turnero_solicitudes s
JOIN lab_pacientes p ON p.id = s.paciente_id
WHERE s.id = ?
');
$stmt->execute([$solicitudId]);
$pre = $stmt->fetch(PDO::FETCH_ASSOC);
if ($pre) {
$pacientePreId = (int) $pre['pac_id'];
$pacientePreNom = $pre['nombre_completo'];
$pacientePreDoc = $pre['numero_documento'];
}
} catch (\Throwable $e) {
// continuar sin pre-cargar
}
}
// Cargar tipos de exámenes activos agrupados por categoría
$examTipos = [];
try {
$pdo = Database::getInstance()->getConnection();
$stmt = $pdo->query('
SELECT id, codigo, nombre, categoria, requiere_ayuno, horas_ayuno,
precio_base, instrucciones
FROM exam_tipos
WHERE activo = 1
ORDER BY categoria, nombre
');
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$cat = $row['categoria'] ?: 'General';
$examTipos[$cat][] = $row;
}
} catch (\Throwable $e) {}
Layout::open('Nueva Orden de Exámenes', 'fas fa-plus-circle');
?>
<div class="container-fluid py-3" style="max-width: 960px;">
<div class="d-flex align-items-center gap-2 mb-3">
<a href="<?= BASE_URL ?>/erp.php?m=registro_exams&v=lista" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Volver
</a>
<h4 class="mb-0"><i class="fas fa-plus-circle me-2 text-primary"></i>Nueva Orden de Exámenes</h4>
</div>
<form id="formOrden" novalidate>
<input type="hidden" id="ordenId" value="">
<input type="hidden" id="solicitudIdHidden" value="<?= (int) $solicitudId ?>">
<!-- ── SECCIÓN 1: Paciente ─────────────────────────── -->
<div class="card shadow-sm mb-3">
<div class="card-header bg-primary text-white py-2">
<i class="fas fa-user me-2"></i><strong>1. Datos del paciente</strong>
</div>
<div class="card-body">
<input type="hidden" id="pacienteId" value="<?= (int) $pacientePreId ?>">
<?php if ($pacientePreId): ?>
<!-- Paciente pre-cargado desde turnero -->
<div id="pacienteSeleccionado" class="alert alert-success d-flex align-items-center gap-3 mb-2">
<i class="fas fa-user-check fs-4"></i>
<div>
<div class="fw-bold" id="pacNombre"><?= htmlspecialchars($pacientePreNom) ?></div>
<div class="small" id="pacDoc"><?= htmlspecialchars($pacientePreDoc) ?></div>
</div>
<button type="button" class="btn btn-sm btn-outline-danger ms-auto" id="btnCambiarPaciente">
<i class="fas fa-times me-1"></i>Cambiar
</button>
</div>
<div id="buscadorPaciente" class="d-none">
<?php else: ?>
<div id="pacienteSeleccionado" class="alert alert-light border-dashed d-none">
<div class="fw-bold" id="pacNombre"></div>
<div class="small text-muted" id="pacDoc"></div>
<button type="button" class="btn btn-sm btn-outline-secondary mt-1" id="btnCambiarPaciente">Cambiar</button>
</div>
<div id="buscadorPaciente">
<?php endif; ?>
<label class="form-label fw-semibold">Buscar paciente</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="search" id="inputBuscarPac" class="form-control"
placeholder="Nombre o número de documento…" autocomplete="off">
</div>
<div id="resultadosPac" class="list-group mt-1 shadow-sm"></div>
</div><!-- /buscadorPaciente -->
</div><!-- /card-body -->
</div>
<!-- ── SECCIÓN 2: Datos médicos ───────────────────── -->
<div class="card shadow-sm mb-3">
<div class="card-header py-2">
<i class="fas fa-stethoscope me-2 text-secondary"></i><strong>2. Datos médicos</strong>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-5">
<label class="form-label small fw-semibold">Médico solicitante</label>
<input type="text" id="medicoNombre" class="form-control form-control-sm"
placeholder="Nombre del médico">
</div>
<div class="col-md-3">
<label class="form-label small fw-semibold">Registro médico</label>
<input type="text" id="medicoRegistro" class="form-control form-control-sm"
placeholder="No. registro">
</div>
<div class="col-md-4">
<label class="form-label small fw-semibold">Prioridad</label>
<select id="prioridad" class="form-select form-select-sm">
<option value="normal" selected>Normal</option>
<option value="urgente">Urgente</option>
<option value="stat">STAT (inmediato)</option>
</select>
</div>
<div class="col-12">
<label class="form-label small fw-semibold">Diagnóstico / indicación</label>
<textarea id="diagnostico" class="form-control form-control-sm" rows="2"
placeholder="CIE-10 o descripción clínica"></textarea>
</div>
<div class="col-12">
<label class="form-label small fw-semibold">Notas internas</label>
<textarea id="notas" class="form-control form-control-sm" rows="2"
placeholder="Observaciones para el bacteriólogo…"></textarea>
</div>
</div>
</div>
</div>
<!-- ── SECCIÓN 3: Exámenes solicitados ───────────── -->
<div class="card shadow-sm mb-3">
<div class="card-header py-2">
<i class="fas fa-vials me-2 text-secondary"></i>
<strong>3. Exámenes solicitados</strong>
</div>
<div class="card-body">
<!-- Buscador de exámenes -->
<div class="input-group mb-3">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="search" id="inputBuscarExam" class="form-control"
placeholder="Filtrar exámenes por nombre o código…">
</div>
<?php if (empty($examTipos)): ?>
<div class="alert alert-warning">
No hay tipos de examen configurados. Configure los exámenes en
<a href="<?= BASE_URL ?>/erp.php?m=turnero&v=configuracion">Configuración → Exámenes</a>.
</div>
<?php else: ?>
<div id="catalogoExams">
<?php foreach ($examTipos as $categoria => $tipos): ?>
<div class="exam-categoria mb-2">
<div class="fw-semibold text-uppercase text-muted small border-bottom pb-1 mb-2 categoria-header">
<?= htmlspecialchars($categoria) ?>
</div>
<div class="row g-2">
<?php foreach ($tipos as $t): ?>
<div class="col-sm-6 col-md-4 exam-item"
data-nombre="<?= htmlspecialchars(strtolower($t['nombre'])) ?>"
data-codigo="<?= htmlspecialchars(strtolower($t['codigo'])) ?>">
<label class="card h-100 border-0 bg-light p-2 cursor-pointer exam-card"
for="et<?= $t['id'] ?>">
<div class="d-flex align-items-start gap-2">
<input class="form-check-input mt-1 exam-checkbox flex-shrink-0"
type="checkbox"
id="et<?= $t['id'] ?>"
value="<?= $t['id'] ?>"
data-nombre="<?= htmlspecialchars($t['nombre']) ?>"
data-ayuno="<?= $t['requiere_ayuno'] ? '1' : '0' ?>"
data-horas-ayuno="<?= (int) $t['horas_ayuno'] ?>"
data-precio="<?= $t['precio_base'] !== null ? number_format((float)$t['precio_base'], 0, ',', '.') : '' ?>">
<div class="lh-sm">
<div class="fw-semibold small"><?= htmlspecialchars($t['nombre']) ?></div>
<div class="text-muted" style="font-size:.75rem"><?= htmlspecialchars($t['codigo']) ?></div>
<?php if ($t['requiere_ayuno']): ?>
<span class="badge bg-warning text-dark" style="font-size:.65rem">
<i class="fas fa-clock me-1"></i>Ayuno <?= $t['horas_ayuno'] ? $t['horas_ayuno'].'h' : '' ?>
</span>
<?php endif; ?>
<?php if ($t['precio_base']): ?>
<span class="badge bg-light text-secondary border" style="font-size:.65rem">
$<?= number_format((float)$t['precio_base'], 0, ',', '.') ?>
</span>
<?php endif; ?>
</div>
</div>
</label>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Resumen seleccionados -->
<div id="resumenExams" class="mt-3 d-none">
<div class="alert alert-primary py-2 mb-0">
<strong><i class="fas fa-check-circle me-1"></i>Seleccionados:</strong>
<span id="resumenLista"></span>
<span id="ayunoAviso" class="ms-2 badge bg-warning text-dark d-none">
<i class="fas fa-clock me-1"></i> Ayuno requerido
</span>
</div>
</div>
</div>
</div>
<!-- ── Botones ───────────────────────────────────── -->
<div class="d-flex gap-2 justify-content-end mb-4">
<a href="<?= BASE_URL ?>/erp.php?m=registro_exams&v=lista"
class="btn btn-outline-secondary">
Cancelar
</a>
<button type="submit" class="btn btn-primary px-4" id="btnGuardar">
<i class="fas fa-save me-1"></i> Crear orden
</button>
</div>
</form>
</div>
<style>
.exam-card { cursor: pointer; transition: border-color .15s; }
.exam-card:hover { border-color: #0d6efd !important; background: #e9f0ff !important; }
.exam-card:has(.exam-checkbox:checked) { background: #dbeafe !important; border: 1px solid #0d6efd !important; }
.cursor-pointer { cursor: pointer; }
</style>
<script>
(function () {
const BASE = '<?= BASE_URL ?>';
const API_PAC = BASE + '/api/lab/get_pacientes.php';
const API_SV = BASE + '/modules/registro_exams/api/save_orden.php';
let debounceTimer = null;
let examDebounce = null;
// ── Buscador de pacientes ──────────────────────────────────
const inputBuscarPac = document.getElementById('inputBuscarPac');
const resultadosPac = document.getElementById('resultadosPac');
const buscadorDiv = document.getElementById('buscadorPaciente');
const seleccionadoDiv = document.getElementById('pacienteSeleccionado');
const pacNombreEl = document.getElementById('pacNombre');
const pacDocEl = document.getElementById('pacDoc');
const pacienteIdEl = document.getElementById('pacienteId');
if (inputBuscarPac) {
inputBuscarPac.addEventListener('input', () => {
clearTimeout(debounceTimer);
const q = inputBuscarPac.value.trim();
if (q.length < 2) { resultadosPac.innerHTML = ''; return; }
debounceTimer = setTimeout(() => buscarPaciente(q), 300);
});
}
async function buscarPaciente(q) {
try {
const r = await fetch(`${API_PAC}?busqueda=${encodeURIComponent(q)}&limit=8`);
const j = await r.json();
if (!j.ok || !j.data?.length) {
resultadosPac.innerHTML = '<div class="list-group-item text-muted small">Sin resultados</div>';
return;
}
resultadosPac.innerHTML = j.data.map(p =>
`<button type="button" class="list-group-item list-group-item-action py-2"
data-id="${p.id}" data-nombre="${escAtt(p.nombre_completo)}" data-doc="${escAtt(p.numero_documento)} ${escAtt(p.tipo_documento||'')}">
<div class="fw-semibold lh-sm">${escHtml(p.nombre_completo)}</div>
<div class="small text-muted">${escHtml(p.tipo_documento||'')} ${escHtml(p.numero_documento)}</div>
</button>`
).join('');
resultadosPac.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', () => seleccionarPaciente(
parseInt(btn.dataset.id), btn.dataset.nombre, btn.dataset.doc
));
});
} catch { resultadosPac.innerHTML = ''; }
}
function seleccionarPaciente(id, nombre, doc) {
pacienteIdEl.value = id;
pacNombreEl.textContent = nombre;
pacDocEl.textContent = doc;
resultadosPac.innerHTML = '';
buscadorDiv.classList.add('d-none');
seleccionadoDiv.classList.remove('d-none');
seleccionadoDiv.classList.remove('alert-light');
seleccionadoDiv.classList.add('alert-success');
}
const btnCambiar = document.getElementById('btnCambiarPaciente');
if (btnCambiar) {
btnCambiar.addEventListener('click', () => {
pacienteIdEl.value = '';
buscadorDiv.classList.remove('d-none');
seleccionadoDiv.classList.add('d-none');
if (inputBuscarPac) { inputBuscarPac.value = ''; inputBuscarPac.focus(); }
});
}
// ── Buscador de exámenes ───────────────────────────────────
const inputBuscarExam = document.getElementById('inputBuscarExam');
if (inputBuscarExam) {
inputBuscarExam.addEventListener('input', () => {
clearTimeout(examDebounce);
examDebounce = setTimeout(() => filtrarExams(inputBuscarExam.value.trim().toLowerCase()), 200);
});
}
function filtrarExams(q) {
const items = document.querySelectorAll('.exam-item');
const cats = document.querySelectorAll('.exam-categoria');
items.forEach(el => {
const visible = !q || el.dataset.nombre.includes(q) || el.dataset.codigo.includes(q);
el.style.display = visible ? '' : 'none';
});
// Ocultar categorías vacías
cats.forEach(cat => {
const visibles = [...cat.querySelectorAll('.exam-item')].some(i => i.style.display !== 'none');
cat.style.display = visibles ? '' : 'none';
});
}
// ── Resumen de exámenes seleccionados ──────────────────────
document.querySelectorAll('.exam-checkbox').forEach(cb => {
cb.addEventListener('change', actualizarResumen);
});
function actualizarResumen() {
const sel = [...document.querySelectorAll('.exam-checkbox:checked')];
const resumenDiv = document.getElementById('resumenExams');
const resumenLista = document.getElementById('resumenLista');
const ayunoAviso = document.getElementById('ayunoAviso');
if (!sel.length) {
resumenDiv.classList.add('d-none');
return;
}
resumenDiv.classList.remove('d-none');
resumenLista.textContent = sel.map(c => c.dataset.nombre).join(' · ');
const ayuno = sel.some(c => c.dataset.ayuno === '1');
ayunoAviso.classList.toggle('d-none', !ayuno);
}
// ── Enviar formulario ──────────────────────────────────────
document.getElementById('formOrden').addEventListener('submit', async (e) => {
e.preventDefault();
const pacId = parseInt(document.getElementById('pacienteId').value) || 0;
if (!pacId) {
alert('Seleccione un paciente antes de continuar.');
return;
}
const items = [...document.querySelectorAll('.exam-checkbox:checked')]
.map(c => parseInt(c.value));
if (!items.length) {
alert('Seleccione al menos un examen.');
return;
}
const btn = document.getElementById('btnGuardar');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Guardando…';
const body = {
id: null,
paciente_id: pacId,
solicitud_id: parseInt(document.getElementById('solicitudIdHidden').value) || null,
medico_nombre: document.getElementById('medicoNombre').value.trim(),
medico_registro: document.getElementById('medicoRegistro').value.trim(),
diagnostico: document.getElementById('diagnostico').value.trim(),
prioridad: document.getElementById('prioridad').value,
notas: document.getElementById('notas').value.trim(),
items: items,
};
try {
const r = await fetch(API_SV, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const j = await r.json();
if (!j.ok) throw new Error(j.error);
// Redirigir a la orden creada
window.location.href = `${BASE}/erp.php?m=registro_exams&v=orden&id=${j.id}`;
} catch (err) {
alert('Error al guardar: ' + err.message);
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-save me-1"></i> Crear orden';
}
});
function escHtml(s) { return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function escAtt(s) { return String(s||'').replace(/"/g,'&quot;'); }
})();
</script>
<?php Layout::close(); ?>