feat(turnero): selector de médico tratante en recepción, visible en toma de muestras

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-03 00:55:32 -05:00
co-authored by Claude Sonnet 4.6
parent ec614c2a89
commit d3826972ad
6 changed files with 101 additions and 5 deletions
+4
View File
@@ -0,0 +1,4 @@
ALTER TABLE turnero_solicitudes
ADD COLUMN medico_id INT UNSIGNED DEFAULT NULL
COMMENT 'FK medicos.id — médico tratante seleccionado en recepción'
AFTER solo_muestras;
+1
View File
@@ -11,6 +11,7 @@ $pendientes = [
'20260703_solo_muestras.sql',
'20260703_medicos.sql',
'20260703_medicos_seed.sql',
'20260703_solicitud_medico.sql',
];
$pdo = Database::getInstance()->getConnection();
+4 -3
View File
@@ -32,6 +32,7 @@ $obs = isset($datos['observaciones']) ? trim((string) $datos['observac
$numOrden = isset($datos['numero_orden']) ? trim((string) $datos['numero_orden']) : null;
$embarazada = !empty($datos['embarazada']) ? 1 : 0;
$soloMuestras = !empty($datos['solo_muestras']) ? 1 : 0;
$medicoId = !empty($datos['medico_id']) ? (int)$datos['medico_id'] : null;
$pagosDetalle = null;
if ($metodoPago === 'combinado' && isset($datos['pagos_detalle']) && is_array($datos['pagos_detalle'])) {
$pd = $datos['pagos_detalle'];
@@ -114,12 +115,12 @@ try {
$stmt = $pdo->prepare(
"INSERT INTO turnero_solicitudes
(turno_id, paciente_id, lugar_id, numero_orden, total_cobrado, metodo_pago, pagos_detalle, observaciones, embarazada, solo_muestras, creado_por)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
(turno_id, paciente_id, lugar_id, numero_orden, total_cobrado, metodo_pago, pagos_detalle, observaciones, embarazada, solo_muestras, medico_id, creado_por)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
$stmt->execute([
$turnoId, $pacienteId, $lugarId, $numOrden,
$total, $metodoPago ?: null, $pagosDetalle, $obs ?: null, $embarazada, $soloMuestras, adminId(),
$total, $metodoPago ?: null, $pagosDetalle, $obs ?: null, $embarazada, $soloMuestras, $medicoId, adminId(),
]);
$solicitudId = (int) $pdo->lastInsertId();
+6 -2
View File
@@ -56,10 +56,14 @@ $respuesta = [
if ($incluirSolicitud) {
$stmt = $pdo->prepare(
"SELECT s.id, s.turno_id, s.paciente_id, s.lugar_id,
s.total_cobrado, s.metodo_pago, s.observaciones, s.embarazada, s.creado_at,
l.nombre AS lugar_nombre
s.total_cobrado, s.metodo_pago, s.observaciones, s.embarazada, s.solo_muestras, s.medico_id, s.creado_at,
l.nombre AS lugar_nombre,
CONCAT(m.nombres, ' ', m.apellidos) AS medico_nombre,
m.cod_especialidad AS medico_especialidad,
m.codigo AS medico_codigo
FROM turnero_solicitudes s
LEFT JOIN turnero_lugares l ON l.id = s.lugar_id
LEFT JOIN medicos m ON m.id = s.medico_id
WHERE s.turno_id = ?
LIMIT 1"
);
+16
View File
@@ -299,6 +299,12 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
<i class="fas fa-baby me-1"></i>Paciente embarazada
</span>
</div>
<div id="pac-medico" class="d-none mt-2">
<div class="pac-dato">
<span class="lbl">Médico</span>
<span class="val" id="pac-medico-nombre" style="color:#4f46e5"></span>
</div>
</div>
<div id="bloque-pac-sin" class="text-muted small">
<i class="fas fa-info-circle me-1"></i>Sin paciente vinculado
@@ -610,6 +616,16 @@ async function cargarFichaSolicitud(turnoId) {
const badgeEmb = document.getElementById('pac-embarazada');
if (badgeEmb) badgeEmb.classList.toggle('d-none', !(sol && sol.embarazada == 1));
const medDiv = document.getElementById('pac-medico');
if (medDiv) {
const tieneMedico = sol && sol.medico_nombre;
medDiv.classList.toggle('d-none', !tieneMedico);
if (tieneMedico) {
const esp = sol.medico_especialidad ? ' · ' + sol.medico_especialidad : '';
document.getElementById('pac-medico-nombre').textContent = sol.medico_nombre + esp;
}
}
if (pac) {
document.getElementById('bloque-pac-info').style.display = '';
document.getElementById('bloque-pac-sin').classList.add('d-none');
+70
View File
@@ -448,6 +448,27 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</div>
<!-- Médico tratante -->
<div class="mt-2" id="bloque-medico">
<div class="small fw-semibold text-secondary mb-1">
<i class="fas fa-user-md me-1"></i>Médico tratante <span class="text-muted fw-normal">(opcional)</span>
</div>
<div id="medico-seleccionado" class="d-none mb-1">
<span class="badge text-bg-light border" style="font-size:.8rem;padding:5px 9px" id="medico-badge-txt"></span>
<button type="button" class="btn btn-link btn-sm p-0 ms-1 text-danger" onclick="quitarMedico()" title="Quitar médico"><i class="fas fa-times"></i></button>
</div>
<div id="medico-buscador">
<input type="text" id="inp-buscar-medico" class="form-control form-control-sm"
placeholder="Buscar por nombre, código o doc…"
autocomplete="off" oninput="buscarMedico()">
<div id="medico-dropdown" class="position-relative">
<ul id="medico-resultados" class="list-unstyled mb-0 border rounded bg-white shadow-sm position-absolute w-100 d-none"
style="z-index:200;max-height:180px;overflow-y:auto;top:2px"></ul>
</div>
</div>
<input type="hidden" id="inp-medico-id">
</div>
<!-- Historial colapsable del paciente -->
<div id="sec-historial-pac" class="d-none">
<button class="hist-toggle-btn" id="btn-hist-toggle" onclick="toggleHistorialPaciente()">
@@ -1328,6 +1349,7 @@ async function guardarSolicitud() {
observaciones: document.getElementById('inp-obs').value.trim() || null,
embarazada: document.getElementById('chk-embarazada').checked ? 1 : 0,
solo_muestras: soloMuestras ? 1 : 0,
medico_id: parseInt(document.getElementById('inp-medico-id').value) || null,
}),
});
const json = await res.json();
@@ -1672,6 +1694,53 @@ async function soltarTurno() {
}
// ── Helpers ───────────────────────────────────────────────────
// ── Médico tratante ─────────────────────────────────────────────
let _medicoTimer = null;
const API_MEDICOS = '<?= BASE_URL ?>modules/medicos/api/list.php';
function buscarMedico() {
clearTimeout(_medicoTimer);
const q = document.getElementById('inp-buscar-medico').value.trim();
const ul = document.getElementById('medico-resultados');
if (q.length < 2) { ul.classList.add('d-none'); ul.innerHTML = ''; return; }
_medicoTimer = setTimeout(async () => {
const res = await fetch(API_MEDICOS + '?q=' + encodeURIComponent(q));
const json = await res.json();
const rows = (json.data || []).slice(0, 8);
if (!rows.length) { ul.innerHTML = '<li class="px-3 py-2 text-muted small">Sin resultados</li>'; ul.classList.remove('d-none'); return; }
ul.innerHTML = rows.map(m =>
`<li class="px-3 py-2 small" style="cursor:pointer;border-bottom:1px solid #f1f5f9"
onmousedown="seleccionarMedico(${m.id},'${escJs(m.codigo)}','${escJs(m.nombres)} ${escJs(m.apellidos)}','${escJs(m.cod_especialidad||'')}')">
<span class="fw-semibold">${esc(m.nombres)} ${esc(m.apellidos)}</span>
<span class="text-muted ms-1">[${esc(m.codigo)}]</span>
${m.cod_especialidad ? `<span class="badge text-bg-light ms-1" style="font-size:.7rem">${esc(m.cod_especialidad)}</span>` : ''}
</li>`
).join('');
ul.classList.remove('d-none');
}, 250);
}
function seleccionarMedico(id, codigo, nombre, esp) {
document.getElementById('inp-medico-id').value = id;
document.getElementById('medico-badge-txt').textContent = nombre + (esp ? ' · ' + esp : '') + ' [' + codigo + ']';
document.getElementById('medico-seleccionado').classList.remove('d-none');
document.getElementById('medico-buscador').classList.add('d-none');
document.getElementById('medico-resultados').classList.add('d-none');
}
function quitarMedico() {
document.getElementById('inp-medico-id').value = '';
document.getElementById('medico-seleccionado').classList.add('d-none');
document.getElementById('medico-buscador').classList.remove('d-none');
document.getElementById('inp-buscar-medico').value = '';
}
function resetMedico() {
quitarMedico();
}
function escJs(s) { return String(s||'').replace(/\\/g,'\\\\').replace(/'/g,"\\'"); }
function toggleSoloMuestras() {
const solo = document.getElementById('chk-solo-muestras').checked;
document.querySelectorAll('.exam-chk').forEach(c => { c.checked = false; c.disabled = solo; });
@@ -1686,6 +1755,7 @@ function resetCheckboxes() {
document.getElementById('inp-total').value = '';
document.getElementById('inp-obs').value = '';
resetPagoCombinado();
resetMedico();
}
function toggleTodosExamenes(val) {
document.querySelectorAll('.exam-chk').forEach(c => c.checked = val);