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, 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, p.direccion, p.genero, t.sesion_id, ts.numero_orden, med.nombres AS medico_nombres, med.apellidos AS medico_apellidos, med.cod_especialidad AS medico_especialidad, med.codigo AS medico_codigo, med.docidmedico AS medico_docid, cu.full_name AS creador_nombre 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) LEFT JOIN medicos med ON med.id = ts.medico_id LEFT JOIN admin_users cu ON cu.id = tc.creado_por 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 (in_array($tcRow['estado'], ['firmado', 'cerrado_anticipado'])) { echo json_encode(['ok' => false, 'error' => 'Este consentimiento ya fue finalizado']); exit; } $input = json_decode(file_get_contents('php://input'), true) ?? []; // Cierre anticipado de toma progresiva con justificación if (isset($input['mp_cerrar_anticipado'])) { $motivo = trim($input['motivo'] ?? ''); if (!$motivo) { echo json_encode(['ok' => false, 'error' => 'Motivo requerido']); exit; } $existing = json_decode($tcRow['datos_respuestas'] ?? '{}', true) ?: []; $existing['_cierre_anticipado'] = ['motivo' => $motivo, 'cerrado_at' => date('Y-m-d H:i:s')]; $db->getConnection() ->prepare("UPDATE turnero_consentimientos SET estado='cerrado_anticipado', firmado_at=NOW(), datos_respuestas=? WHERE token=?") ->execute([json_encode($existing, JSON_UNESCAPED_UNICODE), $tokenTurnero]); require_once __DIR__ . '/modules/turnero/api/_helpers.php'; notificarSSE((int)$tcRow['sesion_id']); echo json_encode(['ok' => true, 'mp_cerrado' => true]); exit; } // 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) ?: []; $allowSave = $tcRow['estado'] === 'pendiente' || ($modoTurnero && in_array($tcRow['estado'], ['en_progreso', 'enviado'])); if ($allowSave) { $db->getConnection() ->prepare("UPDATE turnero_consentimientos SET datos_respuestas=? WHERE token=?") ->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'=> $tcRow['creador_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'] ?? '', 'fecha_nacimiento' => $tcRow['fecha_nacimiento'] ?? '', 'telefono' => $tcRow['paciente_telefono'] ?? '', 'eps' => $tcRow['eps'] ?? '', 'direccion' => $tcRow['direccion'] ?? '', 'genero' => $tcRow['genero'] ?? '', 'medico_nombre' => trim(($tcRow['medico_nombres'] ?? '') . ' ' . ($tcRow['medico_apellidos'] ?? '')), 'medico_especialidad'=> $tcRow['medico_especialidad'] ?? '', 'medico_codigo' => $tcRow['medico_codigo'] ?? '', 'medico_docid' => $tcRow['medico_docid'] ?? '', ]]), '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, p.direccion, p.genero, 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, p.direccion, p.genero, 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 && !in_array($envio['estado'], ['firmado', 'cerrado_anticipado']); $embebido = isset($_GET['embed']) && $_GET['embed'] === '1'; $compact = $embebido && isset($_GET['compact']); $autoprint = isset($_GET['autoprint']) && $_GET['autoprint'] === '1'; // Firma pre-guardada del profesional logueado (para botón de 1 clic) // Aplica tanto al turnero embebido como a la firma directa fuera del turnero // (staff logueado viendo/firmando un formulario por ?id=), para que el // nombre y cédula del firmante salgan en cualquier formato. $firmaProfPreguardada = null; $profNombre = null; $profDocumento = null; if ((($modoTurnero && $embebido) || (!$modoTurnero && !$modoPublico)) && isUserLoggedIn()) { $uid = (int)($_SESSION['admin_user']['id'] ?? 0); if ($uid) { try { $stmt = $db->getConnection()->prepare( "SELECT au.firma_svg, au.full_name, au.cedula AS au_cedula, le.nombre_completo AS pro_nombre, le.numero_documento AS pro_documento FROM admin_users au LEFT JOIN lab_enfermeras le ON le.id = au.enfermera_id WHERE au.id = ? LIMIT 1" ); $stmt->execute([$uid]); $pRow = $stmt->fetch(PDO::FETCH_ASSOC) ?: []; $firmaProfPreguardada = $pRow['firma_svg'] ?: null; $profNombre = $pRow['pro_nombre'] ?: ($pRow['full_name'] ?: null); $profDocumento = $pRow['au_cedula'] ?: ($pRow['pro_documento'] ?: null); } catch (\Throwable $_) {} } } // Formulario marcado explícitamente como solo-profesional (sin firma de ningún tipo) $_formSoloPro = $modoTurnero && $embebido && !empty($tcRow['solo_profesional']); // Pre-scan: formulario que solo requiere firma del profesional (sin firma paciente) $_soloFirmaPro = ($modoTurnero && !empty($tcRow['es_toma_progresiva'])) || (!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 condición del separador para no mezclar Glicemia y Cortisol (ambas usan prefijo "Toma"). $_mpMap = []; $_mpGroupsFull = []; // grupos sin filtrar (para la pantalla de configuración) $_mpSkippedSepIds = []; // separadores de tomas no seleccionadas (se ocultarán en render) $_tomasConfig = null; // configuración guardada por paciente ({grupo:[firmaId,...]}) if ($modoTurnero && $embebido && $_soloFirmaPro) { $_mpSecs = []; $_mpLabel = null; $_mpMin = null; $_mpHoraFija = null; $_mpHora = null; $_mpCondKey = null; $_mpCondDisplay = null; $_mpSepId = null; foreach ($esquema as $_c) { $_t = $_c['tipo'] ?? ''; if ($_t === 'separador') { $_mpLabel = $_c['label'] ?? ''; $_mpMin = null; $_mpHoraFija = null; $_mpHora = null; $_mpSepId = $_c['id'] ?? null; $_mpCond = $_c['condicion'] ?? null; // Clave de agrupación: usa valores de condición para distinguir Glicemia vs Cortisol if ($_mpCond) { $_cv = $_mpCond['valores'] ?? ($_mpCond['valor'] ? [$_mpCond['valor']] : []); $_mpCondKey = implode('|', $_cv); $_mpCondDisplay = trim(explode('·', $_mpLabel)[0] ?? ''); } else { $_mpCondKey = $_mpCondDisplay = trim(explode('·', $_mpLabel)[0] ?? ''); } if (preg_match('/[Mm]inuto\s+(\d+)/u', $_mpLabel, $_mx)) { $_mpMin = (int)$_mx[1]; } elseif (preg_match('/(\d{1,2}):(\d{2})\s*(a\.?m\.?|p\.?m\.?)/i', $_mpLabel, $_mx)) { $_h = (int)$_mx[1]; $_m2 = (int)$_mx[2]; $_pm = strtolower(preg_replace('/[^apm]/i', '', $_mx[3])) === 'pm'; if ($_pm && $_h < 12) $_h += 12; $_mpHoraFija = sprintf('%02d:%02d', $_h, $_m2); $_mpMin = PHP_INT_MAX; // sentinel: va después de todos los Minuto X } elseif (stripos($_mpLabel, 'Otro') !== false) { // Si el usuario ingresó la hora de "Otro", usarla para ordenar cronológicamente $_otroValor = trim((string)($datosCliente['_otro_hora'] ?? '')); $_mpMin = is_numeric($_otroValor) ? (int)$_otroValor : PHP_INT_MAX; } } elseif ($_t === 'hora' && ($_mpMin !== null || $_mpHoraFija !== null)) { $_mpHora = $_c['id'] ?? null; } elseif ($_t === 'firma_profesional' && $_mpHora !== null && ($_mpMin !== null || $_mpHoraFija !== null)) { $_mpSecs[] = [ 'label' => $_mpLabel, 'sep_id' => $_mpSepId, 'min' => $_mpMin ?? PHP_INT_MAX, 'hora_fija' => $_mpHoraFija, 'hora' => $_mpHora, 'firma' => $_c['id'], 'tipo' => $_mpCondKey, 'display' => $_mpCondDisplay, ]; $_mpHora = null; } } // Ordenar por tiempo antes de agrupar para garantizar orden cronológico usort($_mpSecs, fn($a, $b) => $a['min'] <=> $b['min']); // Mapa sep_id → minuto para re-ordenar secciones en el render $_mpSepOrder = []; foreach ($_mpSecs as $_s) { $_mpSepOrder[$_s['sep_id']] = $_s['min']; } $_mpGroups = []; foreach ($_mpSecs as $_s) { $_mpGroups[$_s['tipo']][] = $_s; } $_mpGroupsFull = $_mpGroups; // Aplicar filtro de ciclos si el profesional ya configuró las tomas para este paciente $__rawCfg = $datosCliente['_tomas_config'] ?? null; if (is_array($__rawCfg)) { $_tomasConfig = $__rawCfg; // exported to JS as-is (nested format) // Flatten nested { examName: { groupKey: [fids] } } → { groupKey: [fids] } for rendering $__first = reset($__rawCfg); $__isNested = is_array($__first) && !empty($__first) && is_array(reset($__first)); $__flatCfg = []; if ($__isNested) { foreach ($__rawCfg as $__examCfg) { if (!is_array($__examCfg)) continue; foreach ($__examCfg as $__gk => $__fids) { foreach ($__fids as $__fid) $__flatCfg[$__gk][] = $__fid; } } foreach ($__flatCfg as &$__v) $__v = array_values(array_unique($__v)); unset($__v); } else { $__flatCfg = $__rawCfg; } foreach ($_mpGroups as $__gk => &$__gsecs) { if (!isset($__flatCfg[$__gk])) { foreach ($__gsecs as $__s) { $_mpSkippedSepIds[$__s['sep_id']] = true; } $__gsecs = []; continue; } $__allowed = array_flip($__flatCfg[$__gk]); foreach ($__gsecs as $__s) { if (!isset($__allowed[$__s['firma']])) { $_mpSkippedSepIds[$__s['sep_id']] = true; } } $__gsecs = array_values(array_filter($__gsecs, fn($s) => isset($__allowed[$s['firma']]))); } unset($__gsecs); } // Construir $_mpMap desde grupos (posiblemente filtrados) 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']] = [ 'label' => $_s['label'], 'hora_campo' => $_s['hora'], 'next_label' => $_nx ? $_nx['label'] : null, 'next_firma_id' => $_nx ? $_nx['firma'] : null, 'is_last' => !$_nx, 'exam_type' => $_s['display'], ]; } } } // Primera firma pendiente en toma progresiva (las demás se ocultan hasta su turno) $_mpPrimeraPendiente = null; if (!empty($_mpMap)) { foreach ($_mpMap as $_fid => $_) { if (empty($datosCliente[$_fid]) && empty($datosCliente[$_fid . '_svg'])) { $_mpPrimeraPendiente = $_fid; break; } } } // Mapa firma_id → obs_campo_id (campo texto/textarea que acompaña cada firma) $_mpFirmaObs = []; if (!empty($esquema) && !empty($_mpMap)) { $_prevFirma = null; $_pendingObs = null; foreach ($esquema as $_oc) { $_ot = $_oc['tipo'] ?? ''; if ($_ot === 'separador') { $_prevFirma = null; $_pendingObs = null; } elseif (($_ot === 'texto' || $_ot === 'textarea') && isset($_oc['id'])) { if ($_prevFirma !== null) { $_mpFirmaObs[$_prevFirma] = $_oc['id']; $_prevFirma = null; } else { $_pendingObs = $_oc['id']; } } elseif ($_ot === 'firma_profesional' && isset($_oc['id']) && isset($_mpMap[$_oc['id']])) { if ($_pendingObs !== null) { $_mpFirmaObs[$_oc['id']] = $_pendingObs; } $_prevFirma = $_oc['id']; $_pendingObs = null; } elseif (!in_array($_ot, ['hora'])) { $_prevFirma = null; } } foreach ($_mpFirmaObs as $_fid => $_obsId) { if (isset($_mpMap[$_fid])) $_mpMap[$_fid]['obs_campo'] = $_obsId; } } // Horas de tomas ya firmadas → read-only en edición (no se pueden corregir si ya se firmó esa toma) $_signedHoraCampos = []; foreach ($_mpMap as $_fid => $_mpe) { if ((!empty($datosCliente[$_fid]) && strlen($datosCliente[$_fid]) > 10) || (!empty($datosCliente[$_fid . '_svg']) && strlen($datosCliente[$_fid . '_svg']) > 10)) { if (!empty($_mpe['hora_campo'])) $_signedHoraCampos[$_mpe['hora_campo']] = true; } } $_hcToFirmaIds = []; foreach ($_mpMap as $_fid => $_mpe) { if (!empty($_mpe['hora_campo'])) $_hcToFirmaIds[$_mpe['hora_campo']][] = $_fid; } // siguiente_toma_at para recuperar countdown si el modal fue cerrado y reabierto $_mpSiguienteTomAt = null; if ($modoTurnero && $embebido && !empty($_mpMap) && $_mpPrimeraPendiente !== null) { $raw = $tcRow['siguiente_toma_at'] ?? null; if ($raw && $raw > date('Y-m-d H:i:s')) { $_mpSiguienteTomAt = $raw; } } // 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); } function _addExamWizardHtml(string $cid): string { return '
Nuevo tipo de examen
1
'; } ?> <?= esc2($envio['form_nombre']) ?> — Respuesta >
Abrir en navegador Volver
Documento firmado

