feat: encuesta de satisfacción post-finalizar toma de muestras

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-29 21:23:07 -05:00
co-authored by Claude Sonnet 4.6
parent 4a4cbc31b8
commit 2455417bb4
2 changed files with 119 additions and 2 deletions
+52
View File
@@ -0,0 +1,52 @@
<?php
/**
* POST /modules/turnero/api/enviar_encuesta.php
* Envía la plantilla "encuesta" al paciente del turno vía WhatsApp turnero.
* Body JSON: { turno_id: int }
*/
require_once __DIR__ . '/_helpers.php';
require_once __DIR__ . '/../../../services/WhatsAppService.php';
requireMethod('POST');
requireTurnero();
$body = inputJson();
$turnoId = (int)($body['turno_id'] ?? 0);
if (!$turnoId) jsonError('turno_id requerido.');
$pdo = db();
// Obtener user_id y teléfono del paciente vía turnero_turnos → lab_pacientes → users
$row = $pdo->prepare(
"SELECT u.id AS user_id, u.phone_number, t.paciente_cel, t.paciente_nombre
FROM turnero_turnos t
LEFT JOIN lab_pacientes lp ON lp.id = t.paciente_id
LEFT JOIN users u ON u.id = lp.user_id
WHERE t.id = ? LIMIT 1"
);
$row->execute([$turnoId]);
$pac = $row->fetch(PDO::FETCH_ASSOC);
if (!$pac) jsonError('Turno no encontrado.', 404);
$userId = $pac['user_id'] ? (int)$pac['user_id'] : null;
$phone = $pac['phone_number'] ?? null;
// Fallback: buscar por paciente_cel si no hay user_id
if (!$userId && !empty($pac['paciente_cel'])) {
$cel = preg_replace('/\D/', '', $pac['paciente_cel']);
$u2 = $pdo->prepare("SELECT id, phone_number FROM users WHERE REPLACE(phone_number,'+','') LIKE ? LIMIT 1");
$u2->execute(['%' . substr($cel, -10)]);
$uRow = $u2->fetch(PDO::FETCH_ASSOC);
if ($uRow) { $userId = (int)$uRow['id']; $phone = $uRow['phone_number']; }
}
if (!$userId || !$phone) jsonError('El paciente no tiene número de WhatsApp registrado.');
try {
$wa = new WhatsAppService('turnero');
$meta = ['canal' => 'turnero', 'operator_id' => adminId()];
$wa->sendTemplateMessage($phone, 'encuesta', 'es_CO', [], [], null, $meta);
jsonOk(['mensaje' => 'Encuesta enviada a ' . ($pac['paciente_nombre'] ?? $phone)]);
} catch (\Throwable $e) {
jsonError('Error al enviar: ' . $e->getMessage());
}
+67 -2
View File
@@ -994,6 +994,26 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
</div>
</div>
<!-- ── Bar encuesta post-finalizar ─────────────────────────────── -->
<div id="bar-encuesta" style="
display:none;position:fixed;bottom:0;left:0;right:0;z-index:9100;
background:#0f4c81;color:#fff;padding:14px 20px;
display:none;align-items:center;gap:14px;flex-wrap:wrap;
box-shadow:0 -4px 20px rgba(0,0,0,.25)">
<i class="fas fa-star" style="font-size:1.2rem;color:#fbbf24;flex-shrink:0"></i>
<span id="bar-encuesta-txt" style="flex:1;font-size:.95rem;font-weight:500"></span>
<button onclick="_enviarEncuesta()" id="btn-enc-enviar"
style="background:#22c55e;color:#fff;border:none;border-radius:8px;
padding:8px 18px;font-weight:600;cursor:pointer;white-space:nowrap">
<i class="fas fa-paper-plane me-1"></i>Enviar encuesta
</button>
<button onclick="_cerrarEncuestaBar()"
style="background:rgba(255,255,255,.15);color:#fff;border:none;border-radius:8px;
padding:8px 14px;cursor:pointer;white-space:nowrap">
Omitir
</button>
</div>
<!-- ── Modal de consentimiento embebido (modo embebido) ────────── -->
<div id="modal-consentimiento" style="
display:none;position:fixed;inset:0;z-index:9000;
@@ -1961,7 +1981,9 @@ async function finalizarAtencion() {
if (hayPendientes) { mostrarError('Debe firmar todos los consentimientos antes de finalizar.'); return; }
if (hayMuestrasPendientes) { mostrarError('Debe marcar cada muestra como recibida o pendiente antes de finalizar.'); return; }
if (!confirm(`¿Finalizar atención del turno ${turnoActivo.codigo}?`)) return;
await cambiarEstadoTurno('finalizado');
const _t = { id: turnoActivo.id, paciente_nombre: turnoActivo.paciente_nombre };
const ok = await cambiarEstadoTurno('finalizado');
if (ok) _ofrecerEncuesta(_t);
}
async function marcarAusente() {
@@ -2024,15 +2046,58 @@ async function cambiarEstadoTurno(nuevoEstado) {
} else {
mostrarError(json.error);
}
return;
return false;
}
resetFicha();
cargarCola();
return true;
} catch (err) {
mostrarError(err.message);
return false;
}
}
// ── Encuesta post-finalizar ───────────────────────────────────
let _encuestaTurnoId = null, _encuestaTimer = null;
function _ofrecerEncuesta(turno) {
_encuestaTurnoId = turno.id;
const bar = document.getElementById('bar-encuesta');
document.getElementById('bar-encuesta-txt').textContent =
`¿Enviar encuesta de satisfacción a ${turno.paciente_nombre || 'el paciente'}?`;
document.getElementById('btn-enc-enviar').disabled = false;
document.getElementById('btn-enc-enviar').innerHTML = '<i class="fas fa-paper-plane me-1"></i>Enviar encuesta';
bar.style.display = 'flex';
clearTimeout(_encuestaTimer);
_encuestaTimer = setTimeout(_cerrarEncuestaBar, 12000);
}
async function _enviarEncuesta() {
if (!_encuestaTurnoId) return;
const btn = document.getElementById('btn-enc-enviar');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Enviando…';
try {
const res = await fetch(API + 'enviar_encuesta.php', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ turno_id: _encuestaTurnoId }),
});
const json = await res.json();
_cerrarEncuestaBar();
if (json.ok) mostrarToast('Encuesta enviada ✓', 'success', 3000);
else mostrarToast('No se pudo enviar: ' + (json.error || 'error'), 'warning', 4000);
} catch (e) {
_cerrarEncuestaBar();
mostrarToast('Error al enviar encuesta', 'warning', 3000);
}
}
function _cerrarEncuestaBar() {
clearTimeout(_encuestaTimer);
document.getElementById('bar-encuesta').style.display = 'none';
_encuestaTurnoId = null;
}
// ── Reset ficha ───────────────────────────────────────────────
function resetFicha() {
clearInterval(pollingConsentId);