This commit is contained in:
Lizandro Guarnizo
2026-04-18 23:41:01 -05:00
parent 876e935a66
commit 318a1742cc
37 changed files with 8838 additions and 89 deletions
+237 -8
View File
@@ -1,13 +1,22 @@
<?php
/**
* ver_formulario_enviado.php — Ver respuesta de un formulario (imprimible / PDF)
* Acceso admin/enfermero: ver_formulario_enviado.php?id=ENVIO_ID (requiere sesión)
* Acceso público cliente: ver_formulario_enviado.php?t=TOKEN (sin sesión, solo firmado/completado)
* Acceso admin/enfermero: ver_formulario_enviado.php?id=ENVIO_ID (requiere sesión)
* Acceso público cliente: ver_formulario_enviado.php?t=TOKEN (sin sesión, solo firmado/completado)
* Acceso turnero (consent): ver_formulario_enviado.php?token=UUID (firma de consentimiento turnero)
*/
require_once 'config/config.php';
// ── TURNERO: Consentimiento por token UUID (detección previa al flujo normal) ──
$tokenTurnero = trim($_GET['token'] ?? '');
$modoTurnero = ($tokenTurnero !== '');
if ($modoTurnero && !preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $tokenTurnero)) {
http_response_code(400); die('Token de consentimiento inválido');
}
$tokenPublico = trim($_GET['t'] ?? '');
$modoPublico = ($tokenPublico !== '');
$modoPublico = ($modoTurnero || $tokenPublico !== '');
if (!$modoPublico && !isUserLoggedIn()) {
header('Location: login.php'); exit;
@@ -16,7 +25,93 @@ if (!$modoPublico && !isUserLoggedIn()) {
// ── Cargar datos ──────────────────────────────────────────────────────
$db = Database::getInstance();
if ($modoPublico) {
if ($modoTurnero) {
// ── Turnero: cargar desde turnero_consentimientos ──────────────────
$tcRow = $db->fetch(
"SELECT tc.id, tc.turno_id, tc.formulario_id, tc.token, tc.estado,
tc.enviado_at, tc.firmado_at, tc.ip_firma, tc.ua_firma, tc.firma_svg,
f.nombre AS form_nombre, f.categoria, f.descripcion AS form_descripcion,
f.esquema, f.doc_encabezado, f.doc_subtitulo, f.doc_logo_base64, f.doc_color, f.doc_pie_pagina,
p.nombre_completo AS paciente_nombre, p.numero_documento, p.tipo_documento,
p.fecha_nacimiento, p.telefono AS paciente_telefono, p.eps,
t.sesion_id
FROM turnero_consentimientos tc
JOIN lab_formularios f ON f.id = tc.formulario_id
JOIN turnero_turnos t ON t.id = tc.turno_id
JOIN turnero_solicitudes ts ON ts.turno_id = tc.turno_id
LEFT JOIN lab_pacientes p ON p.id = ts.paciente_id
WHERE tc.token = ?",
[$tokenTurnero]
);
if (!$tcRow) { http_response_code(404); die('Consentimiento no encontrado'); }
// ── POST: guardar firma y marcar como firmado ──────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json; charset=utf-8');
if ($tcRow['estado'] === 'firmado') {
echo json_encode(['ok' => false, 'error' => 'Este consentimiento ya fue firmado']); exit;
}
$input = json_decode(file_get_contents('php://input'), true) ?? [];
$firmaSvg = trim($input['firma_svg'] ?? '');
if (strlen($firmaSvg) < 100) {
echo json_encode(['ok' => false, 'error' => 'Firma requerida']); exit;
}
$ip = filter_var(
$_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '',
FILTER_VALIDATE_IP
) ?: ($_SERVER['REMOTE_ADDR'] ?? '');
$ua = substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 500);
$pdo = $db->getConnection();
$stmt = $pdo->prepare(
"UPDATE turnero_consentimientos
SET estado='firmado', firmado_at=NOW(), firma_svg=?,
ip_firma=?, ua_firma=?, version_formulario=?
WHERE token=?"
);
$stmt->execute([$firmaSvg, $ip, $ua, (int)$tcRow['formulario_id'], $tokenTurnero]);
require_once __DIR__ . '/modules/turnero/api/_helpers.php';
notificarSSE((int)$tcRow['sesion_id']);
echo json_encode(['ok' => true, 'message' => 'Consentimiento firmado correctamente']); exit;
}
// ── GET: construir $envio compatible con la plantilla ─────────────
$envio = [
'id' => (int)$tcRow['id'],
'estado' => $tcRow['estado'],
'form_nombre' => $tcRow['form_nombre'],
'form_descripcion' => $tcRow['form_descripcion'],
'categoria' => $tcRow['categoria'],
'esquema' => $tcRow['esquema'],
'firma_svg' => $tcRow['firma_svg'],
'doc_color' => $tcRow['doc_color'],
'doc_encabezado' => $tcRow['doc_encabezado'],
'doc_subtitulo' => $tcRow['doc_subtitulo'],
'doc_logo_base64' => $tcRow['doc_logo_base64'],
'doc_pie_pagina' => $tcRow['doc_pie_pagina'],
'paciente_nombre' => $tcRow['paciente_nombre'],
'numero_documento' => $tcRow['numero_documento'],
'tipo_documento' => $tcRow['tipo_documento'],
'fecha_nacimiento' => $tcRow['fecha_nacimiento'],
'paciente_telefono' => $tcRow['paciente_telefono'],
'eps' => $tcRow['eps'],
'enviado_por_nombre'=> 'Sistema Turnero',
'enviado_por_email' => null,
'datos_cliente' => '{}',
'datos_prefilled' => json_encode(['__paciente' => [
'nombre_completo' => $tcRow['paciente_nombre'] ?? '',
'numero_documento' => $tcRow['numero_documento'] ?? '',
'tipo_documento' => $tcRow['tipo_documento'] ?? '',
'telefono' => $tcRow['paciente_telefono'] ?? '',
'eps' => $tcRow['eps'] ?? '',
]]),
'created_at' => $tcRow['enviado_at'],
'completado_en' => $tcRow['firmado_at'],
'ip_cliente' => $tcRow['ip_firma'],
'hash_verificacion' => null,
'permite_firma' => 1,
'requiere_firma' => 1,
];
} elseif ($modoPublico) {
// Acceso por token: solo si el formulario ya fue firmado/completado
if (!preg_match('/^[a-f0-9]{64}$/i', $tokenPublico)) {
http_response_code(400); die('Token inválido');
@@ -217,15 +312,30 @@ function esc2(mixed $v): string {
<!-- ── Barra de acciones ──────────────────────────────────────────── -->
<div class="action-bar no-print">
<?php if (!$modoPublico): ?>
<a href="lab_formularios.php#envios"><i class="fas fa-arrow-left me-1"></i>Volver</a>
<?php else: ?>
<span style="font-size:13px;opacity:.85"><i class="fas fa-file-alt me-1"></i>Documento firmado</span>
<?php if ($modoTurnero): ?>
<span style="font-size:13px;opacity:.85">
<i class="fas fa-file-signature me-1"></i>
<?= $envio['estado'] === 'firmado' ? 'Consentimiento firmado ✓' : 'Lea el documento y firme al final' ?>
</span>
<div style="flex:1"></div>
<?php if ($envio['estado'] === 'firmado'): ?>
<button onclick="window.print()" class="btn btn-warning btn-sm fw-semibold">
<i class="fas fa-file-pdf me-2"></i>Imprimir
</button>
<?php endif; ?>
<?php elseif (!$modoPublico): ?>
<a href="lab_formularios.php#envios"><i class="fas fa-arrow-left me-1"></i>Volver</a>
<div style="flex:1"></div>
<button onclick="window.print()" class="btn btn-warning btn-sm fw-semibold">
<i class="fas fa-file-pdf me-2"></i>Descargar / Imprimir PDF
</button>
<?php else: ?>
<span style="font-size:13px;opacity:.85"><i class="fas fa-file-alt me-1"></i>Documento firmado</span>
<div style="flex:1"></div>
<button onclick="window.print()" class="btn btn-warning btn-sm fw-semibold">
<i class="fas fa-file-pdf me-2"></i>Descargar / Imprimir PDF
</button>
<?php endif; ?>
</div>
<!-- ── Documento ─────────────────────────────────────────────────── -->
@@ -466,6 +576,40 @@ function esc2(mixed $v): string {
</div>
<?php endif; ?>
<!-- ── Widget firma turnero (solo modo consentimiento) ───────── -->
<?php if ($modoTurnero && $envio['estado'] !== 'firmado'): ?>
<div class="mt-4 no-print" id="turneroFirmaWidget">
<div class="section-title" style="color:#1565c0">
<i class="fas fa-pen me-1"></i>Firma del paciente / responsable
</div>
<p class="text-muted small mb-2">
He leído y comprendido el contenido de este documento.
Por favor dibuje su firma en el recuadro:
</p>
<canvas id="turneroCv" width="500" height="160"
style="border:2px solid #1565c0;border-radius:8px;background:#f0f4ff;
cursor:crosshair;display:block;max-width:100%;touch-action:none">
</canvas>
<div class="mt-2 d-flex gap-2 flex-wrap">
<button id="turneroLimpiar" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-eraser me-1"></i>Limpiar
</button>
<button id="turneroFirmar" class="btn btn-success fw-semibold">
<i class="fas fa-check-circle me-1"></i>Confirmar y firmar consentimiento
</button>
</div>
<div id="turneroMsg" class="mt-2 small"></div>
</div>
<?php elseif ($modoTurnero && $envio['estado'] === 'firmado'): ?>
<div class="alert alert-success mt-4 d-flex align-items-center gap-2 no-print">
<i class="fas fa-check-circle fs-4"></i>
<div>
<strong>Consentimiento firmado</strong><br>
Puede cerrar esta ventana. El personal de salud ha sido notificado.
</div>
</div>
<?php endif; ?>
</div><!-- /doc-body -->
<div class="doc-footer">
@@ -575,6 +719,91 @@ function esc2(mixed $v): string {
});
});
})();
/* ── Firma de consentimiento turnero ────────────────────────────────── */
(function () {
var widget = document.getElementById('turneroFirmaWidget');
if (!widget) return;
var canvas = document.getElementById('turneroCv');
var ctx = canvas.getContext('2d');
var btnLimp = document.getElementById('turneroLimpiar');
var btnFirm = document.getElementById('turneroFirmar');
var msgEl = document.getElementById('turneroMsg');
var drawing = false;
(function scaleCanvas() {
var ratio = window.devicePixelRatio || 1;
var w = canvas.offsetWidth || canvas.width;
var h = canvas.offsetHeight || canvas.height;
canvas.width = w * ratio;
canvas.height = h * ratio;
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
ctx.scale(ratio, ratio);
ctx.strokeStyle = '#1565c0';
ctx.lineWidth = 2.5;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
})();
function getPos(e) {
var r = canvas.getBoundingClientRect();
var src = e.touches ? e.touches[0] : e;
return { x: src.clientX - r.left, y: src.clientY - r.top };
}
canvas.addEventListener('mousedown', function(e){ drawing=true; ctx.beginPath(); var p=getPos(e); ctx.moveTo(p.x,p.y); });
canvas.addEventListener('mousemove', function(e){ if(!drawing) return; var p=getPos(e); ctx.lineTo(p.x,p.y); ctx.stroke(); });
canvas.addEventListener('mouseup', function(){ drawing=false; });
canvas.addEventListener('mouseleave', function(){ drawing=false; });
canvas.addEventListener('touchstart', function(e){ e.preventDefault(); drawing=true; ctx.beginPath(); var p=getPos(e); ctx.moveTo(p.x,p.y); }, {passive:false});
canvas.addEventListener('touchmove', function(e){ e.preventDefault(); if(!drawing) return; var p=getPos(e); ctx.lineTo(p.x,p.y); ctx.stroke(); }, {passive:false});
canvas.addEventListener('touchend', function(){ drawing=false; });
btnLimp.addEventListener('click', function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
msgEl.textContent = '';
});
btnFirm.addEventListener('click', function() {
var png = canvas.toDataURL('image/png');
if (png.length < 1500) {
msgEl.innerHTML = '<span class="text-danger"><i class="fas fa-exclamation-triangle me-1"></i>Por favor dibuje su firma antes de confirmar.</span>';
return;
}
btnFirm.disabled = true;
btnFirm.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Guardando...';
msgEl.textContent = '';
fetch(window.location.href, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ firma_svg: png })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.ok) {
widget.outerHTML =
'<div class="alert alert-success mt-4 d-flex align-items-center gap-2 no-print">' +
'<i class="fas fa-check-circle fs-4"></i>' +
'<div><strong>Consentimiento firmado correctamente.</strong><br>' +
'Puede cerrar esta ventana.</div></div>';
// Notificar ventana padre si está en iframe/modal
try { window.parent.postMessage({ type: 'turneroFirmado' }, '*'); } catch(e) {}
} else {
msgEl.innerHTML = '<span class="text-danger"><i class="fas fa-times me-1"></i>' + (data.error || 'Error al guardar') + '</span>';
btnFirm.disabled = false;
btnFirm.innerHTML = '<i class="fas fa-check-circle me-1"></i>Confirmar y firmar consentimiento';
}
})
.catch(function() {
msgEl.innerHTML = '<span class="text-danger"><i class="fas fa-times me-1"></i>Error de conexión.</span>';
btnFirm.disabled = false;
btnFirm.innerHTML = '<i class="fas fa-check-circle me-1"></i>Confirmar y firmar consentimiento';
});
});
})();
</script>
</body>
</html>