diff --git a/modules/turnero/api/get_bandeja.php b/modules/turnero/api/get_bandeja.php index a017d53..00c5a9f 100644 --- a/modules/turnero/api/get_bandeja.php +++ b/modules/turnero/api/get_bandeja.php @@ -81,12 +81,39 @@ if (isset($_GET['turno_id'])) { $stmtCom->execute([$tid]); $comentarios = $stmtCom->fetchAll(PDO::FETCH_ASSOC); + // Turnos vinculados por muestras pendientes completadas en otra visita (ambos sentidos) + $relacionados = []; + $stmtRelFwd = $pdo->prepare(" + SELECT t2.id AS rel_id, t2.codigo AS rel_codigo, s2.fecha AS rel_fecha + FROM turnero_muestras tm + JOIN turnero_turnos t2 ON t2.id = tm.recibida_en_turno_id + JOIN turnero_sesiones s2 ON s2.id = t2.sesion_id + WHERE tm.solicitud_id = ? AND tm.recibida_en_turno_id IS NOT NULL + "); + $stmtRelFwd->execute([$solId]); + foreach ($stmtRelFwd->fetchAll(PDO::FETCH_ASSOC) as $r) { + $relacionados[] = ['turno_id' => (int)$r['rel_id'], 'codigo' => $r['rel_codigo'], 'fecha' => $r['rel_fecha'], 'direccion' => 'seguimiento']; + } + $stmtRelBack = $pdo->prepare(" + SELECT t1.id AS rel_id, t1.codigo AS rel_codigo, s1.fecha AS rel_fecha + FROM turnero_muestras tm + JOIN turnero_solicitudes ts1 ON ts1.id = tm.solicitud_id + JOIN turnero_turnos t1 ON t1.id = ts1.turno_id + JOIN turnero_sesiones s1 ON s1.id = t1.sesion_id + WHERE tm.recibida_en_turno_id = ? + "); + $stmtRelBack->execute([$tid]); + foreach ($stmtRelBack->fetchAll(PDO::FETCH_ASSOC) as $r) { + $relacionados[] = ['turno_id' => (int)$r['rel_id'], 'codigo' => $r['rel_codigo'], 'fecha' => $r['rel_fecha'], 'direccion' => 'origen']; + } + jsonOk([ 'turno' => $data, 'examenes_por_cat'=> $examenesPorCat, 'muestras' => $muestras, 'consentimientos' => $consentimientos, 'comentarios' => $comentarios, + 'relacionados' => $relacionados, ]); } diff --git a/modules/turnero/api/get_formulario_anterior.php b/modules/turnero/api/get_formulario_anterior.php new file mode 100644 index 0000000..89e7d96 --- /dev/null +++ b/modules/turnero/api/get_formulario_anterior.php @@ -0,0 +1,53 @@ +prepare( + "SELECT tc.datos_respuestas, tc.firmado_at, t.codigo AS turno_codigo + FROM turnero_consentimientos tc + JOIN turnero_turnos t ON t.id = tc.turno_id + LEFT JOIN turnero_solicitudes ts ON ts.turno_id = t.id + WHERE tc.formulario_id = ? + AND tc.estado = 'firmado' + AND COALESCE(ts.paciente_id, t.paciente_id) = ? + AND t.id != ? + ORDER BY tc.firmado_at DESC + LIMIT 1" +); +$stmt->execute([$formularioId, $pacienteId, $excludeTurno]); +$row = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$row) { + jsonOk(['encontrado' => false]); +} + +$datos = json_decode($row['datos_respuestas'] ?? '{}', true) ?: []; +// No traer firmas ni identidad del profesional anterior: cada visita se firma de nuevo +foreach (array_keys($datos) as $k) { + if (str_contains($k, 'firma') || $k === '_pro_nombre' || $k === '_pro_cedula') { + unset($datos[$k]); + } +} + +jsonOk([ + 'encontrado' => true, + 'datos_respuestas'=> $datos, + 'firmado_at' => $row['firmado_at'], + 'turno_codigo' => $row['turno_codigo'], +]); diff --git a/modules/turnero/api/get_historial.php b/modules/turnero/api/get_historial.php index 0d224b0..8e08e90 100644 --- a/modules/turnero/api/get_historial.php +++ b/modules/turnero/api/get_historial.php @@ -144,6 +144,7 @@ $turnoIds = array_column($turnos, 'id'); $examenes = []; $consentimientos = []; $comentarios = []; +$relacionados = []; if ($turnoIds) { $ph = implode(',', array_fill(0, count($turnoIds), '?')); @@ -194,6 +195,41 @@ if ($turnoIds) { } } + // Turnos vinculados por muestras pendientes completadas en otra visita — batch (ambos sentidos) + // Sentido "seguimiento": este turno tenía una muestra pendiente que se completó en OTRO turno + $stmtRelFwd = $pdo->prepare( + "SELECT DISTINCT ts.turno_id AS turno_id, t2.id AS rel_id, t2.codigo AS rel_codigo, s2.fecha AS rel_fecha + FROM turnero_muestras tm + JOIN turnero_solicitudes ts ON ts.id = tm.solicitud_id + JOIN turnero_turnos t2 ON t2.id = tm.recibida_en_turno_id + JOIN turnero_sesiones s2 ON s2.id = t2.sesion_id + WHERE ts.turno_id IN ($ph) AND tm.recibida_en_turno_id IS NOT NULL" + ); + $stmtRelFwd->execute($turnoIds); + foreach ($stmtRelFwd->fetchAll(PDO::FETCH_ASSOC) as $row) { + $tid = (int)$row['turno_id']; + $relacionados[$tid][] = [ + 'turno_id' => (int)$row['rel_id'], 'codigo' => $row['rel_codigo'], + 'fecha' => $row['rel_fecha'], 'direccion' => 'seguimiento', + ]; + } + // Sentido "origen": este turno completó una muestra pendiente de OTRO turno anterior + $stmtRelBack = $pdo->prepare( + "SELECT DISTINCT tm.recibida_en_turno_id AS turno_id, t1.id AS rel_id, t1.codigo AS rel_codigo, s1.fecha AS rel_fecha + FROM turnero_muestras tm + JOIN turnero_solicitudes ts ON ts.id = tm.solicitud_id + JOIN turnero_turnos t1 ON t1.id = ts.turno_id + JOIN turnero_sesiones s1 ON s1.id = t1.sesion_id + WHERE tm.recibida_en_turno_id IN ($ph)" + ); + $stmtRelBack->execute($turnoIds); + foreach ($stmtRelBack->fetchAll(PDO::FETCH_ASSOC) as $row) { + $tid = (int)$row['turno_id']; + $relacionados[$tid][] = [ + 'turno_id' => (int)$row['rel_id'], 'codigo' => $row['rel_codigo'], + 'fecha' => $row['rel_fecha'], 'direccion' => 'origen', + ]; + } } foreach ($turnos as &$turno) { @@ -201,6 +237,7 @@ foreach ($turnos as &$turno) { $turno['examenes'] = $examenes[$tid] ?? []; $turno['consentimientos'] = $consentimientos[$tid] ?? []; $turno['comentarios'] = $comentarios[$tid] ?? []; + $turno['relacionados'] = $relacionados[$tid] ?? []; } // ── Resumen por estado (del rango filtrado) ─────────────────── diff --git a/modules/turnero/api/update_muestra_estado.php b/modules/turnero/api/update_muestra_estado.php index 6d4509d..bd972f1 100644 --- a/modules/turnero/api/update_muestra_estado.php +++ b/modules/turnero/api/update_muestra_estado.php @@ -8,6 +8,8 @@ * muestra_id int requerido * estado string requerido 'recibida' | 'rechazada' | 'pendiente' * motivo_rechazo string opcional requerido si estado = 'rechazada' + * turno_id int opcional turno activo desde el que se recibe (para trazabilidad + * cuando la muestra es de una visita anterior) */ require_once __DIR__ . '/_helpers.php'; @@ -18,6 +20,7 @@ $datos = inputJson(); $muestraId = isset($datos['muestra_id']) ? (int)$datos['muestra_id'] : 0; $estado = trim($datos['estado'] ?? ''); $motivo = isset($datos['motivo_rechazo']) ? trim($datos['motivo_rechazo']) : null; +$turnoId = isset($datos['turno_id']) ? (int)$datos['turno_id'] : 0; if ($muestraId <= 0) jsonError('muestra_id inválido.'); if (!in_array($estado, ['recibida', 'rechazada', 'pendiente'], true)) { @@ -29,8 +32,9 @@ $pdo = db(); // Verificar que la muestra existe $stmt = $pdo->prepare( - "SELECT tm.id, tm.solicitud_id, tm.estado + "SELECT tm.id, tm.solicitud_id, tm.estado, ts.turno_id AS turno_origen_id FROM turnero_muestras tm + JOIN turnero_solicitudes ts ON ts.id = tm.solicitud_id WHERE tm.id = ?" ); $stmt->execute([$muestraId]); @@ -38,6 +42,10 @@ $muestra = $stmt->fetch(PDO::FETCH_ASSOC); if (!$muestra) jsonError('Muestra no encontrada.', 404); +// Solo registrar "recibida en otro turno" cuando de verdad es una visita distinta +// a la que generó la muestra (no tocar la solicitud/turno original). +$recibidaEnTurno = ($turnoId && $turnoId !== (int)$muestra['turno_origen_id']) ? $turnoId : null; + // Construir campos a actualizar $ahora = date('Y-m-d H:i:s'); $adminI = adminId(); @@ -46,9 +54,9 @@ if ($estado === 'recibida') { $pdo->prepare( "UPDATE turnero_muestras SET estado = 'recibida', recibida_por = ?, recibida_at = ?, - motivo_rechazo = NULL + recibida_en_turno_id = ?, motivo_rechazo = NULL WHERE id = ?" - )->execute([$adminI, $ahora, $muestraId]); + )->execute([$adminI, $ahora, $recibidaEnTurno, $muestraId]); } elseif ($estado === 'rechazada') { $pdo->prepare( "UPDATE turnero_muestras @@ -61,7 +69,7 @@ if ($estado === 'recibida') { $pdo->prepare( "UPDATE turnero_muestras SET estado = 'pendiente', recibida_por = NULL, - recibida_at = NULL, motivo_rechazo = NULL + recibida_at = NULL, recibida_en_turno_id = NULL, motivo_rechazo = NULL WHERE id = ?" )->execute([$muestraId]); } diff --git a/modules/turnero/views/bandeja.php b/modules/turnero/views/bandeja.php index 55caccf..c879631 100644 --- a/modules/turnero/views/bandeja.php +++ b/modules/turnero/views/bandeja.php @@ -642,6 +642,21 @@ function _renderDetalle(d) { } secM += ``; + // Turno vinculado (muestra pendiente completada en otra visita, o viceversa) + let secRel = ''; + if (d.relacionados?.length) { + secRel = `
Turno vinculado
`; + d.relacionados.forEach(r => { + const txt = r.direccion === 'seguimiento' + ? `Muestra completada en ${r.codigo}` + : `Completa muestra pendiente de ${r.codigo}`; + secRel += ``; + }); + secRel += `
`; + } + // Consentimientos let secC = `
Consentimientos
`; if (!d.consentimientos?.length) { @@ -687,7 +702,7 @@ function _renderDetalle(d) {
`; - document.getElementById('bnd-detail').innerHTML = hdr + secEx + secM + secC + secCom; + document.getElementById('bnd-detail').innerHTML = hdr + secEx + secM + secRel + secC + secCom; } // ── Marcar visto ────────────────────────────────────────────── diff --git a/modules/turnero/views/historial.php b/modules/turnero/views/historial.php index 1e16069..ad6a698 100644 --- a/modules/turnero/views/historial.php +++ b/modules/turnero/views/historial.php @@ -345,6 +345,15 @@ function buscar(pageNum) { cargarPagina(); } +function irATurno(codigo, fecha) { + document.getElementById('fDesde').value = fecha || hoy(); + document.getElementById('fHasta').value = fecha || hoy(); + document.getElementById('fQ').value = codigo; + document.getElementById('fEstado').value = ''; + buscar(); + window.scrollTo({top:0, behavior:'smooth'}); +} + async function cargarPagina() { mostrarSpinner(); const params = new URLSearchParams({ @@ -524,6 +533,23 @@ function renderDetalle(t) { { lbl:'Atendió muestras', val: esc(t.atendido_lugar_nombre || '—') }, { lbl:'Notas', val: esc(t.notas || '—') }, ]; + // Turnos vinculados (muestra pendiente completada en otra visita, o viceversa) + let relHtml = ''; + if (t.relacionados && t.relacionados.length) { + relHtml = `
+
Turno vinculado
+
${t.relacionados.map(r => { + const txt = r.direccion === 'seguimiento' + ? `Muestra completada en ${esc(r.codigo)}` + : `Completa muestra pendiente de ${esc(r.codigo)}`; + return ``; + }).join('')}
+
`; + } + // Exámenes let examHtml = ''; if (t.examenes && t.examenes.length) { @@ -555,7 +581,7 @@ function renderDetalle(t) {
${it.val}
`).join('')} - ${examHtml}${comHtml} + ${relHtml}${examHtml}${comHtml}
Documentos
Cargando… diff --git a/modules/turnero/views/lugar.php b/modules/turnero/views/lugar.php index 44c67ff..f52454d 100644 --- a/modules/turnero/views/lugar.php +++ b/modules/turnero/views/lugar.php @@ -2550,6 +2550,7 @@ async function marcarMuestra(muestraId, estado, motivo = null) { try { const body = { muestra_id: muestraId, estado }; if (motivo) body.motivo_rechazo = motivo; + if (turnoActivo?.id) body.turno_id = turnoActivo.id; const res = await fetch(API + 'update_muestra_estado.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, diff --git a/ver_formulario_enviado.php b/ver_formulario_enviado.php index 83992e2..6f3d339 100644 --- a/ver_formulario_enviado.php +++ b/ver_formulario_enviado.php @@ -34,6 +34,7 @@ if ($modoTurnero) { tc.siguiente_toma_at, tc.toma_inicio_at, f.nombre AS form_nombre, f.categoria, f.descripcion AS form_descripcion, f.esquema, f.es_toma_progresiva, f.solo_profesional, f.doc_encabezado, f.doc_subtitulo, f.doc_logo_base64, f.doc_color, f.doc_pie_pagina, + p.id AS paciente_id, p.nombre_completo AS paciente_nombre, p.numero_documento, p.tipo_documento, p.fecha_nacimiento, p.telefono AS paciente_telefono, p.eps, @@ -986,6 +987,15 @@ function _addExamWizardHtml(string $cid): string {
+ + + + ; window._profNombre = ; window._profDocumento = ; + +// ── Cargar datos de la visita anterior (mismo formulario, mismo paciente) ── +(function() { + var btn = document.getElementById('btnCargarAnterior'); + if (!btn) return; + btn.addEventListener('click', function() { + var base = window.location.href.split('/ver_formulario_enviado.php')[0]; + var msg = document.getElementById('cargarAnteriorMsg'); + btn.disabled = true; + btn.innerHTML = 'Buscando…'; + var params = new URLSearchParams({ + paciente_id: btn.dataset.paciente, + formulario_id: btn.dataset.formulario, + exclude_turno_id: btn.dataset.turno, + }); + fetch(base + '/modules/turnero/api/get_formulario_anterior.php?' + params) + .then(function(r) { return r.json(); }) + .then(function(json) { + btn.disabled = false; + btn.innerHTML = 'Cargar datos de la visita anterior'; + if (!json.ok || !json.encontrado) { + msg.style.display = ''; + msg.textContent = 'No hay una visita anterior con este formulario firmado.'; + return; + } + var datos = json.datos_respuestas || {}; + document.querySelectorAll('[name]').forEach(function(el) { + var raw = el.name, isArr = raw.slice(-2) === '[]', name = isArr ? raw.slice(0, -2) : raw; + if (!(name in datos)) return; + var val = datos[name]; + if (el.type === 'checkbox') { + var arr = Array.isArray(val) ? val : [val]; + el.checked = arr.includes(el.value); + } else if (el.type === 'radio') { + el.checked = (el.value === val); + } else { + el.value = val; + } + }); + msg.style.display = ''; + msg.textContent = 'Datos cargados de la visita ' + (json.turno_codigo || '') + '. Revise y firme.'; + }) + .catch(function() { + btn.disabled = false; + btn.innerHTML = 'Cargar datos de la visita anterior'; + msg.style.display = ''; + msg.textContent = 'Error de conexión.'; + }); + }); +})(); // Fix 1: recuperar countdown si el modal fue cerrado y reabierto mientras una toma estaba en curso (function() {