Files
whatsapp/ver_formulario_enviado.php
T
2026-03-10 18:42:51 -05:00

401 lines
20 KiB
PHP

<?php
/**
* ver_formulario_enviado.php — Ver respuesta de un formulario (imprimible / PDF)
* Requiere: admin o enfermero autenticado
* URL: ver_formulario_enviado.php?id=ENVIO_ID
*/
require_once 'config/config.php';
if (!isUserLoggedIn()) { header('Location: login.php'); exit; }
$id = (int)($_GET['id'] ?? 0);
if (!$id) { http_response_code(400); die('ID requerido'); }
// ── Cargar datos ──────────────────────────────────────────────────────
$db = Database::getInstance();
$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
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
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'); }
}
// ── 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);
// 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);
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= esc2($envio['form_nombre']) ?> — Respuesta</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
body { font-size: 14px; background: #f4f7fc; }
/* ── Barra de acciones (se oculta al imprimir) ── */
.action-bar {
position: sticky; top: 0; z-index: 100;
background: <?= htmlspecialchars($docColor) ?>; color: #fff;
padding: 10px 20px; display: flex; align-items: center; gap: 12px;
}
.action-bar a { color: rgba(255,255,255,.8); text-decoration: none; font-size: 13px; }
.action-bar a:hover { color: #fff; }
/* ── Documento ──────────────────────────────────── */
.doc-wrap { max-width: 780px; margin: 24px auto; background: #fff;
border-radius: 10px; box-shadow: 0 2px 20px rgba(0,0,0,.12);
overflow: hidden; }
.doc-header { background: <?= htmlspecialchars($docColor) ?>;
color: #fff; padding: 24px 32px 20px; }
.doc-header-inner { display: flex; align-items: flex-start; gap: 16px; }
.doc-header-logo { flex-shrink: 0; }
.doc-header-logo img { max-height: 64px; max-width: 100px; border-radius: 6px;
background: rgba(255,255,255,.15); padding: 4px; }
.doc-header-text { flex: 1; }
.doc-header-text h4 { margin: 0 0 2px; font-size: 15px; font-weight: 700;
opacity: .9; letter-spacing: .01em; }
.doc-header-text .doc-subtitulo { font-size: 12px; opacity: .75; margin-bottom: 4px; }
.doc-header-text .doc-contacto { font-size: 11px; opacity: .7; }
.doc-header-text h3 { margin: 8px 0 0; font-size: 18px; font-weight: 700;
border-top: 1px solid rgba(255,255,255,.3); padding-top: 8px; }
.doc-header-badge { flex-shrink: 0; }
.doc-body { padding: 28px 32px; }
/* ── Sección ────────────────────────────────────── */
.section-title { font-size: 11px; font-weight: 700; text-transform: uppercase;
letter-spacing: .05em; color: #6c757d; border-bottom: 1px solid #dee2e6;
padding-bottom: 6px; margin: 24px 0 14px; }
.section-title:first-child { margin-top: 0; }
/* ── Fila campo ─────────────────────────────────── */
.campo-row { display: flex; gap: 16px; padding: 6px 0;
border-bottom: 1px solid #f0f0f0; }
.campo-row:last-child { border-bottom: none; }
.campo-label { flex: 0 0 38%; font-size: 12px; color: #6c757d; padding-top: 1px; }
.campo-valor { flex: 1; font-size: 13px; font-weight: 600; color: #212529;
word-break: break-word; }
/* ── Separador del esquema ──────────────────────── */
.esquema-sep { font-size: 11px; font-weight: 700; text-transform: uppercase;
letter-spacing: .05em; color: <?= htmlspecialchars($docColor) ?>; margin: 20px 0 8px;
border-bottom: 2px solid <?= htmlspecialchars($docColor) ?>; padding-bottom: 4px; }
/* ── Estado badge ───────────────────────────────── */
.estado-badge { display: inline-block; padding: 3px 10px; border-radius: 20px;
font-size: 11px; font-weight: 700; text-transform: uppercase; }
.estado-firmado { background: #d1fae5; color: #065f46; }
.estado-completado { background: #dbeafe; color: #1e40af; }
.estado-pendiente { background: #fef3c7; color: #92400e; }
/* ── Firma ──────────────────────────────────────── */
.firma-box { background: #f8faff; border: 1px solid #c9d8ff;
border-radius: 8px; padding: 14px; display: inline-block; margin-top: 8px; }
.firma-box img { max-height: 140px; max-width: 340px; display: block; }
/* ── Footer del doc ─────────────────────────────── */
.doc-footer { background: #f8faff; border-top: 1px solid #e9ecef;
padding: 14px 32px; font-size: 11px; color: #6c757d;
display: flex; justify-content: space-between; flex-wrap: wrap; gap: 6px; }
.hash-short { font-family: monospace; opacity: .7; }
/* ── Sello SHA-256 ──────────────────────────────── */
.hash-seal { border: 1px solid #c3d3f7; border-radius: 8px; overflow: hidden; }
.hash-seal-header { background: <?= htmlspecialchars($docColor) ?>; color: #fff; padding: 8px 14px;
font-size: 11px; font-weight: 700; text-transform: uppercase;
letter-spacing: .05em; }
.hash-seal-body { background: #f0f5ff; padding: 12px 14px; }
.hash-label { font-size: 11px; color: #6c757d; margin-bottom: 4px; }
.hash-value { display: block; font-family: monospace; font-size: 11px;
color: #1e3a6e; word-break: break-all; background: #fff;
border: 1px solid #d0dcf7; border-radius: 4px; padding: 6px 8px; }
.hash-hint { font-size: 11px; color: #6c757d; }
.hash-hint a { color: #1565c0; word-break: break-all; }
@media print {
.hash-seal { border-color: #000; }
.hash-seal-header { background: #000 !important; -webkit-print-color-adjust:exact; print-color-adjust:exact; }
}
/* ═══════ ESTILOS DE IMPRESIÓN ═══════════════════ */
@media print {
body { background: #fff !important; font-size: 12px; }
.action-bar { display: none !important; }
.doc-wrap { margin: 0; border-radius: 0; box-shadow: none; max-width: 100%; }
.doc-header { -webkit-print-color-adjust: exact; print-color-adjust: exact; padding: 18px 22px; }
.doc-body { padding: 18px 22px; }
.doc-footer { padding: 10px 22px; }
@page { margin: 1cm; }
}
</style>
</head>
<body>
<!-- ── Barra de acciones ──────────────────────────────────────────── -->
<div class="action-bar no-print">
<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>
</div>
<!-- ── Documento ─────────────────────────────────────────────────── -->
<div class="doc-wrap">
<!-- Header -->
<div class="doc-header">
<div class="doc-header-inner">
<?php if ($docLogo): ?>
<div class="doc-header-logo">
<img src="<?= htmlspecialchars($docLogo) ?>" alt="Logo">
</div>
<?php endif; ?>
<div class="doc-header-text">
<?php if ($docEncabezado): ?>
<h4><?= esc2($docEncabezado) ?></h4>
<?php endif; ?>
<?php if ($docSubtitulo): ?>
<div class="doc-subtitulo"><?= esc2($docSubtitulo) ?></div>
<?php endif; ?>
<?php
$contacto = array_filter([$docDireccion, $docCiudad, $docTelefono, $docEmail]);
if ($contacto): ?>
<div class="doc-contacto"><?= esc2(implode(' · ', $contacto)) ?></div>
<?php endif; ?>
<h3><?= esc2($envio['form_nombre']) ?></h3>
<?php if ($envio['form_descripcion']): ?>
<div style="font-size:12px;opacity:.8;margin-top:4px"><?= esc2($envio['form_descripcion']) ?></div>
<?php endif; ?>
</div>
<div class="doc-header-badge" style="text-align:right">
<?php
$eBadge = match($envio['estado']) {
'firmado' => 'estado-firmado',
'completado' => 'estado-completado',
default => 'estado-pendiente',
};
?>
<span class="estado-badge <?= $eBadge ?>"><?= esc2($envio['estado']) ?></span>
<div style="font-size:10px;opacity:.7;margin-top:4px"><?= esc2($envio['categoria'] ?? '') ?></div>
</div>
</div>
</div>
<div class="doc-body">
<!-- Info del paciente -->
<?php if ($envio['paciente_nombre']): ?>
<div class="section-title"><i class="fas fa-user me-1"></i>Datos del paciente</div>
<div class="campo-row">
<div class="campo-label">Nombre completo</div>
<div class="campo-valor"><?= esc2($envio['paciente_nombre']) ?></div>
</div>
<?php if ($envio['numero_documento']): ?>
<div class="campo-row">
<div class="campo-label"><?= esc2($envio['tipo_documento'] ?? 'Documento') ?></div>
<div class="campo-valor"><?= esc2($envio['numero_documento']) ?></div>
</div>
<?php endif; ?>
<?php if ($envio['fecha_nacimiento']): ?>
<div class="campo-row">
<div class="campo-label">Fecha de nacimiento</div>
<div class="campo-valor"><?= esc2(date('d/m/Y', strtotime($envio['fecha_nacimiento']))) ?></div>
</div>
<?php endif; ?>
<?php if ($envio['paciente_telefono']): ?>
<div class="campo-row">
<div class="campo-label">Teléfono</div>
<div class="campo-valor"><?= esc2($envio['paciente_telefono']) ?></div>
</div>
<?php endif; ?>
<?php if ($envio['eps']): ?>
<div class="campo-row">
<div class="campo-label">EPS / Aseguradora</div>
<div class="campo-valor"><?= esc2($envio['eps']) ?></div>
</div>
<?php endif; ?>
<?php endif; ?>
<!-- Respuestas del formulario (en orden del esquema) -->
<div class="section-title"><i class="fas fa-wpforms me-1"></i>Respuestas del formulario</div>
<?php
$paciente = $datosPrefilled['__paciente'] ?? [];
foreach ($esquema as $campo):
$tipo = $campo['tipo'] ?? '';
if ($tipo === 'separador'): ?>
<div class="esquema-sep"><?= esc2($campo['label'] ?? '') ?></div>
<?php continue; endif;
if ($tipo === 'firma') continue;
// ── Parrafo estático ──────────────────────────────────
if ($tipo === 'parrafo'):
$ws = !empty($campo['flujoLibre']) ? 'normal' : 'pre-wrap';
?>
<div class="mb-3" style="font-size:.88rem;line-height:1.75;color:#444;text-align:justify;white-space:<?= $ws ?>"><?= htmlspecialchars($campo['contenido'] ?? '', ENT_QUOTES) ?></div>
<?php continue; endif;
// ── Parrafo inline (texto con marcadores {key}) ───────
if ($tipo === 'parrafo_inline'):
$contenido = $campo['contenido'] ?? '';
// Sustituir {key} con valor del paciente o de $todos
$rendered = preg_replace_callback('/\{([a-z_]+)\}/', function($m) use ($paciente, $todos) {
return $paciente[$m[1]] ?? $todos[$m[1]] ?? $m[0];
}, $contenido);
// Convertir saltos de línea a <br>
$rendered = nl2br(htmlspecialchars($rendered, ENT_QUOTES));
?>
<div class="mb-3" style="font-size:.88rem;line-height:2;color:#444;text-align:justify"><?= $rendered ?></div>
<?php continue; endif;
$cid = $campo['id'] ?? null;
if (!$cid) continue;
// ── Linked: valor viene del paciente prefilled ────────
if ($tipo === 'linked') {
$lk = $campo['linked_key'] ?? '';
$valor = $paciente[$lk] ?? $todos[$cid] ?? null;
} else {
$valor = $todos[$cid] ?? null;
}
if ($valor === null || $valor === '') continue;
$display = is_array($valor) ? implode(', ', $valor) : (string)$valor;
?>
<div class="campo-row">
<div class="campo-label"><?= esc2($campo['label'] ?? $cid) ?></div>
<div class="campo-valor"><?= esc2($display) ?></div>
</div>
<?php endforeach; ?>
<!-- Firma digital (dibujo) -->
<?php if ($envio['firma_svg']): ?>
<div class="section-title mt-4"><i class="fas fa-signature me-1"></i>Firma digital (dibujo)</div>
<div class="firma-box">
<img src="<?= htmlspecialchars($envio['firma_svg']) ?>" alt="Firma digital">
</div>
<?php endif; ?>
<!-- Firma foto -->
<?php if (!empty($datosCliente['__firma_foto'])): ?>
<div class="section-title mt-4"><i class="fas fa-camera me-1"></i>Foto de firma / documento</div>
<div class="firma-box">
<img src="<?= htmlspecialchars($datosCliente['__firma_foto']) ?>" alt="Foto de firma">
</div>
<?php endif; ?>
<!-- Info del envío -->
<div class="section-title mt-4"><i class="fas fa-clock me-1"></i>Información del envío</div>
<div class="campo-row">
<div class="campo-label">Enviado por</div>
<div class="campo-valor"><?= esc2($envio['enviado_por_nombre'] ?? '—') ?></div>
</div>
<div class="campo-row">
<div class="campo-label">Fecha de envío</div>
<div class="campo-valor"><?= esc2($creado) ?></div>
</div>
<?php if ($envio['completado_en']): ?>
<div class="campo-row">
<div class="campo-label">Completado el</div>
<div class="campo-valor"><?= esc2($completado) ?></div>
</div>
<?php endif; ?>
<?php if ($envio['ip_cliente']): ?>
<div class="campo-row">
<div class="campo-label">IP del cliente</div>
<div class="campo-valor text-muted small fw-normal"><?= esc2($envio['ip_cliente']) ?></div>
</div>
<?php endif; ?>
<?php if ($envio['hash_verificacion']): ?>
<!-- Sello de integridad SHA-256 -->
<div class="hash-seal mt-4">
<div class="hash-seal-header">
<i class="fas fa-shield-alt me-1"></i>
Sello de integridad del documento
</div>
<div class="hash-seal-body">
<div class="hash-label">Hash SHA-256 de verificación:</div>
<code class="hash-value"><?= esc2($envio['hash_verificacion']) ?></code>
<div class="hash-hint mt-2">
Verifique la autenticidad en:
<a href="verificar_formulario.php?h=<?= urlencode($envio['hash_verificacion']) ?>" target="_blank">
<?php
$protocol2 = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS']!=='off') ? 'https' : 'http';
echo $protocol2.'://'.$_SERVER['HTTP_HOST'].'/verificar_formulario.php?h='.urlencode($envio['hash_verificacion']);
?>
</a>
</div>
</div>
</div>
<?php endif; ?>
</div><!-- /doc-body -->
<div class="doc-footer">
<span>
<?php if ($docPiePagina): ?>
<?= esc2($docPiePagina) ?> &nbsp;&bull;&nbsp;
<?php endif; ?>
<i class="fas fa-shield-alt me-1"></i>Generado el <?= date('d/m/Y H:i') ?> &nbsp;&bull;&nbsp; ID #<?= $envio['id'] ?>
</span>
<?php if ($envio['hash_verificacion']): ?>
<span class="hash-short" title="Hash SHA-256"><?= substr($envio['hash_verificacion'],0,16) ?>...</span>
<?php endif; ?>
</div>
</div><!-- /doc-wrap -->
</body>
</html>