'estado-firmado', 'completado' => 'estado-completado', default => 'estado-pendiente', }; ?>
Configurar protocolo de tomas

Marque las tomas que va a realizar.

($c['tipo'] ?? '') === 'linked')); ?>
Datos del paciente
Nombre completo
Fecha de nacimiento
Teléfono
EPS / Aseguradora
$envio['paciente_nombre'] ?? null, 'numero_documento' => $envio['numero_documento'] ?? null, 'tipo_documento' => $envio['tipo_documento'] ?? null, 'fecha_nacimiento' => $envio['fecha_nacimiento'] ?? null, 'telefono' => $envio['paciente_telefono'] ?? null, 'eps' => $envio['eps'] ?? null, 'direccion' => $envio['direccion'] ?? null, 'genero' => $envio['genero'] ?? null, ], fn($v) => $v !== null && $v !== ''); // Firma global del profesional (un solo valor para todos los campos firma_profesional) $firmaSharedProfesional = $envio['firma_profesional_svg'] ?? null; // Firma global del paciente: fallback solo para registros sin datos por-campo (_svg). $firmaSharedPaciente = $envio['firma_svg'] ?? null; // Si datos_respuestas ya tiene algún campo _svg, no usar firma_svg global como fallback // (evita mostrar la firma del disentimiento en el campo de consentimiento y viceversa). $_hayDatosPerCampoSvg = !empty(array_filter(array_keys($datosCliente ?? []), fn($k) => str_ends_with($k, '_svg'))); // Rastrear si ya se renderizó la firma inline (para no duplicarla al pie) $_lnkBtnPuesto = false; // el lápiz de corregir va una sola vez $_renderedFirmaProfesional = false; $_renderedFirmaPaciente = false; $_esquemaTieneFirmaPaciente = false; $_esquemaTieneFirmaAlguna = false; // true si hay cualquier campo firma/firma_profesional $_firmaGlobalPacienteUsada = false; // la firma global solo va al primer campo firma if ($compact): ?>
false, 'min' => null, 'campos' => []]; foreach ($esquema as $__c) { if (($__c['tipo'] ?? '') === 'separador') { if (!empty($__cur['campos'])) $__secs[] = $__cur; $__it = isset($_mpSepOrder[$__c['id'] ?? '']); $__cur = ['is_toma' => $__it, 'min' => $__it ? $_mpSepOrder[$__c['id']] : null, 'campos' => [$__c]]; } else { $__cur['campos'][] = $__c; } } if (!empty($__cur['campos'])) $__secs[] = $__cur; $__tomaIdxs = []; $__tomaList = []; foreach ($__secs as $i => $s) { if ($s['is_toma']) { $__tomaIdxs[] = $i; $__tomaList[] = $s; } } usort($__tomaList, fn($a, $b) => $a['min'] <=> $b['min']); foreach ($__tomaIdxs as $j => $i) { $__secs[$i] = $__tomaList[$j]; } $esquema = array_merge(...array_column($__secs, 'campos')); } $_saltarSeccion = false; $_mpSkipSec = false; // toma excluida por _tomas_config; siempre ocultar sin importar modoEditar // Atributos heredados por los campos de la sección actual. Sin esto, una // sección cuya condición no se cumple ocultaba solo su encabezado y dejaba // sus campos (hora, observaciones, firma) sueltos y visibles. $_secAttr = ''; foreach ($esquema as $campo): $tipo = $campo['tipo'] ?? ''; if ($tipo === 'separador'): $_mpSkipSec = false; $cond = $campo['condicion'] ?? null; if ($cond) { $vCtrl = $todos[$cond['campo_id']] ?? null; // La condición puede depender de un campo vinculado (p. ej. el // sexo del paciente, que gobierna la sección obstétrica). Esos // no están en las respuestas: su valor sale de la ficha. if ($vCtrl === null) { foreach ($esquema as $__c) { if (($__c['id'] ?? '') === $cond['campo_id'] && ($__c['tipo'] ?? '') === 'linked') { $vCtrl = $paciente[$__c['linked_key'] ?? ''] ?? null; break; } } } $vCond = $cond['valores'] ?? ($cond['valor'] ? [$cond['valor']] : []); $_saltarSeccion = is_array($vCtrl) ? empty(array_intersect($vCond, $vCtrl)) : !in_array($vCtrl, $vCond, true); } else { $_saltarSeccion = false; } // Ocultar secciones de tomas excluidas por la configuración de ciclos del paciente if (!$_saltarSeccion && isset($_mpSkippedSepIds[$campo['id'] ?? ''])) { $_saltarSeccion = true; $_mpSkipSec = true; } // La sección queda en el DOM (el JS puede mostrarla si cambian de examen) // pero oculta, y sus campos heredan el mismo estado. $_secAttr = ' data-sep-id="' . esc2($campo['id'] ?? '') . '"' . (($_saltarSeccion && $modoEditar) ? ' style="display:none"' : ''); if (($_saltarSeccion && !$modoEditar) || $_mpSkipSec) continue; ?>
>
>
>
<?= $fLabel ?>

