feat: make side panel Cultivos section read-only and place bed badge on the left

This commit is contained in:
2026-08-11 13:10:55 +00:00
commit a17deb815e
117 changed files with 32924 additions and 0 deletions
+753
View File
@@ -0,0 +1,753 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<title>Generador HC Ingreso Santojanni</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js"></script>
<style>
body { font-family: Arial, sans-serif; background: #f0f4f8; display: flex;
flex-direction: column; align-items: center; padding: 40px; }
h1 { font-size: 1.2rem; color: #1a3a5c; margin-bottom: 8px; }
p { color: #555; font-size: .9rem; margin-bottom: 24px; }
button { background: #1a3a5c; color: #fff; border: none; padding: 14px 36px;
font-size: 1rem; border-radius: 6px; cursor: pointer; }
button:hover { background: #25547a; }
#status { margin-top: 16px; font-size: .85rem; color: #444; }
</style>
</head>
<body>
<h1>Historia Clínica de Ingreso Hospital Donación F. Santojanni</h1>
<p>Hace clic en el botón para generar el PDF con las 5 páginas del formulario en blanco.</p>
<button onclick="generarPDF()">Generar PDF</button>
<div id="status"></div>
<script>
/* ============================================================
CONSTANTES GLOBALES
============================================================ */
const A4W = 595.28;
const A4H = 841.89;
const ML = 25; // margen izquierdo
const MR = 25; // margen derecho
const INNER = A4W - ML - MR; // ancho útil: 545.28
// Colores
const NEGRO = PDFLib.rgb(0, 0, 0);
const GRIS_HEAD = PDFLib.rgb(0.82, 0.82, 0.82); // fondo encabezados de sección
const GRIS_LABEL = PDFLib.rgb(0.93, 0.93, 0.93); // fondo celdas etiqueta
const BLANCO = PDFLib.rgb(1, 1, 1);
/* ============================================================
HELPERS
============================================================ */
/** Dibuja un rectángulo con borde negro (relleno opcional). */
function rect(page, x, y, w, h, { fill = null, borderWidth = 0.5 } = {}) {
if (fill) page.drawRectangle({ x, y, width: w, height: h, color: fill });
page.drawRectangle({ x, y, width: w, height: h,
borderColor: NEGRO, borderWidth, color: fill ?? PDFLib.rgb(1,1,1) });
}
/** Dibuja texto centrado horizontalmente en un rango x→x+w. */
function textCenter(page, txt, x, y, w, { font, size = 8, color = NEGRO } = {}) {
const tw = font.widthOfTextAtSize(txt, size);
page.drawText(txt, { x: x + (w - tw) / 2, y, size, font, color });
}
/** Caja de sección con fondo gris y título centrado en negrita. */
function sectionHeader(page, label, x, y, w, h = 14, { bold, regular } = {}) {
rect(page, x, y, w, h, { fill: GRIS_HEAD });
textCenter(page, label, x, y + 3, w, { font: bold, size: 8 });
return y - h; // retorna la Y del borde inferior de la caja
}
/** Celda con etiqueta izquierda (gris) y área de escritura (blanca). */
function labelCell(page, label, x, y, labelW, totalW, h, { regular, bold, fontSize = 7 } = {}) {
// fondo etiqueta
page.drawRectangle({ x, y, width: labelW, height: h, color: GRIS_LABEL });
page.drawRectangle({ x, y, width: labelW, height: h,
borderColor: NEGRO, borderWidth: 0.4, color: GRIS_LABEL });
// área escribible
page.drawRectangle({ x: x + labelW, y, width: totalW - labelW, height: h,
borderColor: NEGRO, borderWidth: 0.4, color: BLANCO });
page.drawText(label, { x: x + 2, y: y + 2.5, size: fontSize, font: regular, color: NEGRO });
}
/** Línea horizontal simple. */
function hLine(page, x, y, w) {
page.drawLine({ start: { x, y }, end: { x: x + w, y }, thickness: 0.4, color: NEGRO });
}
/** Línea vertical simple. */
function vLine(page, x, y1, y2) {
page.drawLine({ start: { x, y: y1 }, end: { x, y: y2 }, thickness: 0.4, color: NEGRO });
}
/* ============================================================
CABECERA COMÚN (páginas 1-5)
============================================================ */
function drawPageHeader(page, pageNum, bold, regular) {
const top = A4H - 18;
// Logo placeholder izquierdo (círculo con "BA")
page.drawCircle({ x: ML + 12, y: top, size: 11, color: PDFLib.rgb(0.2, 0.4, 0.7) });
page.drawText("BA", { x: ML + 6, y: top - 4, size: 7, font: bold, color: BLANCO });
// Texto central
const titulo1 = "HOSPITAL DONACIÓN F. SANTOJANNI DIVISIÓN CLÍNICA MÉDICA";
const titulo2 = "HISTORIA CLÍNICA DE INGRESO";
const tw1 = bold.widthOfTextAtSize(titulo1, 9);
const tw2 = bold.widthOfTextAtSize(titulo2, 9);
page.drawText(titulo1, { x: (A4W - tw1) / 2, y: top + 4, size: 9, font: bold, color: NEGRO });
page.drawText(titulo2, { x: (A4W - tw2) / 2, y: top - 6, size: 9, font: bold, color: NEGRO });
// Logo placeholder derecho
page.drawCircle({ x: A4W - ML - 12, y: top, size: 11, color: PDFLib.rgb(0.2, 0.4, 0.7) });
page.drawText("H", { x: A4W - ML - 15, y: top - 4, size: 7, font: bold, color: BLANCO });
// Número de página
const pTxt = `${pageNum}`;
page.drawText(pTxt, { x: (A4W - regular.widthOfTextAtSize(pTxt, 8)) / 2,
y: 15, size: 8, font: regular, color: NEGRO });
// Línea separadora
hLine(page, ML, A4H - 30, INNER);
return A4H - 38; // Y de inicio del contenido
}
/* ============================================================
PÁGINA 1 Datos filiatorios / Motivo / Enfermedad actual
============================================================ */
async function buildPage1(pdfDoc, bold, regular) {
const page = pdfDoc.addPage([A4W, A4H]);
let y = drawPageHeader(page, 1, bold, regular);
/* --- DATOS FILIATORIOS --- */
y = sectionHeader(page, "DATOS FILIATORIOS", ML, y, INNER, 14, { bold, regular });
// Fila 1: fecha ingreso hospital | fecha ingreso clínica médica | habitación
const colW = [INNER * 0.30, INNER * 0.42, INNER * 0.28];
const rowH = 12;
let cx = ML;
["FECHA DE INGRESO AL HOSPITAL (DD/MM/AA)", "FECHA DE INGRESO A CLÍNICA MÉDICA (DD/MM/AA)", "HABITACIÓN"].forEach((lbl, i) => {
rect(page, cx, y - rowH, colW[i], rowH);
page.drawText(lbl, { x: cx + 2, y: y - rowH + 3.5, size: 5.5, font: regular, color: NEGRO });
cx += colW[i];
});
// Fecha clínica médica pre-cargada (16/04/2026)
const fechaTxt = "16/04/2026";
const mid = ML + colW[0];
page.drawText(fechaTxt, { x: mid + (colW[1] - bold.widthOfTextAtSize(fechaTxt, 9)) / 2,
y: y - rowH + 3, size: 9, font: bold, color: NEGRO });
y -= rowH;
// Fila 2: Apellido y nombre | DNI/CI
const mitad = INNER / 2;
labelCell(page, "APELLIDO y NOMBRE", ML, y - rowH, 45, mitad, rowH, { regular, bold });
labelCell(page, "DNI / C.I", ML + mitad, y - rowH, 30, mitad, rowH, { regular, bold });
y -= rowH;
// Fila 3
labelCell(page, "FECHA DE NACIMIENTO", ML, y - rowH, 50, mitad, rowH, { regular, bold });
labelCell(page, "HISTORIA CLINICA", ML + mitad, y - rowH, 42, mitad, rowH, { regular, bold });
y -= rowH;
// Fila 4
labelCell(page, "EDAD", ML, y - rowH, 22, mitad, rowH, { regular, bold });
labelCell(page, "OBRA SOCIAL", ML + mitad, y - rowH, 42, mitad, rowH, { regular, bold });
y -= rowH;
// Fila 5
labelCell(page, "NACIONALIDAD", ML, y - rowH, 42, mitad, rowH, { regular, bold });
// DERIVADO DE: con valor "Guardia Externa"
labelCell(page, "DERIVADO DE:", ML + mitad, y - rowH, 42, mitad, rowH, { regular, bold });
page.drawText("Guardia Externa", { x: ML + mitad + 44, y: y - rowH + 3,
size: 7, font: regular, color: NEGRO });
y -= rowH;
// Fila 6
labelCell(page, "DIRECCIÓN", ML, y - rowH, 30, mitad, rowH, { regular, bold });
labelCell(page, "APACHE / MORTALIDAD", ML + mitad, y - rowH, 55, mitad, rowH, { regular, bold });
y -= rowH;
// Fila 7: Teléfono de contacto (ancho completo)
labelCell(page, "Teléfono de contacto:", ML, y - rowH, 60, INNER, rowH, { regular, bold });
y -= rowH + 4;
/* --- MOTIVO DE CONSULTA --- */
y = sectionHeader(page, "MOTIVO DE CONSULTA", ML, y, INNER, 13, { bold, regular });
const motivoH = 24;
rect(page, ML, y - motivoH, INNER, motivoH);
y -= motivoH + 4;
/* --- ENFERMEDAD ACTUAL --- */
y = sectionHeader(page, "ENFERMEDAD ACTUAL", ML, y, INNER, 13, { bold, regular });
const eaH = 220;
rect(page, ML, y - eaH, INNER, eaH);
y -= eaH + 4;
/* --- ANTECEDENTES DE ENFERMEDAD ACTUAL --- */
y = sectionHeader(page, "ANTECEDENTES DE ENFERMEDAD ACTUAL", ML, y, INNER, 13, { bold, regular });
const antH = Math.max(y - 30, 40); // lo que quede en la página
rect(page, ML, y - antH, INNER, antH);
}
/* ============================================================
PÁGINA 2 Antecedentes personales / Medicación / Hábitos
============================================================ */
async function buildPage2(pdfDoc, bold, regular) {
const page = pdfDoc.addPage([A4W, A4H]);
let y = drawPageHeader(page, 2, bold, regular);
/* --- ANTECEDENTES PERSONALES --- */
y = sectionHeader(page, "ANTECEDENTES PERSONALES", ML, y, INNER, 13, { bold, regular });
const apSections = [
{ label: "CLÍNICOS", h: 38 },
{ label: "QUIRÚRGICOS /\nTRAUMATOLÓGICOS", h: 38 },
{ label: "GINECO-OBSTÉTRICOS", h: 38 },
{ label: "ALÉRGICOS /\nTRANSFUSIONALES", h: 38 },
];
const labelW = 80;
apSections.forEach(sec => {
rect(page, ML, y - sec.h, labelW, sec.h, { fill: GRIS_LABEL });
rect(page, ML + labelW, y - sec.h, INNER - labelW, sec.h);
// texto label multilinea centrado verticalmente
sec.label.split("\n").forEach((ln, i) => {
page.drawText(ln, { x: ML + 2, y: y - sec.h / 2 + (sec.label.includes("\n") ? (i === 0 ? 5 : -4) : -2),
size: 7, font: regular, color: NEGRO });
});
y -= sec.h;
});
y -= 4;
/* --- MEDICACIÓN HABITUAL --- */
y = sectionHeader(page, "MEDICACIÓN HABITUAL", ML, y, INNER, 13, { bold, regular });
const medH = 9;
const medColW = INNER / 2;
for (let i = 1; i <= 20; i++) {
const col = i <= 10 ? 0 : 1;
const row = i <= 10 ? i - 1 : i - 11;
const cx2 = ML + col * medColW;
const cy = y - row * medH - medH;
// número
rect(page, cx2, cy, 10, medH);
page.drawText(`${i}`, { x: cx2 + 2, y: cy + 2, size: 6.5, font: regular, color: NEGRO });
// área
rect(page, cx2 + 10, cy, medColW - 10, medH);
}
y -= 10 * medH + 4;
/* --- HÁBITOS + ANTECEDENTES HEREDOFAMILIARES (lado a lado) --- */
const halfW = INNER / 2 - 2;
const habX = ML;
const aheX = ML + halfW + 4;
// Hábitos
y = sectionHeader(page, "HÁBITOS", habX, y, halfW, 13, { bold, regular });
// tabla de hábitos
const habRows = [
["Apetito", "Conservado", "Sí"],
["Sueño", "Conservado", "Sí"],
["Catarsis", "Conservado", "Sí"],
["Diuresis", "Conservado", "Sí"],
];
let hy = y;
const hRowH = 9;
habRows.forEach(([item, val, check]) => {
const widths = [35, 40, 12, halfW - 87];
let hx = habX;
[item, val, check, "Aclare:"].forEach((txt, i) => {
rect(page, hx, hy - hRowH, widths[i], hRowH);
page.drawText(txt, { x: hx + 2, y: hy - hRowH + 2.5, size: 6, font: regular, color: NEGRO });
hx += widths[i];
});
hy -= hRowH;
});
hy -= 2;
// Tabaquismo / Alcoholismo / Drogas
const addictionRows = [
{ label: "Tabaquismo", check: "Sí", unit: "paq/y" },
{ label: "Alcoholismo", check: "No", unit: "gr/d" },
{ label: "Drogas\nilícitas", check: "No", unit: "Cuales" },
];
addictionRows.forEach(row => {
const aw = [40, 14, halfW - 54];
let ax = habX;
[row.label, row.check, row.unit + " Desde/Hasta"].forEach((txt, i) => {
rect(page, ax, hy - hRowH * 1.5, aw[i], hRowH * 1.5);
page.drawText(txt.split("\n")[0], { x: ax + 2, y: hy - hRowH * 1.5 + 6, size: 6, font: regular, color: NEGRO });
if (txt.includes("\n")) page.drawText(txt.split("\n")[1], { x: ax + 2, y: hy - hRowH * 1.5 + 1, size: 6, font: regular, color: NEGRO });
ax += aw[i];
});
hy -= hRowH * 1.5;
});
// Antecedentes heredofamiliares (misma Y de inicio)
let aheY = y + 13; // misma línea que "HÁBITOS" header
aheY = sectionHeader(page, "ANTECEDENTES HEREDOFAMILIARES", aheX, aheY, halfW, 13, { bold, regular });
const aheRows = [
{ label: "Madre", extra: ["Vive", "No", "Años:"] },
{ label: "Patología:" },
{ label: "Padre", extra: ["Vive", "No", "Años:"] },
{ label: "Patología:" },
{ label: "Hermanos", extra: ["N.º", "", "Años:"] },
{ label: "Patología:" },
{ label: "Hijos", extra: ["N.º", "", "Años:"] },
{ label: "Patología:" },
{ label: "Comentarios" },
];
const aRowH = 10;
aheRows.forEach(row => {
rect(page, aheX, aheY - aRowH, halfW, aRowH);
page.drawText(row.label, { x: aheX + 2, y: aheY - aRowH + 2.5, size: 6.5, font: regular, color: NEGRO });
if (row.extra) {
const ex = row.extra;
const ew = [(halfW - 50) / 3, (halfW - 50) / 3, (halfW - 50) / 3];
let ex2 = aheX + 50;
ex.forEach((txt, i) => {
rect(page, ex2, aheY - aRowH, ew[i], aRowH);
page.drawText(txt, { x: ex2 + 2, y: aheY - aRowH + 2.5, size: 6, font: regular, color: NEGRO });
ex2 += ew[i];
});
}
aheY -= aRowH;
});
}
/* ============================================================
PÁGINA 3 Contactos / Vacunas / Determinantes / Examen físico
============================================================ */
async function buildPage3(pdfDoc, bold, regular) {
const page = pdfDoc.addPage([A4W, A4H]);
let y = drawPageHeader(page, 3, bold, regular);
const halfW = INNER / 2 - 2;
/* --- CONTACTOS + VACUNACIÓN (lado a lado) --- */
// Headers a la misma Y
const yStart = y;
sectionHeader(page, "CONTACTOS", ML, yStart, halfW, 13, { bold, regular });
sectionHeader(page, "VACUNACIÓN/SEROLOGÍAS", ML + halfW + 4, yStart, halfW, 13, { bold, regular });
y -= 13;
// Contactos
const contactos = [
{ label: "Animales", val: "No", extra: "Cuáles?" },
{ label: "TBC", val: "No", extra: "Cuándo?" },
{ label: "Chagas", val: "No", extra: "Cómo?" },
{ label: "HIV", val: "No", extra: "Conducta\nriesgo?" },
{ label: "Otros", val: "No", extra: "Cuál/es?" },
];
let cy2 = y;
const cRowH = 16;
contactos.forEach(c => {
const cw = [40, 18, halfW - 58];
let cx2 = ML;
[c.label, c.val, c.extra].forEach((txt, i) => {
rect(page, cx2, cy2 - cRowH, cw[i], cRowH);
txt.split("\n").forEach((ln, li) => {
page.drawText(ln, { x: cx2 + 2, y: cy2 - cRowH + (cRowH / 2) + 1 - li * 7, size: 6.5, font: regular, color: NEGRO });
});
cx2 += cw[i];
});
cy2 -= cRowH;
});
// Vacunación
const vacX = ML + halfW + 4;
let vy = y;
// cabecera sub
const vacHeaders = ["Dosis", "Ultima", "Nombre comercial"];
const vacW = [halfW * 0.22, halfW * 0.22, halfW * 0.56];
// fila "Vacuna COVID" + sub-encabezados
const vacRowH = 10;
rect(page, vacX, vy - vacRowH, halfW * 0.3, vacRowH, { fill: GRIS_LABEL });
page.drawText("Vacuna COVID", { x: vacX + 2, y: vy - vacRowH + 2.5, size: 6.5, font: regular, color: NEGRO });
let vx = vacX + halfW * 0.3;
vacHeaders.forEach((h, i) => {
rect(page, vx, vy - vacRowH, vacW[i], vacRowH, { fill: GRIS_LABEL });
page.drawText(h, { x: vx + 2, y: vy - vacRowH + 2.5, size: 6, font: regular, color: NEGRO });
vx += vacW[i];
});
vy -= vacRowH;
// valor dosis 3
rect(page, vacX, vy - vacRowH, halfW * 0.3, vacRowH, { fill: GRIS_LABEL });
rect(page, vacX + halfW * 0.3, vy - vacRowH, vacW[0], vacRowH);
page.drawText("3", { x: vacX + halfW * 0.3 + 4, y: vy - vacRowH + 2.5, size: 7, font: bold, color: NEGRO });
rect(page, vacX + halfW * 0.3 + vacW[0], vy - vacRowH, vacW[1] + vacW[2], vacRowH);
vy -= vacRowH;
const vacRows = [
{ label: "Antigripal", check: "No" },
{ label: "Antineumocócica", check: "No" },
{ label: "Doble Adultos", check: "No" },
{ label: "Otras", check: "" },
];
const vacRH = 10;
vacRows.forEach(row => {
rect(page, vacX, vy - vacRH, halfW * 0.3, vacRH, { fill: GRIS_LABEL });
page.drawText(row.label, { x: vacX + 2, y: vy - vacRH + 2.5, size: 6.5, font: regular, color: NEGRO });
rect(page, vacX + halfW * 0.3, vy - vacRH, vacW[0], vacRH);
page.drawText(row.check, { x: vacX + halfW * 0.3 + 2, y: vy - vacRH + 2.5, size: 7, font: regular, color: NEGRO });
rect(page, vacX + halfW * 0.3 + vacW[0], vy - vacRH, vacW[1] + vacW[2], vacRH);
vy -= vacRH;
});
// Serologías
rect(page, vacX, vy - vacRH, halfW * 0.3, vacRH, { fill: GRIS_LABEL });
page.drawText("Serologías", { x: vacX + 2, y: vy - vacRH + 2.5, size: 6.5, font: regular, color: NEGRO });
rect(page, vacX + halfW * 0.3, vy - vacRH, halfW * 0.7, vacRH);
const resultTxt = "Resultado";
page.drawText(resultTxt, { x: vacX + halfW * 0.3 + (halfW * 0.7 - regular.widthOfTextAtSize(resultTxt, 7)) / 2, y: vy - vacRH + 2.5, size: 7, font: regular, color: NEGRO });
vy -= vacRH;
// 2 filas vacías de serologías
for (let i = 0; i < 2; i++) {
rect(page, vacX, vy - vacRH, halfW, vacRH);
vy -= vacRH;
}
y = Math.min(cy2, vy) - 4;
/* --- DETERMINANTES SOCIO-EPIDEMIOLÓGICOS --- */
y = sectionHeader(page, "DETERMINANTES SOCIO-EPIDEMIOLÓGICOS", ML, y, INNER, 13, { bold, regular });
const detRows = [
{ label: "LUGAR DE NACIMIENTO" },
{ label: "RESIDENCIA PASADA Y ACTUAL" },
{ label: "VIVIENDA", special: "vivienda" },
{ label: "NIVEL DE ESCOLARIDAD ALCANZADO" },
{ label: "OCUPACIÓN/OFICIO/PROFESIÓN" },
{ label: "VIAJES RECIENTES" },
];
const detH = 12;
const detLW = 90;
detRows.forEach(row => {
if (row.special === "vivienda") {
// dos sub-filas: Material/Sanitarios + Agua/Cloacas
rect(page, ML, y - detH, detLW, detH * 2);
page.drawText(row.label, { x: ML + 2, y: y - detH + 2.5, size: 7, font: regular, color: NEGRO });
// sub-fila 1
const subW = (INNER - detLW) / 4;
const subLabels1 = ["Material", "No", "Sanitarios completos", "No"];
let sx = ML + detLW;
subLabels1.forEach((lbl, i) => {
const fill = (i % 2 === 0) ? GRIS_LABEL : null;
rect(page, sx, y - detH, subW, detH, { fill: fill || BLANCO });
page.drawText(lbl, { x: sx + 2, y: y - detH + 3, size: 6.5, font: regular, color: NEGRO });
sx += subW;
});
y -= detH;
// sub-fila 2
const subLabels2 = ["Agua Potable", "No", "Cloacas", "No"];
sx = ML + detLW;
subLabels2.forEach((lbl, i) => {
const fill = (i % 2 === 0) ? GRIS_LABEL : null;
rect(page, sx, y - detH, subW, detH, { fill: fill || BLANCO });
page.drawText(lbl, { x: sx + 2, y: y - detH + 3, size: 6.5, font: regular, color: NEGRO });
sx += subW;
});
y -= detH;
} else {
rect(page, ML, y - detH, detLW, detH, { fill: GRIS_LABEL });
page.drawText(row.label, { x: ML + 2, y: y - detH + 3, size: 7, font: regular, color: NEGRO });
rect(page, ML + detLW, y - detH, INNER - detLW, detH);
y -= detH;
}
});
y -= 4;
/* --- EXAMEN FÍSICO --- */
y = sectionHeader(page, "EXAMEN FÍSICO", ML, y, INNER, 13, { bold, regular });
// Fila 1: TA / FC / FR / Temp / Sat / FiO2
const efRow1 = [
{ label: "TENSIÓN ARTERIAL", w: INNER * 0.18 },
{ label: "FRECUENCIA\nCARDÍACA", w: INNER * 0.14 },
{ label: "FRECUENCIA\nRESPIRATORIA", w: INNER * 0.14 },
{ label: "TEMPERATURA\nAXILAR", w: INNER * 0.14 },
{ label: "SATUROMETRÍA", w: INNER * 0.14 },
{ label: "FIO2(FRACCIÓN\nINSPIRADA DE OXIGENO)", w: INNER * 0.26 },
];
const efH1 = 22;
const efH1b = 12;
let ex = ML;
efRow1.forEach(col => {
rect(page, ex, y - efH1, col.w, efH1 / 2, { fill: GRIS_LABEL });
col.label.split("\n").forEach((ln, i) => {
page.drawText(ln, { x: ex + 2, y: y - efH1 / 2 + (col.label.includes("\n") ? (i === 0 ? 3 : -4) : -1),
size: 5.5, font: regular, color: NEGRO });
});
rect(page, ex, y - efH1, col.w, efH1);
ex += col.w;
});
y -= efH1 + 2;
// Fila 2: SNG / SV / AVP / AVC / Peso-Talla-IMC
const efRow2 = [
{ label: "SONDA\nNASOGÁSTRICA", val: "No", w: INNER * 0.14 },
{ label: "SONDA\nVESICAL", val: "Sonda Vesical",w: INNER * 0.16 },
{ label: "ACCESO VENOSO\nPERIFÉRICO", val: "Si - MSI", w: INNER * 0.16 },
{ label: "ACCESO VENOSO CENTRAL", val: "No", w: INNER * 0.27 },
{ label: "PESO / TALLA / IMC", val: "", w: INNER * 0.27 },
];
const ef2H = 18;
ex = ML;
efRow2.forEach(col => {
rect(page, ex, y - ef2H, col.w, ef2H / 2, { fill: GRIS_LABEL });
col.label.split("\n").forEach((ln, li) => {
page.drawText(ln, { x: ex + 2, y: y - ef2H / 2 + (col.label.includes("\n") ? (li === 0 ? 3 : -4) : -1),
size: 5.5, font: regular, color: NEGRO });
});
rect(page, ex, y - ef2H, col.w, ef2H);
page.drawText(col.val, { x: ex + 2, y: y - ef2H + 3, size: 6.5, font: regular, color: NEGRO });
ex += col.w;
});
y -= ef2H + 2;
// Sistemas
const sistemas = [
"SISTEMA\nNERVIOSO\nCENTRAL",
"CARDIOVASCULAR",
"RESPIRATORIO",
"DIGESTIVO",
"GENITO/URINARIO",
"SISTEMA OSTEO \nMUSCULO -\nARTICULAR",
];
const sysLW = 60;
const sysH = 28;
sistemas.forEach(sis => {
rect(page, ML, y - sysH, sysLW, sysH, { fill: GRIS_LABEL });
sis.split("\n").forEach((ln, i) => {
const lines = sis.split("\n").length;
const baseY = y - sysH + sysH / 2 - 3;
page.drawText(ln, { x: ML + 2, y: baseY + (lines - 1 - i) * 7, size: 6.5, font: regular, color: NEGRO });
});
rect(page, ML + sysLW, y - sysH, INNER - sysLW, sysH);
y -= sysH;
});
}
/* ============================================================
PÁGINA 4 Laboratorio / Bacteriología / ECG / Estudios
============================================================ */
async function buildPage4(pdfDoc, bold, regular) {
const page = pdfDoc.addPage([A4W, A4H]);
let y = drawPageHeader(page, 4, bold, regular);
/* --- LABORATORIO --- */
y = sectionHeader(page, "LABORATORIO", ML, y, INNER, 13, { bold, regular });
// Parámetros en columnas de 3 grupos (label + espacio)
const labParams = [
["HTO (%)", "GLUC (mg/dl)", "Na+ (mEq/L)", "BT (mg/dl)", "TP (%)"],
["HB (G/DL)", "UREA (mg/dl)", "K+ (mEq/L)", "BD (mg/dl)", "KPTT (seg)"],
["GB (X10⁹/L)", "CREAT (mg/dl)", "Cl- (mEq/L)", "GOT (UI/L)", "RIN"],
["PQ (X10⁹/L)", "", "P- (mEq/L)", "GPT (UI/L)", "PROT (g/dL)"],
["", "", "Mg+ (mEq/L)", "FAL (UI/L)", "ALB (g/dL)"],
];
const labCols = 5;
const labColW = INNER / labCols;
const labRowH = 11;
const labLabelW = labColW * 0.55;
labParams.forEach((row, ri) => {
let lx = ML;
row.forEach((param, ci) => {
if (param) {
rect(page, lx, y - labRowH, labLabelW, labRowH, { fill: GRIS_LABEL });
page.drawText(param, { x: lx + 2, y: y - labRowH + 3, size: 5.5, font: regular, color: NEGRO });
rect(page, lx + labLabelW, y - labRowH, labColW - labLabelW, labRowH);
}
lx += labColW;
});
y -= labRowH;
});
// EAB
y -= 2;
const eabLabels = ["EAB", "PH", "CO2", "O2", "HCO3", "EB", "SAT", "A.L", "FIO2"];
const eabW = [28, 30, 30, 30, 35, 30, 30, 30, 30];
const eabH = 11;
let eabX = ML;
eabLabels.forEach((lbl, i) => {
rect(page, eabX, y - eabH, eabW[i], eabH / 2, { fill: GRIS_LABEL });
page.drawText(lbl, { x: eabX + 2, y: y - eabH / 2 + 1, size: 6, font: bold, color: NEGRO });
rect(page, eabX, y - eabH, eabW[i], eabH);
eabX += eabW[i];
});
y -= eabH + 2;
// OTROS / FENA
rect(page, ML, y, INNER, 13, { fill: GRIS_LABEL });
page.drawText("OTROS (INCLUYE LÍQUIDOS)", { x: ML + 2, y: y + 4, size: 7, font: bold, color: NEGRO });
y -= 0;
rect(page, ML, y - 36, INNER, 36);
page.drawText("FENA", { x: ML + 2, y: y - 8, size: 7, font: regular, color: NEGRO });
y -= 36 + 13 + 4;
/* --- BACTERIOLOGÍA --- */
y = sectionHeader(page, "BACTERIOLOGÍA", ML, y, INNER, 13, { bold, regular });
const bactHeaders = ["Fecha", "Cultivo", "Resultado", "Sensibilidad"];
const bactW = [INNER * 0.18, INNER * 0.27, INNER * 0.27, INNER * 0.28];
const bactH = 10;
let bx = ML;
bactHeaders.forEach((h, i) => {
rect(page, bx, y - bactH, bactW[i], bactH, { fill: GRIS_LABEL });
textCenter(page, h, bx, y - bactH + 2, bactW[i], { font: bold, size: 7 });
bx += bactW[i];
});
y -= bactH;
for (let i = 0; i < 5; i++) {
bx = ML;
bactHeaders.forEach((_, j) => { rect(page, bx, y - bactH, bactW[j], bactH); bx += bactW[j]; });
y -= bactH;
}
y -= 4;
/* --- ECG --- */
y = sectionHeader(page, "ELECTROCARDIOGRAMA", ML, y, INNER, 13, { bold, regular });
// Fila con fecha + interpretación
const ecgH = 10;
const ecgLW = 28;
const ecgRW = INNER / 2;
const ecgParams = [
["FECHA", "", ecgRW],
["Ritmo", "PR"],
["FC", "Onda T"],
["Eje", "QT"],
["Onda P",""],
["QRS", ""],
];
// Primera fila: fecha + "INTERPRETACIÓN DIAGNÓSTICA:" (columna derecha grande)
rect(page, ML, y - ecgH, ecgLW, ecgH, { fill: GRIS_LABEL });
page.drawText("FECHA", { x: ML + 2, y: y - ecgH + 3, size: 7, font: bold, color: NEGRO });
rect(page, ML + ecgLW, y - ecgH, ecgRW - ecgLW, ecgH);
rect(page, ML + ecgRW, y - ecgH, INNER - ecgRW, ecgH, { fill: GRIS_LABEL });
page.drawText("INTERPRETACIÓN DIAGNÓSTICA:", { x: ML + ecgRW + 2, y: y - ecgH + 3, size: 7, font: bold, color: NEGRO });
y -= ecgH;
const ecgLeft = [["Ritmo"],["FC"],["Eje"],["Onda P"],["QRS"]];
const ecgRight = [["PR"],["Onda T"],["QT"],[""],[""]];
const ecgDataH = 10;
const lhW = INNER / 4;
// zona interpretación (rectángulo derecho que abarca todas las filas)
rect(page, ML + ecgRW, y - ecgDataH * 5, INNER - ecgRW, ecgDataH * 5);
ecgLeft.forEach((row, i) => {
rect(page, ML, y - ecgDataH, lhW * 0.5, ecgDataH, { fill: GRIS_LABEL });
page.drawText(row[0], { x: ML + 2, y: y - ecgDataH + 3, size: 7, font: regular, color: NEGRO });
rect(page, ML + lhW * 0.5, y - ecgDataH, lhW - lhW * 0.5, ecgDataH);
if (ecgRight[i][0]) {
rect(page, ML + lhW, y - ecgDataH, lhW * 0.5, ecgDataH, { fill: GRIS_LABEL });
page.drawText(ecgRight[i][0], { x: ML + lhW + 2, y: y - ecgDataH + 3, size: 7, font: regular, color: NEGRO });
rect(page, ML + lhW * 1.5, y - ecgDataH, lhW - lhW * 0.5, ecgDataH);
}
y -= ecgDataH;
});
y -= 4;
/* --- ESTUDIOS COMPLEMENTARIOS --- */
y = sectionHeader(page, "ESTUDIOS COMPLEMENTARIOS", ML, y, INNER, 13, { bold, regular });
const estHeaders = ["Fecha", "Estudio", "Descripción"];
const estW = [INNER * 0.14, INNER * 0.16, INNER * 0.70];
let estX = ML;
estHeaders.forEach((h, i) => {
rect(page, estX, y - ecgH, estW[i], ecgH, { fill: GRIS_LABEL });
page.drawText(h, { x: estX + 2, y: y - ecgH + 3, size: 7, font: bold, color: NEGRO });
estX += estW[i];
});
y -= ecgH;
const estRowH = 22;
for (let i = 0; i < 4; i++) {
estX = ML;
estHeaders.forEach((_, j) => { rect(page, estX, y - estRowH, estW[j], estRowH); estX += estW[j]; });
// Fecha marcadas: /03/ y /04/
if (i < 2) {
page.drawText(i === 0 ? "/03/." : "/04/.", { x: ML + 2, y: y - estRowH + 8, size: 10, font: bold, color: NEGRO });
}
y -= estRowH;
}
}
/* ============================================================
PÁGINA 5 Diagnóstico / Plan diagnóstico / Plan terapéutico
============================================================ */
async function buildPage5(pdfDoc, bold, regular) {
const page = pdfDoc.addPage([A4W, A4H]);
let y = drawPageHeader(page, 5, bold, regular);
/* --- IMPRESIÓN DIAGNÓSTICA POR SINDROME --- */
y = sectionHeader(page, "IMPRESIÓN DIAGNÓSTICA POR SINDROME", ML, y, INNER, 13, { bold, regular });
const idH = 200;
rect(page, ML, y - idH, INNER, idH);
y -= idH + 4;
/* --- PLAN DIAGNÓSTICO --- */
y = sectionHeader(page, "PLAN DIAGNÓSTICO", ML, y, INNER, 13, { bold, regular });
const pdH = 40;
rect(page, ML, y - pdH, INNER, pdH);
y -= pdH + 4;
/* --- DIAGNÓSTICO AL INGRESO --- */
y = sectionHeader(page, "DIAGNÓSTICO AL INGRESO", ML, y, INNER, 13, { bold, regular });
const diH = 40;
rect(page, ML, y - diH, INNER, diH);
y -= diH + 4;
/* --- PLAN TERAPÉUTICO --- */
y = sectionHeader(page, "PLAN TERAPÈUTICO", ML, y, INNER, 13, { bold, regular });
const ptH = 9;
const ptColW = INNER / 2;
for (let i = 1; i <= 20; i++) {
const col = i <= 10 ? 0 : 1;
const row = i <= 10 ? i - 1 : i - 11;
const ptX = ML + col * ptColW;
const ptY = y - row * ptH - ptH;
rect(page, ptX, ptY, 10, ptH);
page.drawText(`${i}`, { x: ptX + 2, y: ptY + 2, size: 6.5, font: regular, color: NEGRO });
rect(page, ptX + 10, ptY, ptColW - 10, ptH);
}
// Firma
const firmaY = y - 10 * ptH - 20;
page.drawText("......................................", {
x: A4W - ML - 120, y: firmaY, size: 8, font: regular, color: NEGRO
});
}
/* ============================================================
MAIN ensambla todo y descarga
============================================================ */
async function generarPDF() {
const status = document.getElementById("status");
status.textContent = "Generando PDF...";
try {
const pdfDoc = await PDFLib.PDFDocument.create();
const regular = await pdfDoc.embedFont(PDFLib.StandardFonts.Helvetica);
const bold = await pdfDoc.embedFont(PDFLib.StandardFonts.HelveticaBold);
const fonts = { regular, bold };
await buildPage1(pdfDoc, bold, regular);
await buildPage2(pdfDoc, bold, regular);
await buildPage3(pdfDoc, bold, regular);
await buildPage4(pdfDoc, bold, regular);
await buildPage5(pdfDoc, bold, regular);
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "HC_Ingreso_Santojanni.pdf";
a.click();
URL.revokeObjectURL(url);
status.textContent = "✅ PDF generado y descargado correctamente.";
} catch (e) {
console.error(e);
status.textContent = "❌ Error al generar el PDF. Ver consola para detalles.";
}
}
</script>
</body>
</html>