Consultorios Médicos & NOA – Presupuestos
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Consultorios Médicos & NOA - Presupuestos</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
pastel: {
celeste: '#E3F2FD', // Celeste pastel suave
celesteDark: '#90CAF9', // Celeste para destacar bordes
rosa: '#FCE4EC', // Rosa cálido suave
rosaDark: '#F48FB1', // Rosa para botones principales
azulTexto: '#1E3A8A' // Azul marino oscuro para máxima legibilidad
}
}
}
}
}
</script>
<style>
body {
-webkit-tap-highlight-color: transparent;
background: linear-gradient(135deg, #FCE4EC 0%, #E3F2FD 100%);
min-height: 100vh;
user-select: none;
-webkit-user-select: none;
}
input {
user-select: auto;
-webkit-user-select: auto;
}
.custom-scrollbar::-webkit-scrollbar { width: 6px; }
.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: #F48FB1; border-radius: 10px; }
</style>
</head>
<body class="text-slate-800 antialiased p-3 md:p-6">
<div id="app-view" class="max-w-6xl mx-auto flex flex-col lg:flex-row gap-6"></div>
<div id="preview-view" class="min-h-screen bg-slate-800 pb-12 flex flex-col items-center hidden -m-3 md:-m-6 pt-4"></div>
<script>
// --- BASE DE DATOS COMPLETA DE EXÁMENES ---
const ESTUDIOS_DATABASE = [
{ id: 1, categoria: 'Rutina y Determinaciones', nombre: 'Determinación Individual', precio: 15000 },
{ id: 2, categoria: 'Rutina y Determinaciones', nombre: '2 Determinaciones Solas', precio: 16000 },
{ id: 3, categoria: 'Rutina y Determinaciones', nombre: '3 Determinaciones Solas', precio: 17000 },
{ id: 4, categoria: 'Rutina y Determinaciones', nombre: '4 Determinaciones Solas', precio: 18000 },
{ id: 5, categoria: 'Rutina y Determinaciones', nombre: '5 Determinaciones Solas', precio: 20000 },
{ id: 6, categoria: 'Rutina y Determinaciones', nombre: 'Grupo Sanguíneo', precio: 14000 },
{ id: 7, categoria: 'Rutina y Determinaciones', nombre: 'Hemograma', precio: 9000 },
{ id: 8, categoria: 'Rutina y Determinaciones', nombre: 'Hepatograma', precio: 13000 },
{ id: 9, categoria: 'Rutina y Determinaciones', nombre: 'Perfil Lipídico', precio: 14000 },
{ id: 10, categoria: 'Rutina y Determinaciones', nombre: 'Preuniversitario (Hem, Glu, Ure, VSG, VDRL, Chagas, OC)', precio: 38000 },
{ id: 11, categoria: 'Rutina y Determinaciones', nombre: 'Urocultivo', precio: 29000 },
{ id: 12, categoria: 'Hormonas Tiroideas', nombre: 'Perfil Tiroideo (TSH, T4 Total, T4, T3, T3L, ATPO)', precio: 15000 },
{ id: 13, categoria: 'Hormonas Tiroideas', name: 'Antitiroglobulina', precio: 18000 },
{ id: 14, categoria: 'Hormonas Tiroideas', nombre: 'Tiroglobulina', precio: 20000 },
{ id: 15, categoria: 'Hormonas Tiroideas', nombre: 'TRAB', precio: 45000 },
{ id: 16, categoria: 'Hormonas Fem/Masc', nombre: 'Perfil Femenino (FSH, LH, Progesterona, Prolactina)', precio: 17000 },
{ id: 17, categoria: 'Hormonas Fem/Masc', nombre: 'Estrona', precio: 20000 },
{ id: 18, categoria: 'Hormonas Fem/Masc', nombre: 'Estradiol', precio: 17000 },
{ id: 19, categoria: 'Hormonas Fem/Masc', nombre: 'Testosterona Total', precio: 17000 },
{ id: 20, categoria: 'Hormonas Fem/Masc', nombre: 'Testosterona Libre', precio: 18000 },
{ id: 21, categoria: 'Hormonas Fem/Masc', nombre: 'DHEA (No Sulfato)', precio: 25000 },
{ id: 22, categoria: 'Serología e Infecciosas', nombre: 'VDRL (Sífilis)', precio: 15000 },
{ id: 23, categoria: 'Serología e Infecciosas', nombre: 'Hepatitis B Ag. Superficie', precio: 18000 },
{ id: 24, categoria: 'Serología e Infecciosas', nombre: 'Hepatitis C / HIV', precio: 25000 },
{ id: 25, categoria: 'Serología e Infecciosas', nombre: 'Chagas / Toxo IgM', precio: 15000 },
{ id: 26, categoria: 'Serología e Infecciosas', nombre: 'Helicobacter Pylori IgG', precio: 18000 },
{ id: 27, categoria: 'Coagulación / Prequirúrgico', nombre: 'Coagulograma', precio: 20000 },
{ id: 28, categoria: 'Coagulación / Prequirúrgico', nombre: 'TP / KPTT / RIN', precio: 25000 },
{ id: 29, categoria: 'Coagulación / Prequirúrgico', nombre: 'Recuento de Plaquetas', precio: 6000 },
{ id: 30, categoria: 'Cultivos y Orina', nombre: 'Orina Completa', precio: 15000 },
{ id: 31, categoria: 'Cultivos y Orina', nombre: 'Exudado de Fauces', precio: 25000 },
{ id: 32, categoria: 'Cultivos y Orina', nombre: 'Test Rápido (Strepto A)', precio: 25000 },
{ id: 33, categoria: 'Cultivos y Orina', nombre: 'Ambos (Fauces + Test Rápido)', precio: 40000 },
{ id: 34, categoria: 'Otros / Marcadores', nombre: 'Carnet de Conducir', precio: 18000 },
{ id: 35, categoria: 'Otros / Marcadores', nombre: 'Subbeta Cuantitativa', precio: 30000 },
{ id: 36, categoria: 'Otros / Marcadores', nombre: 'Vit D / Vit C', precio: 30000 },
{ id: 37, categoria: 'Otros / Marcadores', nombre: 'Screening Celiaquía', precio: 49000 }
];
// --- ESTADO GLOBAL DE LA APP ---
let state = {
seleccionados: new Set(),
discountType: 'none',
manualPercent: 0,
searchTerm: '',
viewMode: 'app',
isGenerating: false
};
// --- FUNCIONES DE CONTROL ---
window.toggleEstudio = function(id) {
if (state.seleccionados.has(id)) {
state.seleccionados.delete(id);
} else {
state.seleccionados.add(id);
}
updateApp();
};
window.changeSearch = function(value) {
state.searchTerm = value;
updateApp();
};
window.setDiscount = function(type) {
state.discountType = type;
updateApp();
};
window.changeManualPercent = function(value) {
state.manualPercent = value;
updateApp();
};
window.switchView = function(mode) {
state.viewMode = mode;
if (mode === 'app') {
document.getElementById('app-view').classList.remove('hidden');
document.getElementById('preview-view').classList.add('hidden');
} else {
document.getElementById('app-view').classList.add('hidden');
document.getElementById('preview-view').classList.remove('hidden');
}
updateApp();
};
window.descargarPDF = function() {
state.isGenerating = true;
updateApp();
setTimeout(() => {
const elemento = document.getElementById('area-impresion');
window.html2canvas(elemento, { scale: 2, useCORS: true, backgroundColor: '#ffffff' })
.then(canvas => {
const { jsPDF } = window.jspdf;
const pdf = new jsPDF('p', 'mm', 'a4');
const margen = 15;
const pdfAncho = pdf.internal.pageSize.getWidth();
const anchoImagen = pdfAncho - (margen * 2);
const altoImagen = (canvas.height * anchoImagen) / canvas.width;
const imgData = canvas.toDataURL('image/png');
pdf.addImage(imgData, 'PNG', margen, margen, anchoImagen, altoImagen);
pdf.save(`Presupuesto_NOA_${new Date().toISOString().slice(0,10)}.pdf`);
state.isGenerating = false;
updateApp();
}).catch(err => {
console.error(err);
alert("Error al generar el archivo.");
state.isGenerating = false;
updateApp();
});
}, 200);
};
// --- SISTEMA DE RENDERIZADO INTERACTIVO NATIVO ---
function updateApp() {
const selectedItems = ESTUDIOS_DATABASE.filter(e => state.seleccionados.has(e.id));
const subtotal = selectedItems.reduce((acc, curr) => acc + curr.precio, 0);
let discountAmount = 0;
let discountLabel = '';
if (subtotal > 0) {
if (state.discountType === 'pami' || state.discountType === 'coop') {
discountAmount = subtotal * 0.20;
discountLabel = state.discountType === 'pami' ? 'PAMI (20%)' : 'Cooperativa (20%)';
} else if (state.discountType === 'manual') {
const validPercent = Math.max(0, Math.min(100, Number(state.manualPercent) || 0));
discountAmount = subtotal * (validPercent / 100);
discountLabel = `Manual (${validPercent}%)`;
} else if (state.discountType === 'volume') {
if (subtotal < 45000) { discountAmount = subtotal * 0.10; discountLabel = 'Volumen (10%)'; }
else if (subtotal <= 99000) { discountAmount = subtotal * 0.15; discountLabel = 'Volumen (15%)'; }
else { discountAmount = subtotal * 0.20; discountLabel = 'Volumen (20%)'; }
}
}
const totalFinal = subtotal - discountAmount;
const formatMoney = (val) => new Intl.NumberFormat('es-AR', { style: 'currency', currency: 'ARS', maximumFractionDigits: 0 }).format(val);
const currentDate = new Date().toLocaleDateString('es-AR');
// --- FILTRAR Y AGRUPAR EXÁMENES ---
const filtered = ESTUDIOS_DATABASE.filter(est =>
est.nombre.toLowerCase().includes(state.searchTerm.toLowerCase()) ||
est.categoria.toLowerCase().includes(state.searchTerm.toLowerCase())
);
const grouped = filtered.reduce((acc, curr) => {
if (!acc[curr.categoria]) acc[curr.categoria] = [];
acc[curr.categoria].push(curr);
return acc;
}, {});
// ==========================================
// RENDER VISTA 1: INTERFAZ APP DE SELECCIÓN
// ==========================================
if (state.viewMode === 'app') {
let categoriesHTML = '';
if (Object.keys(grouped).length === 0) {
categoriesHTML = `<div class="text-center py-12 bg-white rounded-3xl border-2 border-dashed border-pastel-celesteDark"><p class="text-pastel-azulTexto/60 font-semibold">No se encontraron exámenes para tu búsqueda.</p></div>`;
} else {
Object.entries(grouped).forEach(([category, items]) => {
let itemsHTML = '';
items.forEach(item => {
const isSel = state.seleccionados.has(item.id);
itemsHTML += `
<div onclick="toggleEstudio(${item.id})" class="flex items-center justify-between p-4 cursor-pointer transition-all rounded-2xl active:scale-[0.99] ${isSel ? 'bg-pastel-celeste/60' : 'hover:bg-pastel-rosa/20 bg-white'}">
<div class="flex items-center gap-4">
<div class="w-6 h-6 shrink-0 flex items-center justify-center rounded-full border-2 ${isSel ? 'border-pastel-rosaDark bg-pastel-rosaDark text-white' : 'border-pastel-celesteDark'}">
${isSel ? '✓' : ''}
</div>
<span class="text-sm md:text-base font-semibold ${isSel ? 'text-pastel-azulTexto font-bold' : 'text-slate-600'}">${item.nombre}</span>
</div>
<span class="font-bold text-sm bg-white border border-pastel-celesteDark/40 px-3 py-1 rounded-xl shadow-sm text-pastel-azulTexto">${formatMoney(item.precio)}</span>
</div>
`;
});
categoriesHTML += `
<div class="bg-white/90 backdrop-blur-md rounded-3xl shadow-sm border-2 border-pastel-celeste overflow-hidden mb-5">
<div class="bg-gradient-to-r from-pastel-celeste to-white px-5 py-3.5 border-b border-pastel-celesteDark/30">
<h3 class="text-sm font-black uppercase text-pastel-azulTexto tracking-wider">${category}</h3>
</div>
<div class="p-2 space-y-1 divide-y divide-slate-100/60">${itemsHTML}</div>
</div>
`;
});
}
let ticketHTML = '';
if (selectedItems.length === 0) {
ticketHTML = `<div class="flex flex-col items-center justify-center py-12 text-slate-400 gap-2"><span class="text-3xl">📋</span><p class="text-xs font-bold uppercase tracking-wide">Selecciona los ítems de la lista</p></div>`;
} else {
selectedItems.forEach(item => {
ticketHTML += `
<li class="flex justify-between items-start text-xs border-b border-dashed border-slate-200 pb-2">
<span class="text-pastel-azulTexto font-semibold pr-4 leading-tight">${item.nombre}</span>
<span class="font-bold text-slate-500 shrink-0">${formatMoney(item.precio)}</span>
</li>
`;
});
}
document.getElementById('app-view').innerHTML = `
<div class="flex-1 order-last lg:order-none">
<div class="bg-white/80 backdrop-blur-md p-4 rounded-3xl shadow-sm border-2 border-pastel-rosa mb-4 flex flex-col md:flex-row justify-between items-center gap-3">
<div>
<h1 class="text-xl font-black text-pastel-azulTexto uppercase tracking-tight">Laboratorio NOA</h1>
<p class="text-xs font-bold text-pastel-rosaDark uppercase">Lista de Precios Interactiva</p>
</div>
<input type="text" placeholder="🔍 Buscar examen..." value="${state.searchTerm}" oninput="changeSearch(this.value)" class="w-full md:w-72 px-4 py-2.5 rounded-2xl bg-pastel-celeste/40 border border-pastel-celesteDark/60 focus:outline-none focus:ring-2 focus:ring-pastel-rosaDark text-pastel-azulTexto font-medium placeholder-pastel-azulTexto/40" />
</div>
<div class="custom-scrollbar overflow-y-auto max-h-[75vh] pr-1">${categoriesHTML}</div>
</div>
<div class="w-full lg:w-80 shrink-0">
<div class="bg-white rounded-[2rem] shadow-md border-2 border-pastel-rosa overflow-hidden sticky top-4">
<div class="p-5 bg-gradient-to-tr from-pastel-rosa to-pastel-celeste text-pastel-azulTexto border-b-2 border-pastel-rosaDark/20">
<h2 class="text-lg font-black uppercase tracking-tight">Resumen de Caja</h2>
<p class="text-xs font-bold opacity-70">${state.seleccionados.size} estudios marcados</p>
</div>
<ul class="p-5 space-y-3 max-h-48 overflow-y-auto custom-scrollbar bg-slate-50/50">${ticketHTML}</ul>
<div class="p-4 bg-pastel-celeste/20 border-t border-b border-pastel-celesteDark/30">
<h4 class="text-[11px] font-black uppercase text-pastel-azulTexto tracking-wider mb-2">Aplicar Beneficio</h4>
<div class="grid grid-cols-2 gap-1.5">
${['none', 'volume', 'pami', 'coop'].map(t => `
<button onclick="setDiscount('${t}')" class="py-1.5 px-1 text-[10px] font-bold uppercase rounded-xl border transition-all ${state.discountType === t ? 'bg-pastel-rosaDark border-pastel-rosaDark text-white shadow-sm' : 'bg-white border-slate-200 text-slate-500 hover:bg-slate-50'}">
${t === 'none' ? 'Sin Dto' : t === 'volume' ? 'Auto %' : t === 'pami' ? 'PAMI' : 'Coop.'}
</button>
`).join('')}
<button onclick="setDiscount('manual')" class="col-span-2 py-1.5 text-[10px] font-bold uppercase rounded-xl border transition-all ${state.discountType === 'manual' ? 'bg-pastel-rosaDark border-pastel-rosaDark text-white shadow-sm' : 'bg-white border-slate-200 text-slate-500 hover:bg-slate-50'}">Descuento Manual %</button>
</div>
${state.discountType === 'manual' ? `
<div className="animate-fadeIn mt-2 flex items-center justify-between bg-white rounded-xl p-1.5 px-3 border border-pastel-rosa shadow-inner">
<span class="text-[10px] font-bold text-slate-400 uppercase">Porcentaje:</span>
<div class="flex items-center"><input type="number" min="0" max="100" value="${state.manualPercent}" oninput="changeManualPercent(this.value)" class="w-12 text-right bg-pastel-rosa/20 rounded-md p-0.5 font-bold text-pastel-azulTexto outline-none" /><strong class="text-xs ml-1 text-pastel-rosaDark">%</strong></div>
</div>
` : ''}
</div>
<div class="p-5 bg-white space-y-2">
<div class="flex justify-between text-xs font-semibold text-slate-500"><span>Subtotal:</span><span>${formatMoney(subtotal)}</span></div>
${discountAmount > 0 ? `<div class="flex justify-between text-xs font-bold text-emerald-700 bg-emerald-50 p-2 rounded-xl border border-emerald-100"><span>${discountLabel}:</span><span>-${formatMoney(discountAmount)}</span></div>` : ''}
<div class="flex justify-between items-center pt-2 border-t border-slate-100"><span class="text-xs font-black uppercase text-pastel-azulTexto">Total Final:</span><span class="text-xl font-black text-pastel-rosaDark">${formatMoney(totalFinal)}</span></div>
<button onclick="switchView('preview')" ${state.seleccionados.size === 0 ? 'disabled' : ''} class="w-full mt-4 bg-pastel-azulTexto hover:opacity-95 text-white font-bold py-3.5 rounded-2xl shadow-md transition-all flex justify-center items-center gap-2 text-xs uppercase tracking-wider disabled:bg-gray-200 disabled:text-gray-400 disabled:cursor-not-allowed disabled:shadow-none">📄 Ver Vista Previa A4</button>
</div>
</div>
</div>
`;
}
// ==========================================
// RENDER VISTA 2: HOJA A4 VIRTUAL CON MÁRGENES
// ==========================================
if (state.viewMode === 'preview') {
let tableRowsHTML = '';
selectedItems.forEach((item, idx) => {
tableRowsHTML += `
<tr class="border-b border-slate-100 text-xs">
<td class="p-3 text-slate-400 font-bold">${idx + 1}</td>
<td class="p-3 font-semibold text-[10px] text-slate-400 uppercase tracking-wide">${item.categoria}</td>
<td class="p-3 font-bold text-pastel-azulTexto">${item.nombre}</td>
<td class="p-3 text-right font-mono font-bold text-slate-700">${formatMoney(item.price || item.precio)}</td>
</tr>
`;
});
document.getElementById('preview-view').innerHTML = `
<div class="w-full max-w-[800px] px-4 md:px-0 mb-4 flex justify-between items-center">
<button onclick="switchView('app')" class="flex items-center gap-1 text-white bg-slate-700 hover:bg-slate-600 px-4 py-2 rounded-xl text-sm font-bold transition-all">← Volver a Modificar</button>
<button onclick="descargarPDF()" ${state.isGenerating ? 'disabled' : ''} class="flex items-center gap-2 text-white bg-pastel-rosaDark hover:opacity-90 px-6 py-2 rounded-xl text-sm font-black shadow-lg shadow-pink-500/20 transition-all disabled:bg-slate-600">
${state.isGenerating ? '⏳ Generando Archivo...' : '💾 Descargar Documento PDF'}
</button>
</div>
<div class="px-4 w-full flex justify-center">
<div id="area-impresion" class="bg-white w-full max-w-[800px] min-h-[1050px] p-10 rounded-[1.5rem] relative shadow-2xl flex flex-col justify-between">
<div>
<div class="flex justify-between items-start border-b-4 border-pastel-celeste pb-5 mb-6">
<div>
<h1 class="text-2xl font-black text-pastel-azulTexto tracking-tight leading-tight uppercase">
CONSULTORIOS MÉDICOS & NOA
</h1>
<p class="text-xs font-bold text-pastel-rosaDark uppercase tracking-wider mt-0.5">
Cooperativa Salto de Las Rosas
</p>
<p class="text-[11px] text-slate-500 mt-2 font-medium">
Salto de Las Rosas, San Rafael, Mendoza, Argentina.
</p>
<p class="text-[11px] font-bold text-slate-700 mt-0.5">
WhatsApp: +54 (260) 401.8478
</p>
</div>
<div class="text-right">
<span class="text-xs font-bold text-slate-600 bg-pastel-celeste/40 px-3 py-1.5 rounded-xl block">Fecha: ${currentDate}</span>
<p class="text-[9px] text-slate-400 font-bold uppercase tracking-widest mt-2">Presupuesto de Exámenes</p>
</div>
</div>
<div class="mb-6">
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-3 border-b border-slate-200 pb-1">Detalle del Paciente / Solicitud</h3>
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-pastel-rosa/30 text-pastel-rosaDark text-[10px] font-black uppercase tracking-wider">
<th class="p-2 pl-3 rounded-l-xl w-10">Item</th>
<th className="p-2">Grupo de Estudio</th>
<th className="p-2">Determinación</th>
<th className="p-2 pr-3 text-right rounded-r-xl w-28">Importe</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">${tableRowsHTML}</tbody>
</table>
</div>
</div>
<div>
<div class="flex flex-col items-end space-y-1.5 border-t border-slate-200 pt-4 mb-16">
<div class="flex justify-between w-64 text-xs font-semibold text-slate-500 px-2">
<span>Subtotal:</span><span>${formatMoney(subtotal)}</span>
</div>
${discountAmount > 0 ? `
<div class="flex justify-between w-64 text-xs font-bold text-emerald-700 bg-emerald-50 px-3 py-1.5 rounded-xl border border-emerald-100">
<span>Bonificación (${discountLabel}):</span><span>-${formatMoney(discountAmount)}</span>
</div>
` : ''}
<div class="flex justify-between w-64 items-center bg-pastel-celeste/40 px-3 py-2 rounded-xl mt-1">
<span class="text-xs font-black uppercase text-pastel-azulTexto">Total Neto:</span>
<span class="text-xl font-black text-pastel-azulTexto">${formatMoney(totalFinal)}</span>
</div>
</div>
<div class="flex justify-around border-t border-dashed border-slate-200 pt-8 mb-4">
<div class="text-center w-48 border-t border-slate-400 pt-2">
<p class="font-bold text-[9px] text-slate-400 uppercase tracking-wider">Firma del Profesional</p>
</div>
<div class="text-center w-48 border-t border-slate-400 pt-2">
<p class="font-bold text-[9px] text-slate-400 uppercase tracking-wider">Sello Médico</p>
</div>
</div>
</div>
</div>
</div>
`;
}
}
// Inicializar la aplicación al cargar la ventana
window.onload = () => {
updateApp();
};
</script>
</body>
</html>