C.C.
Foto - <?= $fLabel ?>

He leído el documento. Dibuje su firma:

>
Pendiente de firma del profesional.
>
$rendered = nl2br(htmlspecialchars($rendered, ENT_QUOTES)); ?>
>
$_SESSION['admin_user']['full_name'] ?? '', 'cargo' => $_SESSION['admin_user']['cargo'] ?? '', default => '' }; } $label = esc2($campo['label'] ?? $cid); $req = !empty($campo['required']) ? ' required' : ''; if ($tipo === 'linked'): $lk = $campo['linked_key'] ?? ''; $lval = $paciente[$lk] ?? $todos[$cid] ?? ''; if ($lk === 'fecha_nacimiento' && $lval && preg_match('/^\d{4}-\d{2}-\d{2}/', $lval)) { $lval = date('d/m/Y', strtotime($lval)); } if ($lval !== ''): ?>
>
>
>
>
>
>
diff($bday)->y; } } // texto, numero, fecha, hora, y calculados → input $inputType = match($tipo) { 'numero', 'edad' => 'number', 'fecha', 'fecha_hoy' => 'date', 'hora' => 'time', default => 'text' }; // texto libre ocupa fila completa; tipos cortos caben 3 por fila $inputFullClass = ($compact && $tipo === 'texto') ? ' campo-full' : ''; $extraClase = htmlspecialchars($campo['clase'] ?? '', ENT_QUOTES); ?>
>
> >
diff(new DateTime($fnac))->y; } } } else { $valor = $todos[$cid] ?? null; } if ($valor === null || $valor === '') continue; $display = is_array($valor) ? implode(', ', $valor) : (string)$valor; ?>
Firma digital (dibujo)
Firma digital
Foto de firma / documento
Foto de firma
Información del envío
Enviado por
Fecha de envío
Completado el
IP del cliente
Sello de integridad del documento
Hash SHA-256 de verificación:
Verifique la autenticidad en:
Firma del paciente / responsable

He leído y comprendido el contenido de este documento. Por favor dibuje su firma en el recuadro:

Toma cerrada anticipadamente
Consentimiento firmado
El personal de salud ha sido notificado.