🔍
* generarWord(protocolo, configuracion); */ // ════════════════════════════════════════════════════ // 1. MOTOR ZIP PURO EN JS // ════════════════════════════════════════════════════ function crc32(data) { let crc = 0xFFFFFFFF; for (let i = 0; i < data.length; i++) { crc ^= data[i]; for (let j = 0; j < 8; j++) crc = (crc >>> 1) ^ (crc & 1 ? 0xEDB88320 : 0); } return (crc ^ 0xFFFFFFFF) >>> 0; } function strToBytes(str) { return new TextEncoder().encode(str); } function concatBytes(arrays) { const total = arrays.reduce((s, a) => s + a.length, 0); const out = new Uint8Array(total); let off = 0; for (const a of arrays) { out.set(a, off); off += a.length; } return out; } function writeUint16LE(buf, val, off) { buf[off] = val & 0xFF; buf[off+1] = (val >> 8) & 0xFF; } function writeUint32LE(buf, val, off) { buf[off] = val & 0xFF; buf[off+1] = (val>>8)&0xFF; buf[off+2] = (val>>16)&0xFF; buf[off+3] = (val>>24)&0xFF; } function buildZip(files) { const entries = []; const centralDir = []; let offset = 0; for (const [name, content] of Object.entries(files)) { const nameBytes = strToBytes(name); const dataBytes = typeof content === 'string' ? strToBytes(content) : content; const crc = crc32(dataBytes); const lh = new Uint8Array(30 + nameBytes.length); writeUint32LE(lh, 0x04034b50, 0); writeUint16LE(lh, 20, 4); writeUint16LE(lh, 0, 6); writeUint16LE(lh, 0, 8); writeUint16LE(lh, 0, 10); writeUint16LE(lh, 0, 12); writeUint32LE(lh, crc, 14); writeUint32LE(lh, dataBytes.length, 18); writeUint32LE(lh, dataBytes.length, 22); writeUint16LE(lh, nameBytes.length, 26); writeUint16LE(lh, 0, 28); lh.set(nameBytes, 30); const cd = new Uint8Array(46 + nameBytes.length); writeUint32LE(cd, 0x02014b50, 0); writeUint16LE(cd, 20, 4); writeUint16LE(cd, 20, 6); writeUint16LE(cd, 0, 8); writeUint16LE(cd, 0, 10); writeUint16LE(cd, 0, 12); writeUint16LE(cd, 0, 14); writeUint32LE(cd, crc, 16); writeUint32LE(cd, dataBytes.length, 20); writeUint32LE(cd, dataBytes.length, 24); writeUint16LE(cd, nameBytes.length, 28); writeUint16LE(cd, 0, 30); writeUint16LE(cd, 0, 32); writeUint16LE(cd, 0, 34); writeUint16LE(cd, 0, 36); writeUint32LE(cd, 0, 38); writeUint32LE(cd, offset, 42); cd.set(nameBytes, 46); entries.push(lh, dataBytes); centralDir.push(cd); offset += lh.length + dataBytes.length; } const cdBytes = concatBytes(centralDir); const eocd = new Uint8Array(22); writeUint32LE(eocd, 0x06054b50, 0); writeUint16LE(eocd, 0, 4); writeUint16LE(eocd, 0, 6); writeUint16LE(eocd, Object.keys(files).length, 8); writeUint16LE(eocd, Object.keys(files).length, 10); writeUint32LE(eocd, cdBytes.length, 12); writeUint32LE(eocd, offset, 16); writeUint16LE(eocd, 0, 20); return concatBytes([...entries, cdBytes, eocd]); } // ════════════════════════════════════════════════════ // 2. MOTOR XML DOCX // ════════════════════════════════════════════════════ function x(s) { return String(s || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function hexColor(h) { return h.replace('#','').toUpperCase(); } // Párrafo de texto justificado con narrativa function pJust(text, sz=22, color='000000', bold=false, italic=false, spaceBefore=80, spaceAfter=100) { return ` ${bold?'':''}${italic?'':''} ${x(text)}`; } function pCenter(text, sz=22, color='000000', bold=false, spaceBefore=60, spaceAfter=60) { return ` ${bold?'':''} ${x(text)}`; } function pBullet(text, sz=22) { return ` ${x(text)}`; } function pNumbered(text, sz=22) { return ` ${x(text)}`; } function h1(text, color) { return ` ${x(text)}`; } function h2(text, color) { return ` ${x(text)}`; } function hRule(color) { return ``; } function spacer(n=1) { return ``; } function pageBreak() { return ``; } function tcell(text, w, fill, bold=false, color='000000', sz=20, align='left', vAlign='center') { return ` ${bold?'':''} ${x(text)}`; } function alertBox(title, body, fill, borderColor) { return ` ${x(title)} ${x(body)} `; } // ── TABLA DE IDENTIFICACIÓN ── function tablaIdentificacion(p, cfg, col) { const date = new Date().toLocaleDateString('es-ES',{day:'2-digit',month:'long',year:'numeric'}); const rows = [ ['Código del protocolo', `PRO-${p.id.toUpperCase().replace(/_/g,'-')}-001`, 'Versión', `1.0 — ${date}`], ['Elaborado por', `Dirección del Centro — ${cfg.director}`, 'Normativa', p.normativa], ['Centro', cfg.nombre, 'Empresa', cfg.empresa], ['Ámbito', 'Todo el personal asistencial y directivo del centro', 'Próxima revisión', 'Anual o ante cambio normativo'], ]; return ` ${rows.map((r,i) => ` ${tcell(r[0], 2000, 'EEF3FA', true, col, 18)} ${tcell(r[1], 2680, i%2===0?'FFFFFF':'F8F8F8', false, '222222', 18)} ${tcell(r[2], 2000, 'EEF3FA', true, col, 18)} ${tcell(r[3], 2680, i%2===0?'FFFFFF':'F8F8F8', false, '222222', 18)} `).join('')} `; } // ── DIAGRAMA DE FLUJO EN TABLA ── function diagramaFlujo(p, col, colLight, colWarnBg, colWarn, colGreenBg, colGreen) { const pasos = p.sections.map((s, i) => ({n: i+1, t: s.t})); const nBgCol = col; // azul corporativo function bloqueInicio(texto, sub) { return ` ${x(texto)} ${x(sub)} `; } function flecha() { return ` `; } function bloquePaso(n, titulo, fill, textCol) { return ` PASO ${n} — ${x(titulo.toUpperCase())} `; } function bloqueDecision(fill, textCol) { return ` ◆ DECISION ✔ SI: continuar proceso | ✖ NO: implementar alternativas `; } const fills = ['EEF3FA','FDE8E8','EEF3FA','E6F4ED']; const textCols = [col, colWarn, col, colGreen]; let rows = ''; rows += bloqueInicio('▶ INICIO', 'Nuevo caso / Revisión periódica'); rows += flecha(); if (pasos.length > 0) rows += bloqueDecision('FDF0E0', '8C4A00'); rows += flecha(); pasos.forEach((paso, i) => { const fill = fills[i % fills.length]; const tc = textCols[i % textCols.length]; rows += bloquePaso(paso.n, paso.t, fill, tc); rows += flecha(); }); rows += bloqueInicio('■ REGISTRO Y CIERRE', 'Historia clínica · Sistema de calidad · Revisión periódica'); return ` ${rows} `; } // ── REGISTRO COLECTIVO ── function registroColectivo(col) { const headerCells = ['N.º','Nombre y apellidos completos','Categoría profesional','Turno habitual','Fecha de recepción','Firma','Observaciones']; const widths = [280, 2400, 1920, 1000, 1300, 1260, 1200]; const fills = (i) => i % 2 === 0 ? 'FFFFFF' : 'F5F5F5'; const header = ` ${headerCells.map((t,i) => tcell(t, widths[i], col, true, 'FFFFFF', 18, 'center')).join('')} `; const rows = Array.from({length: 25}, (_,i) => ` ${tcell(String(i+1), widths[0], fills(i), false, '888888', 18, 'center')} ${tcell('', widths[1], fills(i), false, '000000', 18)} ${tcell('', widths[2], fills(i), false, '000000', 18)} ${tcell('', widths[3], fills(i), false, '000000', 18)} ${tcell('', widths[4], fills(i), false, '000000', 18)} ${tcell('', widths[5], fills(i), false, '000000', 18)} ${tcell('', widths[6], fills(i), false, '000000', 18)} `).join(''); const footer = ` Responsable de la recogida de firmas: ________________________ Cargo: _______________________ Fecha de cierre: ___/___/________ `; return ` ${header}${rows}${footer} `; } // ── NARRATIVA EXPANDIDA POR SECCIÓN ── function narrativaSecciones(p, col) { const alertColors = { red: { fill: 'FDE8E8', border: '8C1A1A' }, amber: { fill: 'FDF0E0', border: '8C4A00' }, green: { fill: 'E6F4ED', border: '1A6B45' }, norm: { fill: 'EEF3FA', border: col } }; const ac = alertColors[p.alertType] || alertColors.norm; let xml = ''; xml += h1('1. Introducción y justificación', col); xml += hRule(col); xml += pJust(`El presente protocolo establece el marco de actuación del personal de ${p.cfg_nombre || 'la Residencia'} en materia de "${p.name}". Su elaboración responde a la necesidad de garantizar una atención de calidad, segura y centrada en la persona, en cumplimiento de la normativa vigente y de los estándares del sistema de gestión de calidad del centro.`); xml += pJust(`Este documento es de aplicación obligatoria para todo el personal que interviene en el proceso descrito, independientemente de su categoría profesional o turno de trabajo. Su cumplimiento constituye una responsabilidad individual y colectiva del equipo asistencial.`); xml += spacer(1); xml += h1('2. Objetivo general', col); xml += hRule(col); xml += pJust(`El objetivo central de este protocolo es garantizar una actuación homogénea, segura y documentada en todas las situaciones relacionadas con "${p.name}" que puedan presentarse en el centro, asegurando la protección y el bienestar de los residentes y del personal implicado.`); xml += spacer(1); xml += h1('3. Normativa de referencia', col); xml += hRule(col); xml += alertBox('Marco normativo', p.normativa, 'EEF3FA', col); xml += spacer(0.5); xml += pJust('El protocolo ha sido elaborado atendiendo a la normativa indicada, así como a las guías de práctica clínica y documentos técnicos de referencia de las sociedades científicas y organismos competentes. Cualquier modificación normativa relevante motivará la revisión y actualización del presente documento antes de la fecha de revisión ordinaria.'); xml += spacer(1); xml += h1('4. Ámbito de aplicación y personal responsable', col); xml += hRule(col); xml += pJust(`Este protocolo es de aplicación en todos los turnos y unidades de la residencia, afectando a la totalidad del personal asistencial, directivo y de administración. La Dirección del centro es responsable de garantizar que el protocolo sea conocido, comprendido y aplicado por todos los profesionales implicados.`); xml += spacer(1); // ALERTA PRINCIPAL xml += alertBox('⚠ PUNTO CRÍTICO DE ACTUACIÓN', p.alerta, ac.fill, ac.border); xml += spacer(1); // SECCIONES DEL PROTOCOLO xml += h1('5. Procedimiento detallado de actuación', col); xml += hRule(col); xml += pJust('A continuación se desarrollan las fases del procedimiento que deben seguirse ante cualquier situación contemplada en este protocolo. Cada fase incluye las acciones concretas a realizar, los responsables de su ejecución y los criterios de calidad exigibles.'); xml += spacer(0.5); p.sections.forEach((sec, si) => { xml += h2(`${si + 1}. ${sec.t}`, col); xml += pJust(`Esta fase comprende las siguientes acciones, que deben realizarse en el orden indicado y con los estándares de calidad establecidos por el centro:`); sec.items.forEach(item => { xml += pBullet(item); }); xml += spacer(0.5); }); xml += spacer(1); xml += h1('6. Registros y documentación requerida', col); xml += hRule(col); xml += pJust('Todo el proceso debe quedar documentado en el sistema de información del centro (Resiplus o equivalente) y en los registros físicos correspondientes. La documentación generada tiene valor jurídico y de calidad, y debe conservarse durante el tiempo establecido por la normativa vigente.'); xml += pBullet('Parte de turno o incidencia con descripción detallada de los hechos y actuaciones realizadas.'); xml += pBullet('Historia clínica del residente (si aplica), con fecha, hora y firma del profesional responsable.'); xml += pBullet('Notificaciones a la familia del residente cuando la situación lo requiera.'); xml += pBullet('Comunicación al organismo supervisor de la Comunidad de Madrid en los plazos establecidos, cuando la normativa así lo exija.'); xml += spacer(1); xml += h1('7. Formación y actualización del personal', col); xml += hRule(col); xml += pJust('La efectividad de este protocolo depende directamente del nivel de formación y concienciación del equipo. La Dirección del centro garantiza las siguientes acciones formativas:'); xml += pBullet('Formación inicial para todo el personal de nueva incorporación, dentro del primer mes desde el inicio de la relación laboral.'); xml += pBullet('Sesión de actualización anual para todo el equipo, con presentación de los indicadores de calidad relacionados con este protocolo.'); xml += pBullet('Difusión de cualquier modificación del protocolo antes de su entrada en vigor.'); xml += spacer(1); return xml; } // ════════════════════════════════════════════════════ // 3. ENSAMBLADOR PRINCIPAL // ════════════════════════════════════════════════════ function ensamblarDocx(p, cfg) { const col = hexColor(cfg.color || '#1E4D8C'); const colLight = 'EEF3FA'; const colWarn = '8C1A1A'; const colWarnBg = 'FDE8E8'; const colGreen = '1A6B45'; const colGreenBg = 'E6F4ED'; const date = new Date().toLocaleDateString('es-ES',{day:'2-digit',month:'long',year:'numeric'}); // Enriquecer protocolo con config p.cfg_nombre = cfg.nombre; const header = ` `; const footer = ` `; // ── PORTADA ── let body = ''; body += ` ${pCenter(cfg.nombre.toUpperCase(), 26, 'FFFFFF', true, 0, 60)} ${pCenter(cfg.empresa + ' · ' + cfg.direccion, 18, 'BDD0EE', false, 0, 0)} ${pCenter('Tel: ' + cfg.telefono + ' · ' + cfg.email, 18, 'BDD0EE', false, 0, 0)} `; body += spacer(2); body += pCenter(p.name.toUpperCase(), 32, col, true, 0, 80); body += pCenter(p.sub, 22, '555555', false, 0, 180); body += spacer(1); body += tablaIdentificacion(p, cfg, col); body += spacer(2); body += pageBreak(); // ── CONTENIDO NARRATIVO ── body += narrativaSecciones(p, col); body += pageBreak(); // ── DIAGRAMA DE FLUJO ── body += h1('8. Diagrama de flujo del proceso', col); body += hRule(col); body += pJust('El siguiente diagrama resume de forma visual el flujo de actuación establecido en este protocolo:'); body += spacer(0.5); body += diagramaFlujo(p, col, colLight, colWarnBg, colWarn, colGreenBg, colGreen); body += spacer(1); body += pageBreak(); // ── EVIDENCIA DE APROBACIÓN ── body += h1('9. Evidencia de aprobación del protocolo', col); body += hRule(col); body += pJust('El presente protocolo ha sido elaborado, revisado y aprobado por las personas abajo firmantes, que asumen la responsabilidad de su correcta implantación, difusión y seguimiento en el centro.'); body += spacer(0.5); // Tabla de firmas de dirección body += ` ${tcell('Elaborado por', 3120, col, true, 'FFFFFF', 20, 'center')} ${tcell('Revisado por', 3120, col, true, 'FFFFFF', 20, 'center')} ${tcell('Aprobado por', 3120, col, true, 'FFFFFF', 20, 'center')} ${x(cfg.director)} Director/a del Centro Firma: ____________________ Fecha: ___/___/________ Responsable asistencial Médico/a Coordinador/a o DUE Firma: ____________________ Fecha: ___/___/________ ${x(cfg.nombre)} Alta Dirección del Centro Firma: ____________________ Fecha: ___/___/________ `; body += spacer(2); // ── REGISTRO COLECTIVO ── body += h1('10. Registro colectivo de entrega y lectura del protocolo', col); body += hRule(col); body += pJust('La implantación efectiva de un protocolo requiere no solo su aprobación por la dirección, sino la evidencia documentada de que cada profesional que debe aplicarlo lo ha recibido, leído y comprendido. Este registro tiene valor jurídico y de calidad: certifica que el protocolo ha sido formalmente entregado a todo el personal implicado en su aplicación.'); body += spacer(0.5); body += alertBox('AVISO LEGAL', 'La firma en este registro certifica que el/la trabajador/a ha recibido el documento, ha tenido la oportunidad de leerlo y comprende las obligaciones que se derivan de su aplicación. En caso de dudas, debe dirigirse a su superior inmediato o a la Dirección antes de firmar.', 'FDF0E0', '8C4A00'); body += spacer(0.5); body += registroColectivo(col); body += spacer(1); // Control de versiones body += h2('Control de versiones', col); body += ` ${tcell('Versión', 1200, col, true, 'FFFFFF', 18, 'center')} ${tcell('Fecha', 1560, col, true, 'FFFFFF', 18, 'center')} ${tcell('Descripción del cambio', 4560, col, true, 'FFFFFF', 18)} ${tcell('Autor/a', 2040, col, true, 'FFFFFF', 18)} ${tcell('1.0', 1200, 'FFFFFF', false, '000000', 18, 'center')} ${tcell(date, 1560, 'FFFFFF', false, '000000', 18, 'center')} ${tcell('Versión inicial del protocolo.', 4560, 'FFFFFF', false, '000000', 18)} ${tcell(cfg.director, 2040, 'FFFFFF', false, '000000', 18)} ${tcell('2.0 (prevista)', 1200, 'F5F5F5', false, '888888', 18, 'center')} ${tcell('Anual', 1560, 'F5F5F5', false, '888888', 18, 'center')} ${tcell('Revisión ordinaria anual o ante cambio normativo.', 4560, 'F5F5F5', false, '888888', 18)} ${tcell('—', 2040, 'F5F5F5', false, '888888', 18)} `; body += spacer(2); body += pCenter(`— Fin del Protocolo · Versión 1.0 · ${date} —`, 18, '888888', false, 60, 0); // Header/Footer XML const headerXml = ` ${x(p.name.toUpperCase())} ${x(cfg.nombre)} · ${x(cfg.iso)} `; const footerXml = ` ${x(cfg.empresa)} · ${x(cfg.iso)} · Uso interno Pag. PAGE `; const numXml = ` `; const stylesXml = ` `; const files = { '[Content_Types].xml': ` `, '_rels/.rels': ` `, 'word/_rels/document.xml.rels': ` `, 'word/document.xml': header + body + footer, 'word/numbering.xml': numXml, 'word/styles.xml': stylesXml, 'word/header1.xml': headerXml, 'word/footer1.xml': footerXml, }; return buildZip(files); } // ════════════════════════════════════════════════════ // 4. FUNCIÓN PÚBLICA — llamada desde el HTML // ════════════════════════════════════════════════════ function generarWord(protocolo, configuracion) { try { const cfg = configuracion || window.CFG || { nombre: 'Residencia María Auxiliadora', empresa: 'Geriátrico CAOR S.L.', direccion: 'C/ Eugenia de Montijo, 79 — 28025 Madrid', telefono: '91 465 18 23', email: 'direccion@maria-auxiliadora.es', director: 'Juan José Bote Valero', iso: 'ISO 9001:2015', color: '#1E4D8C' }; const bytes = ensamblarDocx(protocolo, cfg); const blob = new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; const nombreLimpio = protocolo.name.replace(/[^a-zA-Z0-9áéíóúÁÉÍÓÚñÑ\s]/g,'').replace(/\s+/g,'_').substring(0,50); const centroLimpio = cfg.nombre.replace(/\s+/g,'_').substring(0,30); a.download = `Protocolo_${nombreLimpio}_${centroLimpio}.docx`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); return true; } catch(e) { console.error('Error generando Word:', e); alert('Error al generar el documento: ' + e.message); return false; } } // Exportar para uso en Node.js (tests) o en navegador // ════════════════════════════════════════════════════ // CONFIGURACIÓN CORPORATIVA // ════════════════════════════════════════════════════ window.CFG = { nombre: 'Residencia María Auxiliadora', empresa: 'Geriátrico CAOR S.L.', direccion: 'C/ Eugenia de Montijo, 79 — 28025 Madrid', telefono: '91 465 18 23 / 91 461 00 54', email: 'direccion@maria-auxiliadora.es', director: 'Juan José Bote Valero', iso: 'ISO 9001:2015', color: '#1E4D8C' }; function openModal() { Object.keys(window.CFG).forEach(k => { const el = document.getElementById('cfg-' + k); if (el) el.value = window.CFG[k]; }); document.getElementById('configModal').classList.add('open'); } function closeModal() { document.getElementById('configModal').classList.remove('open'); } function saveConfig() { ['nombre','empresa','direccion','telefono','email','director','iso','color'].forEach(k => { const el = document.getElementById('cfg-' + k); if (el && el.value) window.CFG[k] = el.value; }); document.getElementById('header-nombre').textContent = window.CFG.nombre; document.getElementById('header-sub').textContent = window.CFG.empresa + ' · ' + window.CFG.iso; document.getElementById('footer-text').textContent = window.CFG.nombre + ' · ' + window.CFG.empresa + ' · Actualizado 2025/2026'; closeModal(); } // ════════════════════════════════════════════════════ // DATOS — 17 PROTOCOLOS // ════════════════════════════════════════════════════ const PROTOCOLS = [ {id:'med_peligrosos',name:'Manipulación segura de medicamentos',sub:'Recepción, preparación, administración y residuos',badge:'💊',badgeBg:'#EAF3DE',tags:['clinico','tuyo'],normativa:'ISO 9001 · ISO 45001 · RD 1718/2010',alertType:'red',alerta:'Los residuos de medicamentos peligrosos van SIEMPRE en contenedores rígidos azules con símbolo citotóxico. Cerrar al llegar a 2/3 de su capacidad.',sections:[{t:'Recepción y almacenamiento',items:['Revisar integridad de los envases al recibirlos','Usar guantes si son medicamentos peligrosos','Guardar en zona señalizada del botiquín','Lavado de manos tras la manipulación']},{t:'Preparación en zona limpia',items:['Preparar en zona limpia y sin corrientes de aire','Usar mesa de fácil desinfección','Lavado de manos antes y después de la preparación','Usar guantes de nitrilo en todo momento','Usar triturador específico si se requiere triturar','No comer ni beber durante la preparación']},{t:'Los 5 correctos de la administración',items:['1. Paciente correcto — verificar identidad','2. Medicamento correcto — leer etiqueta','3. Dosis correcta — comprobar prescripción','4. Vía correcta — oral/IV/SC/tópica','5. Hora correcta — respetar pauta prescrita']},{t:'Actuación ante derrame',items:['Colocar EPI completo antes de actuar','Señalizar la zona con señal de peligro','Recoger con material absorbente de un solo uso','Depositar residuos en contenedor sanitario biohazard','Desinfectar superficie con alcohol 70°']}]}, {id:'defuncion',name:'Defunción turno de noche',sub:'Protocolo paso a paso — fallecimiento imprevisto',badge:'🕯️',badgeBg:'#F1EFE8',tags:['clinico','organizacion','tuyo'],normativa:'Ley 20/2011 Registro Civil · Decreto 95/2001 CM · Resiplus',alertType:'red',alerta:'Si la defunción es PREVISTA: informe médico + fotocopia DNI + cartilla SS preparados. Si es IMPREVISTA, seguir los 16 pasos del protocolo en orden.',sections:[{t:'Pasos 1–7: Acciones inmediatas en el centro',items:['1. Dejar al difunto en la cama, tapado, sin moverlo','2. Encender ordenador de recepción (clave: 4321)','3. Abrir Resiplus — USUARIO: URGENCIAS / CLAVE: 12','4. Ir a icono Residentes y buscar al fallecido','5. Menú lateral: signo + junto a MÉDICO, clic INFORME MÉDICO','6. Imprimir el informe médico','7. Llamar al 061 para que venga médico a certificar la defunción']},{t:'Pasos 8–16: Certificación, traslado y familia',items:['8. Facilitar al médico SAMUR el informe y narrar los hechos','9. Recibir el CERTIFICADO DE DEFUNCIÓN del SAMUR','10. Bajar al difunto al tanatorio, saco, nombre y apellidos en extremo inferior','11. En Resiplus: menú + junto a GENERAL, clic CONTACTOS','12. Llamar al primer familiar con tacto y comunicar el fallecimiento','13. Indicar que traigan DNI original y póliza de decesos','14. Llamar a la funeraria: 91 524 24 24','15. Facilitar espacio para que técnico y familia acuerden trámites','16. Exigir HOJA DE RETIRADA DEL EXITUS y destino del tanatorio','17. Dejar hoja en mostrador de dirección y hacer parte de incidencia en Resiplus']}]}, {id:'familias',name:'Gestión de conflictos con familias',sub:'Neutralidad, escucha activa y registro de incidencias',badge:'🤝',badgeBg:'#E6F1FB',tags:['social','organizacion','tuyo'],normativa:'Ley 39/2006 · ORDEN 2680/2024 CM',alertType:'amber',alerta:'El centro mantiene NEUTRALIDAD ABSOLUTA en disputas entre familiares. Nunca tomar partido. Toda incidencia relevante debe quedar registrada en el sistema de calidad.',sections:[{t:'Identificar el tipo de familia',items:['Familia Inspectora: revisión constante, incrementar comunicación proactiva','Familia Culpable/Compensadora: alta exigencia emocional, contención y acompañamiento','Familia Ausente Exigente: pocas visitas pero muchas reclamaciones, documentar cada comunicación','Familia Dividida: conflictos entre hermanos, aplicar igualdad informativa','Familia Hereditaria: conflictos patrimoniales, no participar, derivar a vías legales']},{t:'Principios de actuación (siempre)',items:['Escuchar al familiar con calma y sin interrumpir','Mantener neutralidad absoluta, no tomar partido','Explicar protocolos y funcionamiento del centro','Redirigir SIEMPRE la conversación hacia el bienestar del residente','Dar la misma información a TODOS los familiares por igual','Registrar la incidencia si el conflicto persiste']}]}, {id:'desnutricion',name:'Prevención y tratamiento de la desnutrición',sub:'Cribado MNA, texturas IDDSI, soporte nutricional',badge:'🥗',badgeBg:'#EAF3DE',tags:['clinico','tuyo'],normativa:'PRO-NUT-001 · SEGG · ESPEN 2019 · IDDSI · Reg. UE 1169/2011',alertType:'amber',alerta:'Residentes con MNA < 17 requieren INTERVENCIÓN INMEDIATA con valoración médica urgente. Actualizar Excel de seguimiento nutricional al menos trimestralmente.',sections:[{t:'Cribado nutricional (MNA)',items:['Realizar MNA completo al ingreso en primeras 48-72 h','MNA mayor o igual a 24: estado normal, revisión semestral','MNA 17-23,5: riesgo, plan nutricional más suplemento oral','MNA menor de 17: desnutrición, valoración médica urgente e inmediata','Repetir MNA cada 3 meses o ante cambio clínico relevante','Aplicar EAT-10 para cribado de disfagia, resultado mayor o igual a 3 es riesgo']},{t:'Adaptación de texturas IDDSI',items:['IDDSI 7: normal, dieta basal sin restricciones','IDDSI 6: blanda/húmeda, fácil masticación','IDDSI 5: picada/húmeda, disfagia leve, trozos menores de 4 mm','IDDSI 4: puré fino, disfagia moderada, sin grumos','IDDSI 3: licuada/pudding, disfagia severa','IDDSI 1-2: néctar/miel, líquidos espesados con espesante certificado','Documentar nivel IDDSI prescrito en expediente del residente']},{t:'Los 14 alérgenos obligatorios RD 126/2015',items:['Gluten, Crustáceos, Huevo, Pescado, Cacahuetes, Soja, Lácteos, Frutos secos','Apio, Mostaza, Sésamo, SO2/Sulfitos, Altramuces, Moluscos','Registrar alergias en historia clínica y ficha de cocina','Menú semanal con indicación de alérgenos expuesto en comedor y recepción']}]}, {id:'acoso',name:'Protocolo frente al acoso moral y sexual',sub:'Prevención, denuncia y procedimiento interno',badge:'🛡️',badgeBg:'#FCEBEB',tags:['laboral','tuyo'],normativa:'LO 3/2007 · LO 10/2022 · Ley 15/2022 · ET · Convenio 190 OIT',alertType:'red',alerta:'Denuncia a: aurora@maria-auxiliadora.es / juan@maria-auxiliadora.es. Comisión convocada en 3 días hábiles. Instrucción máxima: 10 días laborales.',sections:[{t:'Tipos de acoso a reconocer',items:['Acoso moral: conductas reiteradas que dañan la dignidad, aislar, ridiculizar, sobrecargar','Acoso sexual: comportamiento verbal/físico de naturaleza sexual no deseado','Acoso por razón de sexo: trato desfavorable por ser hombre/mujer','Violencia LGTBI en el trabajo: discriminación por orientación o identidad de género','Ciberviolencia: amenazas, difusión no consentida de imágenes, suplantación online']},{t:'Denuncia formal y tramitación',items:['Incluir nombre y datos de ambas partes en la denuncia','Narración detallada de los hechos con fechas y lugares','Testigos y documentos o pruebas disponibles','Dirección convoca la Comisión en 3 días desde la denuncia','Medidas cautelares si procede: separación física o suspensión con sueldo','Plazo máximo de instrucción: 10 días laborales prorrogables 3 días','Las represalias contra el/la denunciante son falta grave o muy grave']}]}, {id:'calor',name:'Plan actuación frente a golpes de calor',sub:'Campaña 2025 — niveles de alerta AEMET',badge:'🌡️',badgeBg:'#FAEEDA',tags:['seguridad','clinico','tuyo'],normativa:'Plan Nacional de Actuaciones Preventivas MSSSI · AEMET',alertType:'red',alerta:'Ante desorientación, vómitos o pérdida de consciencia: trasladar INMEDIATAMENTE a zona fresca + paños húmedos + llamar al 112. No esperar.',sections:[{t:'Nivel Verde: normalidad, menos de 36,5 grados',items:['Consultar AEMET diariamente y activar protocolo según nivel','Identificar residentes de mayor riesgo: polimedicados, cardiacos, neurológicos','Verificar stock de ventiladores, rociadores y sales de rehidratación oral','Adaptar menú con alimentos ricos en agua: fruta, ensaladas, gazpacho','Comprobar que residentes tienen ropa de algodón ligera y holgada']},{t:'Nivel Amarillo: precaución, más de 36,5 grados',items:['Trasladar residentes de alto riesgo a zonas frescas menores de 26 grados','Registrar temperaturas interiores regularmente','Ofrecer bebida fría cada 1-2 horas, mínimo 2-2,5 litros al día por residente','Consultar con médico posibles cambios en tratamiento o medicación']},{t:'Nivel Rojo: alto riesgo, más de 38,5 grados',items:['Mantener cortinas y ventanas cerradas mientras exterior supere al interior','Suspender actividades exteriores entre las 12:00 y las 18:00','Monitorizar constantes: temperatura, tensión, pulso, hidratación','Kit antigolpes preparado: paños húmedos, agua/isotónicos, abanicos','Línea directa con emergencias: 112 y médico de Atención Primaria']}]}, {id:'lgtbi',name:'Atención y no discriminación LGTBI+',sub:'Derechos, confidencialidad y procedimiento ante discriminación',badge:'🌈',badgeBg:'#EEEDFE',tags:['social','laboral','tuyo'],normativa:'Ley 4/2023 · Ley 3/2016 CM · LO 3/2007 · Ley 15/2022',alertType:'amber',alerta:'La información sobre orientación sexual o identidad de género es ESTRICTAMENTE CONFIDENCIAL. Su divulgación sin consentimiento constituye vulneración de derechos y puede dar lugar a sanción grave.',sections:[{t:'Derechos de las personas residentes LGTBI+',items:['Ser tratadas por el NOMBRE SENTIDO y pronombre elegido','Que su orientación/identidad no sea cuestionada, ridiculizada ni patologizada','Mantener relaciones de pareja en igualdad de condiciones','Acceder a espacios según su identidad de género','Que su historia de vida y familia elegida sean reconocidas y respetadas']},{t:'Actuación ante discriminación o LGTBIfobia',items:['Intervenir de forma INMEDIATA para poner fin a la conducta inapropiada','Prestar apoyo a la persona afectada y garantizar su seguridad','Registrar la incidencia: fecha, descripción, personas implicadas, testigos','Dirección inicia investigación confidencial en 5 días hábiles','Seguimiento para verificar que la situación no se repite']}]}, {id:'sujeciones',name:'Plan de reducción de sujeciones',sub:'Versión Abril 2026 — reducción del 83% desde 2023',badge:'🔓',badgeBg:'#E1F5EE',tags:['clinico','seguridad','tuyo'],normativa:'Decreto 55/2020 CM · Prescriptora: Dra. Nadia Evangelina Yan Acevedo',alertType:'green',alerta:'SITUACIÓN ABRIL 2026: 18 residentes con sujeción activa (~31%). Solo camas ultra bajas (13) y barandillas cortas (5). Cinturones, petos y barandillas largas: 0 desde 2024-2025.',sections:[{t:'Contraindicaciones absolutas: NUNCA usar sujeción',items:['Como castigo ante conductas disruptivas o molestas','Por conveniencia o comodidad del personal','Como sustituto de la vigilancia activa del equipo','Para evitar caídas sin valoración individualizada previa']},{t:'Requisitos para autorizar una sujeción',items:['Prescripción médica escrita con tipo de dispositivo, horario y revisión','Valoración con escalas: Tinetti, Barthel, Norton','Consentimiento informado firmado por residente o tutor legal (R03_PR05)','Registro en historia clínica y en Excel de seguimiento de sujeciones']},{t:'Control y revisión del programa',items:['Revisión médica mensual de cada sujeción activa: R17_PR05 y R18_PR05','Próxima revisión programada: 01/07/2026','Objetivo fin 2026: 15 o menos residentes con sujeción, menos del 25%','Objetivo fin 2027: 10 o menos residentes con sujeción, menos del 17%','Comunicar a familia cualquier modificación en la sujeción de su familiar']}]}, {id:'reglamento',name:'Reglamento de convivencia 2025/2026',sub:'Derechos, deberes y normas — residentes, personal y familias',badge:'📋',badgeBg:'#F1EFE8',tags:['organizacion','social','tuyo'],normativa:'ORDEN 2680/2024 CM · Rev. 1 · Firmado: Juan José Bote Valero',alertType:'amber',alerta:'De obligado cumplimiento para residentes, personal propio y subcontratado, familiares y visitantes. Revisión anual con participación de todos los grupos implicados.',sections:[{t:'Derechos de los residentes',items:['Trato digno y sin discriminación de ningún tipo','Información clara sobre servicios, actividades y cambios relevantes','Participación en decisiones que afecten su vida cotidiana','Intimidad y confidencialidad de datos personales y médicos','Atención personalizada centrada en sus necesidades y preferencias','Protección contra abuso, maltrato o negligencia']},{t:'Deberes y comportamientos sancionables',items:['Respetar las normas y decisiones colectivas del centro','Cuidar las instalaciones y el mobiliario','Mantener trato respetuoso con personal, otros residentes y visitantes','Violencia física o verbal: falta grave','Discriminación por raza, género, orientación sexual, religión: no tolerado','Cualquier forma de acoso físico, verbal o psicológico: sanción']},{t:'Normas de visita para familias',items:['Registrarse a la llegada y salida del centro','Respetar los horarios de visita establecidos','Tratar al personal con cortesía y seguir sus indicaciones','Respetar la privacidad de TODOS los residentes','Sugerencias y quejas a través de los canales establecidos']}]}, {id:'contencion',name:'Contención mecánica y sujeciones',sub:'Autorización, registro y revisión periódica',badge:'🔒',badgeBg:'#FCEBEB',tags:['clinico','seguridad'],normativa:'Decreto 55/2020 CM · Guía IMSERSO 2021',alertType:'red',alerta:'Requiere consentimiento informado del residente o representante legal y prescripción médica documentada.',sections:[{t:'Indicación y autorización',items:['Evaluación médica previa y documentación de la indicación','Consentimiento informado del residente o tutor legal','Prescripción escrita con tipo de sujeción, horario y revisión','Registro en historia clínica y en libro de sujeciones']},{t:'Aplicación, vigilancia y revisión',items:['Colocación correcta del dispositivo, dos dedos de holgura','Cambios posturales cada 2 horas mínimo','Supervisión visual cada 30 minutos, registrar en hoja de vigilancia','Valoración de la piel en cada cambio postural','Retirada temporal para aseo, comidas y movilización activa','Revisión médica semanal de la necesidad de la sujeción','Formación continua sobre alternativas a la sujeción']}]}, {id:'infeccion',name:'Infección / brote epidémico',sub:'COVID-19, gripe, gastroenteritis, sarna...',badge:'🦠',badgeBg:'#FAEEDA',tags:['clinico','seguridad'],normativa:'Orden 668/2020 CM · Protocolo CCAA brotes residencias 2023',alertType:'red',alerta:'Ante cualquier caso sospechoso notificar en menos de 24h al Servicio de Epidemiología de la CM.',sections:[{t:'Detección y contención inmediata',items:['Vigilancia activa diaria de temperatura y síntomas en todos los residentes','Aislamiento inmediato del caso sospechoso en habitación individual','Toma de muestras según protocolo: PCR o test antigénico','Comunicación a Epidemiología CM en menos de 24 h si hay 2 o más casos relacionados','EPIs adecuados: mascarilla FFP2, guantes, bata','Cohortización de casos confirmados y contactos estrechos','Refuerzo de higiene de manos y superficies, frecuencia doble','Restricción de visitas en unidades afectadas, comunicar a familias']},{t:'Seguimiento y cierre del brote',items:['Registro diario de casos nuevos y evolución','Reunión de coordinación del equipo cada 48 h durante el brote','Declaración de fin de brote: 2 períodos de incubación sin nuevos casos','Elaboración de informe final y lecciones aprendidas']}]}, {id:'medicamentos',name:'Administración de medicamentos',sub:'Prescripción, dispensación y registro',badge:'💉',badgeBg:'#E1F5EE',tags:['clinico'],normativa:'Ley 29/2006 · Orden 1598/2015 CM · RD 1718/2010',alertType:'red',alerta:'Verificar siempre los 5 correctos antes de cada administración: residente correcto, medicamento, dosis, vía y hora.',sections:[{t:'Prescripción, preparación y administración',items:['Solo el médico prescribe, enfermería no modifica dosis sin indicación escrita','Hoja de tratamiento actualizada en historia clínica','Conciliación farmacológica en ingresos, traslados y altas hospitalarias','Preparación en zona limpia y sin interrupciones','Identificación del residente antes de administrar: pulsera o foto','Verificación de los 5 correctos en cada administración','Registro inmediato en hoja de administración tras la toma']},{t:'Custodia e incidencias',items:['Custodia segura de estupefacientes: caja bajo llave y libro de registro','Notificación inmediata al médico de cualquier error de medicación','Registro del incidente en sistema interno del centro','Comunicación a familia si el error tiene repercusión clínica','Análisis causa raíz del error y medidas preventivas documentadas']}]}, {id:'upp',name:'Prevención de úlceras por presión (UPP)',sub:'Valoración, prevención y cuidado de heridas',badge:'🩹',badgeBg:'#E1F5EE',tags:['clinico'],normativa:'GNEAUPP 2023 · EPUAP/NPIAP guidelines',alertType:'red',alerta:'Toda UPP de categoría 3 o 4 debe registrarse como incidente de seguridad y comunicarse a la familia y al organismo supervisor de la CM en menos de 24h.',sections:[{t:'Valoración del riesgo con Braden',items:['Escala de Braden al ingreso y cada 3 meses o ante cambio clínico','Braden 9 o menos: riesgo muy alto, revisión en cada turno','Braden 10-12: riesgo alto, SEMP dinámica, revisión semanal','Braden 13-14: riesgo moderado, SEMP, revisión quincenal','Inspección visual de la piel en cada aseo, especial atención a prominencias','Fotografía de lesiones existentes en el ingreso con fecha y firma']},{t:'Medidas preventivas',items:['Plan de cambios posturales pautado según riesgo, cada 2-4 horas','SEMP estática para riesgo bajo-moderado, SEMP dinámica para riesgo alto','Hidratación cutánea diaria con crema emoliente','Protección de talones con dispositivos específicos','Nutrición adecuada, valorar suplementos si IMC menor de 20']},{t:'Gestión de la lesión existente',items:['Clasificación según categoría GNEAUPP: I-IV y sin clasificar','Desbridamiento y cura según protocolo de heridas del centro','Registro fotográfico semanal de la evolución','Interconsulta con médico si no hay mejoría en 2 semanas','Notificación a familia y registro en historia clínica']}]}, {id:'agresiones',name:'Agresiones y conflictos entre residentes',sub:'Intervención, registro y seguimiento',badge:'⚠️',badgeBg:'#FAECE7',tags:['seguridad','social'],normativa:'Ley 39/2006 · Protocolo CM conductas disruptivas',alertType:'red',alerta:'Si hay lesión física grave o agresión sexual: notificar de inmediato a la dirección y valorar denuncia policial.',sections:[{t:'Intervención inmediata',items:['Separación segura de los implicados sin usar fuerza excesiva','Atención sanitaria a heridos, llamar al 112 si hay lesiones graves','Retirar a testigos, otros residentes, de la zona','Notificación inmediata al coordinador o director de turno']},{t:'Registro, análisis y seguimiento',items:['Parte de incidencia con hora, lugar, descripción y testigos','Valoración del agresor para descartar causa tratable','Reunión del equipo interdisciplinar en menos de 48 horas','Revisión del PAI de ambas partes implicadas','Seguimiento psicológico del residente víctima','Evaluación de medidas preventivas a los 30 días']}]}, {id:'desaparicion',name:'Desaparición de residente',sub:'Búsqueda, notificación y prevención',badge:'🔍',badgeBg:'#EEEDFE',tags:['seguridad'],normativa:'Art. 490 CP · Protocolo personas desaparecidas Guardia Civil',alertType:'red',alerta:'Si a los 30 minutos no se localiza al residente, llamar al 112 y a la Policía/Guardia Civil. NO esperar las 24 horas.',sections:[{t:'Búsqueda inmediata: 0-30 minutos',items:['Confirmar ausencia: habitación, zonas comunes, jardín y aseos','Activar protocolo interno, avisar a todo el personal de turno','Revisar accesos: cámaras, registro de salidas, puerta de emergencia','Consultar a residentes que puedan haber visto al desaparecido']},{t:'Notificación y recuperación',items:['Llamar al 112 y a la Policía/Guardia Civil, no esperar 24 horas','Notificar a la familia de inmediato','Facilitar a la policía: foto reciente, descripción física, ropa, patología','Valoración médica completa al regresar al centro','Comunicación al organismo supervisor de la CM en menos de 24 horas']}]}, {id:'acogida',name:'Acogida e ingreso de nuevo residente',sub:'Bienvenida, valoración y adaptación durante 3 meses',badge:'🏠',badgeBg:'#E6F1FB',tags:['organizacion','social'],normativa:'Decreto 143/2010 CM · ORDEN 2680/2024',alertType:'amber',alerta:'El periodo de adaptación es de 3 meses. Seguimiento reforzado durante las primeras 4 semanas.',sections:[{t:'Antes del ingreso y día del ingreso',items:['Entrevista previa con residente y familia por trabajadora social','Recepción y revisión de documentación: informe médico, DNI, resolución IMSERSO','Bienvenida por el/la director/a y enfermera de referencia','Visita guiada al centro: habitación, comedor, zonas comunes','Firma del contrato de ingreso e información sobre derechos y normas','Valoración médica: anamnesis, constantes, medicación actual','Valoraciones enfermería: Barthel, Braden, Mini-Mental']},{t:'Seguimiento primeras semanas y meses',items:['Seguimiento diario por enfermera de referencia durante 1ª semana','Reunión interdisciplinar a los 15 días para ajustar PAI','Contacto semanal con familia durante el primer mes','Revisión del PAI definitivo a los 3 meses']}]}, {id:'duelo',name:'Atención al duelo (familia y equipo)',sub:'Acompañamiento tras el fallecimiento de un residente',badge:'🕊️',badgeBg:'#F1EFE8',tags:['social','organizacion'],normativa:'Ley 2/2010 Voluntad Anticipada CM · Guía SECPAL',alertType:'amber',alerta:'El duelo de los profesionales es un riesgo laboral. Incluirlo en el plan de prevención de riesgos psicosociales del centro.',sections:[{t:'Atención a la familia',items:['Comunicación a la familia: presencial o telefónica, personal de referencia','Ofrecer acompañamiento y espacio para despedirse del ser querido','Coordinación con funeraria y trámites administrativos','Comunicar el fallecimiento al equipo antes de que lo oigan por otros canales','Entrega de pertenencias personales con inventario firmado','Carta o llamada de condolencia de la dirección a los 7-10 días']},{t:'Atención al equipo',items:['Espacio de debriefing con el equipo en menos de 48 horas','Reconocer la labor del equipo en el cuidado del fallecido','Supervisión psicológica disponible para profesionales','Ritual de despedida en la unidad si el equipo lo considera adecuado']}]} ]; // ════════════════════════════════════════════════════ // LÓGICA DE CHECKLISTS Y UI // ════════════════════════════════════════════════════ let cbStates = {}; PROTOCOLS.forEach(p => p.sections.forEach((s,si) => s.items.forEach((_,ii) => { cbStates[`${p.id}-${si}-${ii}`] = false; }))); function totalSteps() { let n=0; PROTOCOLS.forEach(p => p.sections.forEach(s => n+=s.items.length)); return n; } function doneSteps() { return Object.values(cbStates).filter(Boolean).length; } function doneProtos() { return PROTOCOLS.filter(p => { let tot=0, dn=0; p.sections.forEach((s,si) => s.items.forEach((_,ii) => { tot++; if(cbStates[`${p.id}-${si}-${ii}`]) dn++; })); return tot>0 && dn===tot; }).length; } function updateGlobalStats() { const tot=totalSteps(), dn=doneSteps(); document.getElementById('s-total').textContent = PROTOCOLS.length; document.getElementById('s-done').textContent = doneProtos(); document.getElementById('s-pct').textContent = tot ? Math.round(dn/tot*100)+'%' : '0%'; } function getProgress(pid) { const p = PROTOCOLS.find(x=>x.id===pid); let tot=0, done=0; p.sections.forEach((s,si) => s.items.forEach((_,ii) => { tot++; if(cbStates[`${pid}-${si}-${ii}`]) done++; })); return { tot, done, pct: tot ? Math.round(done/tot*100) : 0 }; } function updateProgress(pid) { const {done,tot,pct} = getProgress(pid); ['fill-','fill-mini-'].forEach(pre => { const el=document.getElementById(pre+pid); if(el) el.style.width=pct+'%'; }); const lbl=document.getElementById('lbl-'+pid); if(lbl) lbl.textContent=`${done} de ${tot} pasos`; const pm=document.getElementById('pct-mini-'+pid); if(pm) pm.textContent=pct+'%'; updateGlobalStats(); } function toggleCard(id) { document.getElementById('body-'+id).classList.toggle('open'); document.getElementById('icon-'+id).classList.toggle('open'); } function filterByTag(tag, btn) { document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); document.getElementById('searchInput').value = ''; let vis=0; document.querySelectorAll('.protocol-card').forEach(card => { const show = tag==='all' || card.dataset.tags.includes(tag); card.classList.toggle('hidden', !show); if(show) vis++; }); document.getElementById('emptyState').classList.toggle('hidden', vis>0); } function filterProtocols() { const q = document.getElementById('searchInput').value.toLowerCase(); document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); document.querySelector('.filter-btn').classList.add('active'); let vis=0; document.querySelectorAll('.protocol-card').forEach(card => { const show = !q || card.dataset.search.includes(q); card.classList.toggle('hidden', !show); if(show) vis++; }); document.getElementById('emptyState').classList.toggle('hidden', vis>0); } function resetProtocol(pid) { const p = PROTOCOLS.find(x=>x.id===pid); p.sections.forEach((s,si) => s.items.forEach((_,ii) => { const key=`${pid}-${si}-${ii}`; cbStates[key]=false; const cb=document.getElementById('cb-'+key); if(cb) cb.checked=false; })); updateProgress(pid); } function descargarWord(pid, btn) { const p = PROTOCOLS.find(x=>x.id===pid); if (!p) return; btn.textContent = '⏳ Generando...'; btn.classList.add('loading'); setTimeout(() => { try { const ok = generarWord(p, window.CFG); btn.textContent = ok ? '✓ Descargado' : '⬇ Word'; btn.classList.remove('loading'); setTimeout(() => { btn.textContent = '⬇ Word'; }, 2500); } catch(e) { btn.textContent = '⬇ Word'; btn.classList.remove('loading'); alert('Error al generar el documento. Comprueba la consola del navegador.'); } }, 50); } function renderAll() { const list = document.getElementById('protocolList'); list.innerHTML = ''; PROTOCOLS.forEach(p => { const searchStr = (p.name+' '+p.sub+' '+p.sections.map(s=>s.items.join(' ')).join(' ')).toLowerCase(); const isTuyo = p.tags.includes('tuyo'); let sectHtml = ''; p.sections.forEach((s,si) => { sectHtml += `
${s.t}
`; }); const alertClass = {red:'alert-red',amber:'alert-amber',green:'alert-green'}[p.alertType]||'alert-norm'; const tagsHtml = p.tags.filter(t=>t!=='tuyo').map(t => `${t.charAt(0).toUpperCase()+t.slice(1)}`).join('') + (isTuyo ? `★ Tu protocolo` : ''); const totalP = p.sections.reduce((a,s)=>a+s.items.length,0); const card = document.createElement('div'); card.className = 'protocol-card'; card.dataset.tags = p.tags.join(' '); card.dataset.search = searchStr; card.innerHTML = `
${p.badge}
${p.name}
${p.sub}
${tagsHtml}
0%
Normativa: ${p.normativa}
${p.alerta}
${sectHtml}
`; list.appendChild(card); }); updateGlobalStats(); } renderAll();