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, tc.firma_profesional_svg, tc.firmado_profesional_at, tc.datos_respuestas, 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, ts.numero_orden FROM turnero_consentimientos tc JOIN lab_formularios f ON f.id = tc.formulario_id JOIN turnero_turnos t ON t.id = tc.turno_id LEFT JOIN turnero_solicitudes ts ON ts.turno_id = tc.turno_id LEFT JOIN lab_pacientes p ON p.id = COALESCE(ts.paciente_id, t.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) ?? []; // Borrador: guardar campos sin firma (auto-save desde el formulario) // Muestras Prolongadas: marcar completo sin SVG global if (isset($input['mp_completar'])) { $dr = is_array($input['datos_respuestas'] ?? null) ? $input['datos_respuestas'] : []; $existing = json_decode($tcRow['datos_respuestas'] ?? '{}', true) ?: []; $db->getConnection() ->prepare("UPDATE turnero_consentimientos SET estado='firmado', firmado_at=NOW(), datos_respuestas=? WHERE token=?") ->execute([json_encode(array_merge($existing, $dr), JSON_UNESCAPED_UNICODE), $tokenTurnero]); require_once __DIR__ . '/modules/turnero/api/_helpers.php'; notificarSSE((int)$tcRow['sesion_id']); echo json_encode(['ok' => true, 'mp_completado' => true]); exit; } if (!array_key_exists('firma_svg', $input) && isset($input['datos_respuestas']) && is_array($input['datos_respuestas'])) { $existing = json_decode($tcRow['datos_respuestas'] ?? '{}', true) ?: []; $db->getConnection() ->prepare("UPDATE turnero_consentimientos SET datos_respuestas=? WHERE token=? AND estado='pendiente'") ->execute([json_encode(array_merge($existing, $input['datos_respuestas']), JSON_UNESCAPED_UNICODE), $tokenTurnero]); echo json_encode(['ok' => true, 'draft' => true]); exit; } $firmaSvg = trim($input['firma_svg'] ?? ''); if (strlen($firmaSvg) < 100) { echo json_encode(['ok' => false, 'error' => 'Firma requerida']); exit; } $datosRespuestas = isset($input['datos_respuestas']) && is_array($input['datos_respuestas']) ? json_encode($input['datos_respuestas'], JSON_UNESCAPED_UNICODE) : null; $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=?, datos_respuestas=? WHERE token=?" ); $stmt->execute([$firmaSvg, $ip, $ua, (int)$tcRow['formulario_id'], $datosRespuestas, $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'=> $_SESSION['admin_user']['full_name'] ?? $_SESSION['admin_user']['username'] ?? 'Turnero', 'enviado_por_email' => $_SESSION['admin_user']['email'] ?? null, 'firma_profesional_svg' => $tcRow['firma_profesional_svg'] ?? null, 'datos_cliente' => $tcRow['datos_respuestas'] ?: '{}', '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'] ?? date('Y-m-d H:i:s'), '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'); } $envio = $db->fetch( "SELECT e.*, f.nombre AS form_nombre, f.categoria, f.descripcion AS form_descripcion, f.esquema, f.permite_firma, f.requiere_firma, 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, u.full_name AS enviado_por_nombre, u.email AS enviado_por_email, ld.numero_orden FROM lab_form_envios e JOIN lab_formularios f ON f.id = e.formulario_id LEFT JOIN lab_pacientes p ON p.id = e.paciente_id LEFT JOIN admin_users u ON u.id = e.enviado_por LEFT JOIN lab_domicilios ld ON ld.id = e.domicilio_id WHERE e.token = ?", [$tokenPublico] ); if (!$envio) { http_response_code(404); die('Formulario no encontrado'); } if (!in_array($envio['estado'], ['firmado', 'completado'])) { http_response_code(403); die('El formulario aún no ha sido completado.'); } } else { $id = (int)($_GET['id'] ?? 0); if (!$id) { http_response_code(400); die('ID requerido'); } $envio = $db->fetch( "SELECT e.*, f.nombre AS form_nombre, f.categoria, f.descripcion AS form_descripcion, f.esquema, f.permite_firma, f.requiere_firma, 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, u.full_name AS enviado_por_nombre, u.email AS enviado_por_email, ld.numero_orden FROM lab_form_envios e JOIN lab_formularios f ON f.id = e.formulario_id LEFT JOIN lab_pacientes p ON p.id = e.paciente_id LEFT JOIN admin_users u ON u.id = e.enviado_por LEFT JOIN lab_domicilios ld ON ld.id = e.domicilio_id WHERE e.id = ?", [$id] ); if (!$envio) { http_response_code(404); die('Formulario no encontrado'); } // Enfermero solo puede ver sus propios envíos if (isEnfermero()) { $uid = (int)($_SESSION['admin_user']['id'] ?? 0); if ($envio['enviado_por'] != $uid) { http_response_code(403); die('Sin acceso'); } } } // ── Consecutivo de orden (D-... domicilio | F-... turnero) ──────────── $numeroOrden = $modoTurnero ? ($tcRow['numero_orden'] ?? null) : ($envio['numero_orden'] ?? null); // ── Config global del lab ───────────────────────────────────────────── $cfgRows = $db->fetchAll('SELECT clave, valor FROM lab_config WHERE valor != ""'); $cfg = []; foreach ($cfgRows as $r) { $cfg[$r['clave']] = $r['valor']; } // Merge: per-form override > global config $docColor = $envio['doc_color'] ?: ($cfg['doc_color'] ?? '#1565c0'); $docEncabezado = $envio['doc_encabezado'] ?: ($cfg['empresa_nombre'] ?? ''); $docSubtitulo = $envio['doc_subtitulo'] ?: ($cfg['empresa_subtitulo'] ?? ''); $docLogo = $envio['doc_logo_base64'] ?: ($cfg['doc_logo_base64'] ?? ''); $docPiePagina = $envio['doc_pie_pagina'] ?: ($cfg['doc_pie_pagina'] ?? ''); $docDireccion = $cfg['empresa_direccion'] ?? ''; $docTelefono = $cfg['empresa_telefono'] ?? ''; $docEmail = $cfg['empresa_email'] ?? ''; $docCiudad = $cfg['empresa_ciudad'] ?? ''; $esquema = json_decode($envio['esquema'], true) ?? []; $datosCliente = json_decode($envio['datos_cliente'] ?? '{}', true) ?? []; $datosPrefilled = json_decode($envio['datos_prefilled'] ?? '{}', true) ?? []; $todos = array_merge($datosPrefilled, $datosCliente); $modoEditar = $modoTurnero && $envio['estado'] !== 'firmado'; $embebido = isset($_GET['embed']) && $_GET['embed'] === '1'; $compact = $embebido && isset($_GET['compact']); // Firma pre-guardada del profesional logueado (para botón de 1 clic) $firmaProfPreguardada = null; if ($modoTurnero && $embebido && isUserLoggedIn()) { $uid = (int)($_SESSION['admin_user']['id'] ?? 0); if ($uid) { try { $stmt = $db->getConnection()->prepare("SELECT firma_svg FROM admin_users WHERE id = ? LIMIT 1"); $stmt->execute([$uid]); $firmaProfPreguardada = $stmt->fetchColumn() ?: null; } catch (\Throwable $_) {} } } // Pre-scan: formulario que solo requiere firma del profesional (sin firma paciente) $_soloFirmaPro = !empty(array_filter($esquema, fn($c) => ($c['tipo'] ?? '') === 'firma_profesional')) && empty(array_filter($esquema, fn($c) => ($c['tipo'] ?? '') === 'firma')); // Muestras Prolongadas: detectar secciones hora+firma_profesional con intervalos de minutos // Agrupa por prefijo de sección (ej. "Taukit", "Heliprobe") para calcular esperas correctas $_mpMap = []; if ($modoTurnero && $embebido && $_soloFirmaPro) { $_mpSecs = []; $_mpLabel = null; $_mpMin = null; $_mpHora = null; foreach ($esquema as $_c) { $_t = $_c['tipo'] ?? ''; if ($_t === 'separador') { $_mpLabel = $_c['label'] ?? ''; $_mpMin = null; $_mpHora = null; if (preg_match('/[Mm]inuto\s+(\d+)/u', $_mpLabel, $_mx)) $_mpMin = (int)$_mx[1]; } elseif ($_t === 'hora' && $_mpMin !== null) { $_mpHora = $_c['id'] ?? null; } elseif ($_t === 'firma_profesional' && $_mpHora !== null && $_mpMin !== null) { $_prefix = trim(explode('·', $_mpLabel)[0] ?? ''); $_mpSecs[] = ['label' => $_mpLabel, 'min' => $_mpMin, 'hora' => $_mpHora, 'firma' => $_c['id'], 'tipo' => $_prefix]; $_mpHora = null; } } $_mpGroups = []; foreach ($_mpSecs as $_s) { $_mpGroups[$_s['tipo']][] = $_s; } foreach ($_mpGroups as $_tipo => $_secs) { for ($i = 0, $n = count($_secs); $i < $n; $i++) { $_s = $_secs[$i]; $_nx = $_secs[$i + 1] ?? null; $_mpMap[$_s['firma']] = [ 'hora_campo' => $_s['hora'], 'esperar_min' => $_nx ? ($_nx['min'] - $_s['min']) : 0, 'next_label' => $_nx ? $_nx['label'] : null, 'next_firma_id' => $_nx ? $_nx['firma'] : null, 'is_last' => !$_nx, 'exam_type' => $_tipo, ]; } } } // Mapa id → label $labelMap = []; foreach ($esquema as $c) { if (!empty($c['id'])) $labelMap[$c['id']] = $c['label'] ?? $c['id']; } $completado = ($envio['completado_en'] ?? '') ? date('d/m/Y H:i', strtotime($envio['completado_en'])) : '—'; $creado = ($envio['created_at'] ?? '') ? date('d/m/Y H:i', strtotime($envio['created_at'])) : '—'; function esc2(mixed $v): string { if (is_array($v)) return htmlspecialchars(implode(', ', $v), ENT_QUOTES); return htmlspecialchars((string)($v ?? ''), ENT_QUOTES); } ?>
He leído el documento. Dibuje su firma:
He leído y comprendido el contenido de este documento. Por favor dibuje su firma en el recuadro: