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
+325
View File
@@ -0,0 +1,325 @@
<?php
/**
* modules/registro_exams/views/etiqueta.php
* Vista de impresión A6 (148 × 105 mm) para etiqueta de muestra.
* URL: /erp.php?m=registro_exams&v=etiqueta&id=N
*
* - Se abre en nueva pestaña y lanza window.print() automáticamente.
* - Sin sidebar; página mínima optimizada para impresión.
*/
require_once APP_ROOT . '/config/config.php';
$ordenId = isset($_GET['id']) ? (int) $_GET['id'] : 0;
if ($ordenId <= 0) {
http_response_code(400);
die('Orden no especificada.');
}
// Cargar datos de la orden directamente (sin AJAX, para que el HTML sea self-contained)
try {
$pdo = Database::getInstance()->getConnection();
$stmt = $pdo->prepare('
SELECT
o.id, o.codigo, o.prioridad, o.estado,
o.requiere_ayuno, o.horas_ayuno,
o.medico_nombre, o.diagnostico, o.creado_at,
p.nombre_completo, p.numero_documento, p.tipo_documento,
p.fecha_nacimiento, p.eps
FROM exam_ordenes o
JOIN lab_pacientes p ON p.id = o.paciente_id
WHERE o.id = ?
');
$stmt->execute([$ordenId]);
$orden = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$orden) {
http_response_code(404);
die('Orden no encontrada.');
}
// Ítems
$stmt = $pdo->prepare('
SELECT ei.estado, et.nombre AS tipo_nombre, et.codigo AS tipo_codigo,
et.requiere_ayuno, et.horas_ayuno
FROM exam_items ei
JOIN exam_tipos et ON et.id = ei.exam_tipo_id
WHERE ei.orden_id = ?
ORDER BY et.nombre
');
$stmt->execute([$ordenId]);
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Edad
$edad = null;
if ($orden['fecha_nacimiento']) {
$edad = (new DateTime($orden['fecha_nacimiento']))->diff(new DateTime())->y;
}
// Ayuno requerido (por la orden o por algún ítem)
$ayunoReq = (bool) $orden['requiere_ayuno'];
foreach ($items as $it) {
if ($it['requiere_ayuno']) { $ayunoReq = true; break; }
}
$horasValues = array_filter(array_column($items, 'horas_ayuno'));
$horasAyuno = $orden['horas_ayuno'] ?? ($horasValues ? max($horasValues) : 0);
} catch (\Throwable $e) {
http_response_code(500);
die('Error al cargar datos: ' . htmlspecialchars($e->getMessage()));
}
$fecha = $orden['creado_at']
? (new DateTime($orden['creado_at']))->format('d/m/Y H:i')
: date('d/m/Y');
// Generar código de barras como CSS barcode (sin librería externa)
// Usaremos simplemente el código textual + formato visual
$codigoBarras = $orden['codigo'];
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Etiqueta <?= htmlspecialchars($codigoBarras) ?></title>
<style>
/* ── Reset e impresión A6 ── */
* { box-sizing: border-box; margin: 0; padding: 0; }
@page {
size: A6 landscape; /* 148mm × 105mm en horizontal */
margin: 4mm;
}
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 9pt;
color: #000;
background: #fff;
width: 140mm;
}
/* Pantalla previa */
@media screen {
body {
width: 148mm;
min-height: 105mm;
margin: 10mm auto;
border: 1px dashed #ccc;
padding: 4mm;
background: #fff;
}
.no-print { display: block; }
}
@media print {
.no-print { display: none !important; }
}
/* ── Layout etiqueta ── */
.etiqueta {
width: 100%;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
border-bottom: 1.5pt solid #000;
padding-bottom: 2mm;
margin-bottom: 2mm;
}
.lab-nombre {
font-size: 10pt;
font-weight: bold;
text-transform: uppercase;
}
.codigo {
font-size: 13pt;
font-weight: bold;
letter-spacing: 1px;
text-align: right;
}
.barcode {
font-family: 'Libre Barcode 128', monospace;
font-size: 26pt;
letter-spacing: 0;
line-height: 1;
text-align: right;
}
.paciente-nombre {
font-size: 11pt;
font-weight: bold;
line-height: 1.2;
}
.meta-row {
display: flex;
gap: 6mm;
margin-top: 1mm;
font-size: 8.5pt;
}
.meta-item { white-space: nowrap; }
.meta-label { color: #555; font-size: 7.5pt; display: block; }
.examenes-titulo {
font-weight: bold;
font-size: 8pt;
text-transform: uppercase;
color: #444;
margin-top: 2.5mm;
border-top: .5pt solid #999;
padding-top: 1.5mm;
}
.examenes-lista {
font-size: 8pt;
line-height: 1.5;
}
.badge-prio {
display: inline-block;
font-size: 7pt;
font-weight: bold;
padding: 0.5mm 1.5mm;
border-radius: 2pt;
border: 1pt solid #000;
text-transform: uppercase;
}
.prio-stat { background: #000; color: #fff; }
.prio-urgente { background: #ff0; color: #000; }
.prio-normal { border-color: #aaa; color: #555; }
.aviso-ayuno {
margin-top: 2mm;
font-size: 8pt;
font-weight: bold;
border: 1pt solid #f90;
padding: 1mm 2mm;
border-radius: 2pt;
color: #a60;
}
.footer {
margin-top: 2mm;
font-size: 7pt;
color: #666;
border-top: .5pt solid #ccc;
padding-top: 1mm;
display: flex;
justify-content: space-between;
}
/* ── Botón de impresión (solo pantalla) ── */
.btn-imprimir {
display: block;
margin: 8mm auto;
padding: 6px 20px;
font-size: 12px;
cursor: pointer;
background: #0d6efd;
color: #fff;
border: none;
border-radius: 4px;
}
</style>
<?php
// Intentar cargar Libre Barcode si hay CDN (solo para pantalla; en impresión el texto es suficiente)
?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Libre+Barcode+128+Text&display=swap" rel="stylesheet">
</head>
<body>
<!-- Botón solo en pantalla -->
<div class="no-print" style="text-align:center;margin-bottom:4mm">
<button class="btn-imprimir" onclick="window.print()">🖨 Imprimir etiqueta</button>
<a href="<?= BASE_URL ?>/erp.php?m=registro_exams&v=orden&id=<?= $ordenId ?>"
style="display:inline-block;margin-left:8px;font-size:12px;color:#666">← Volver a orden</a>
</div>
<div class="etiqueta">
<!-- Encabezado: nombre lab + código -->
<div class="header">
<div>
<div class="lab-nombre"><?= defined('APP_NAME') ? htmlspecialchars(APP_NAME) : 'Laboratorio Clínico' ?></div>
<div style="font-size:7.5pt;color:#555">Exámenes de laboratorio en sede</div>
</div>
<div style="text-align:right">
<div class="codigo"><?= htmlspecialchars($codigoBarras) ?></div>
<div class="barcode"><?= htmlspecialchars($codigoBarras) ?></div>
</div>
</div>
<!-- Paciente -->
<div class="paciente-nombre"><?= htmlspecialchars($orden['nombre_completo']) ?></div>
<div class="meta-row">
<div class="meta-item">
<span class="meta-label">Documento</span>
<?= htmlspecialchars($orden['tipo_documento'] . ' ' . $orden['numero_documento']) ?>
</div>
<?php if ($edad !== null): ?>
<div class="meta-item">
<span class="meta-label">Edad</span>
<?= $edad ?> años
</div>
<?php endif; ?>
<?php if ($orden['eps']): ?>
<div class="meta-item">
<span class="meta-label">EPS</span>
<?= htmlspecialchars($orden['eps']) ?>
</div>
<?php endif; ?>
<div class="meta-item" style="margin-left:auto">
<span class="meta-label">Prioridad</span>
<span class="badge-prio prio-<?= $orden['prioridad'] ?>">
<?= strtoupper($orden['prioridad']) ?>
</span>
</div>
</div>
<!-- Exámenes -->
<div class="examenes-titulo">
<i>Exámenes solicitados (<?= count($items) ?>)</i>
</div>
<div class="examenes-lista">
<?php foreach ($items as $i => $item): ?>
<?= ($i + 1) ?>. <?= htmlspecialchars($item['tipo_nombre']) ?>
<span style="color:#888;font-size:7.5pt">(<?= htmlspecialchars($item['tipo_codigo']) ?>)</span><?php
if (!$item['requiere_ayuno']): ?><?php endif; ?>
<?php if ($i < count($items) - 1): ?> ·
<?php endif; ?>
<?php endforeach; ?>
</div>
<!-- Aviso ayuno -->
<?php if ($ayunoReq): ?>
<div class="aviso-ayuno">
⚠ REQUIERE AYUNO<?= $horasAyuno ? " DE {$horasAyuno} HORAS" : '' ?>
</div>
<?php endif; ?>
<!-- Pie: médico + fecha -->
<div class="footer">
<span>Médico: <?= htmlspecialchars($orden['medico_nombre'] ?: '—') ?></span>
<span><?= $fecha ?></span>
</div>
</div><!-- /etiqueta -->
<script>
// Auto-print al abrir
window.addEventListener('load', function () {
// Pequeño delay para cargar la fuente barcode
setTimeout(function () { window.print(); }, 800);
});
</script>
</body>
</html>