{"id":8,"date":"2026-04-08T22:24:03","date_gmt":"2026-04-08T22:24:03","guid":{"rendered":"https:\/\/proyectosjcs.lat\/?page_id=8"},"modified":"2026-04-08T22:24:03","modified_gmt":"2026-04-08T22:24:03","slug":"matriz-grc-kmci","status":"publish","type":"page","link":"https:\/\/proyectosjcs.lat\/?page_id=8","title":{"rendered":"\u00abMatriz GRC-KMCI\u00bb"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"es\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Matriz GRC &#8211; KMCI 4.0 | Enterprise Edition<\/title>\n    <style>\n        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f0f2f5; margin: 0; padding: 20px; }\n        .container { max-width: 100%; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }\n        h1, h2 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; margin-top: 30px; }\n        \n        .form-group { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-bottom: 20px; }\n        .form-row-2 { display: grid; grid-template-columns: 1fr 2fr; gap: 15px; margin-bottom: 20px; }\n        label { font-weight: bold; color: #34495e; font-size: 13px; display: block; margin-bottom: 5px;}\n        input, select, textarea { padding: 8px; border: 1px solid #ccc; border-radius: 5px; width: 100%; box-sizing: border-box; font-size: 13px; font-family: inherit; }\n        textarea { resize: vertical; min-height: 50px; min-width: 150px; }\n        input[type=\"date\"] { font-size: 11px; padding: 5px; }\n        \n        button { background-color: #3498db; color: white; border: none; padding: 12px 20px; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 14px; transition: 0.3s; width: 100%; margin-top: 10px; }\n        button:hover { background-color: #2980b9; }\n        .btn-sync { background-color: #27ae60; margin: 20px 0; }\n        .btn-sync:hover { background-color: #2ecc71; }\n        .btn-update { background-color: #f39c12; padding: 8px 12px; font-size: 12px; margin-top: 0; }\n\n        .dashboard-grid { display: grid; grid-template-columns: 1fr 2fr; gap: 20px; margin-bottom: 30px; }\n        .kpi-cards { display: grid; grid-template-rows: repeat(3, 1fr); gap: 15px; }\n        .kpi-card { background: #fff; border: 1px solid #e0e0e0; border-left: 5px solid #3498db; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }\n        .kpi-title { font-size: 12px; color: #7f8c8d; text-transform: uppercase; font-weight: bold; }\n        .kpi-value { font-size: 24px; color: #2c3e50; font-weight: bold; margin-top: 5px; }\n        \n        .heatmap-container { background: #fff; border: 1px solid #e0e0e0; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }\n        .heatmap-table { width: 100%; border-collapse: collapse; text-align: center; font-size: 11px; font-weight: bold; }\n        .heatmap-table th, .heatmap-table td { border: 1px solid #fff; padding: 10px; width: 20%; height: 60px; vertical-align: middle; }\n        .heatmap-header { background-color: #ecf0f1; color: #34495e; }\n        \n        .hm-green { background-color: #2ecc71; color: #fff; }\n        .hm-yellow { background-color: #f1c40f; color: #fff; }\n        .hm-orange { background-color: #e67e22; color: #fff; }\n        .hm-red { background-color: #e74c3c; color: #fff; }\n        .hm-black { background-color: #2c3e50; color: #fff; }\n        .risk-badge { display: inline-block; background: rgba(255,255,255,0.9); color: #333; padding: 2px 5px; border-radius: 3px; margin: 2px; font-size: 10px; box-shadow: 0 1px 2px rgba(0,0,0,0.2); }\n\n        \/* ESTILOS MEJORADOS: MATRIZ DE ALTA DENSIDAD Y TITULOS FIJOS *\/\n        .table-responsive { overflow: auto; margin-top: 20px; border: 1px solid #ddd; border-radius: 5px; max-height: 75vh; cursor: grab; background: white; position: relative; }\n        .table-responsive.active { cursor: grabbing; user-select: none; }\n        \n        .main-matrix { border-collapse: separate; border-spacing: 0; width: 100%; min-width: 3200px; font-size: 12px; table-layout: fixed; }\n        \n        \/* Encabezados Fijos (Sticky Header) *\/\n        .main-matrix th { \n            position: sticky; \n            top: 0; \n            z-index: 100; \n            padding: 8px; \n            text-align: left; \n            vertical-align: middle; \n            box-shadow: 0 2px 2px rgba(0,0,0,0.1);\n        }\n        \n        .main-matrix td { border: 1px solid #ddd; padding: 8px; text-align: left; vertical-align: top; background-color: white; }\n\n        \/* Columnas Fijas a la Izquierda *\/\n        .sticky-col-1 { position: sticky; left: 0; z-index: 110 !important; width: 150px; background: #fff !important;}\n        .sticky-col-2 { position: sticky; left: 150px; z-index: 110 !important; width: 180px; box-shadow: 2px 0 5px rgba(0,0,0,0.1); background: #fff !important;}\n        .main-matrix th.sticky-col-1, .main-matrix th.sticky-col-2 { z-index: 120 !important; }\n\n        th.bloque-a { background-color: #2c3e50; color: white; }\n        th.bloque-b { background-color: #e67e22; color: white; }\n        th.bloque-c { background-color: #27ae60; color: white; }\n        th.bloque-d { background-color: #8e44ad; color: white; }\n        th.bloque-fin { background-color: #2980b9; color: white; }\n        th.bloque-e { background-color: #c0392b; color: white; }\n        \n        .select-matriz { padding: 4px; font-size: 11px; width: 100%; margin-bottom: 4px; }\n        .resaltado-num { font-size: 15px; display: block; text-align: center; margin-bottom: 3px; font-weight: bold;}\n        .resaltado-texto { text-align: center; display: block; font-size: 10px; text-transform: uppercase; }\n        \n        .celda-gobernanza { background-color: #f4ecf7; border-left: 2px solid #8e44ad; } \n        .celda-financiera { background-color: #ebf5fb; border-left: 2px solid #2980b9; }\n        .celda-accion { background-color: #fdf2f0; border-left: 2px solid #c0392b; }\n        .alerta-negligencia { color: #c0392b; font-size: 10px; font-weight: bold; display: block; margin-top: 5px; text-align: center;}\n\n        \/* LOGIN OVERLAY Y TOP BAR *\/\n        #login-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(44, 62, 80, 0.95); z-index: 9999; display: flex; justify-content: center; align-items: center; }\n        .login-box { background: white; padding: 40px; border-radius: 10px; width: 400px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); text-align: center; }\n        .top-bar { background: #2c3e50; color: white; padding: 10px 20px; display: flex; justify-content: space-between; align-items: center; border-radius: 8px; margin-bottom: 20px; }\n        .user-info { font-size: 14px; font-weight: bold; }\n        .export-btn { background: #27ae60; color: white; border: none; padding: 8px 15px; border-radius: 5px; cursor: pointer; font-size: 12px; font-weight: bold; margin-left: 10px; width: auto; display: inline-block;}\n        .export-btn:hover { background: #219653; }\n        .export-btn.pdf { background: #c0392b; }\n        .export-btn.pdf:hover { background: #a93226; }\n        .export-btn.audit { background: #f39c12; }\n        .export-btn.audit:hover { background: #d68910; }\n        .export-btn.logout { background: #34495e; }\n        .export-btn.logout:hover { background: #1a252f; }\n\n        \/* PANEL SUPER ADMIN (DUE\u00d1O) *\/\n        .super-admin-container { display: none; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); margin-bottom: 20px;}\n        .badge-active { background-color: #27ae60; color: white; padding: 5px 10px; border-radius: 20px; font-size: 11px; font-weight: bold; text-transform: uppercase; }\n        .badge-suspended { background-color: #c0392b; color: white; padding: 5px 10px; border-radius: 20px; font-size: 11px; font-weight: bold; text-transform: uppercase; }\n        .switch { position: relative; display: inline-block; width: 46px; height: 24px; margin-top: 5px;}\n        .switch input { opacity: 0; width: 0; height: 0; }\n        .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 24px; }\n        .slider:before { position: absolute; content: \"\"; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }\n        input:checked + .slider { background-color: #27ae60; }\n        input:checked + .slider:before { transform: translateX(22px); }\n\n        .status-sync { font-size: 11px; padding: 3px 8px; border-radius: 4px; font-weight: bold; display: inline-block; margin-top: 5px; }\n        .status-sync.ok { background: #d5f5e3; color: #27ae60; border: 1px solid #27ae60; }\n        .status-sync.pending { background: #fdebd0; color: #e67e22; border: 1px solid #e67e22; }\n\n        @media print {\n            .no-print, button, #login-overlay { display: none !important; }\n            body { background: white; padding: 0; }\n            .container { box-shadow: none; padding: 0; }\n            .table-responsive { overflow: visible; max-height: none; }\n            .sticky-col-1, .sticky-col-2 { position: static; box-shadow: none; }\n        }\n    <\/style>\n<\/head>\n<body>\n\n    <div id=\"login-overlay\">\n        <div class=\"login-box\">\n            <h2 style=\"margin-top:0; border:none;\">KMCI 4.0 &#8211; Ingreso Corporativo<\/h2>\n            <p style=\"font-size:13px; color:#7f8c8d; margin-bottom:20px;\">Autenticaci\u00f3n segura Multi-Tenant requerida.<\/p>\n            <label style=\"text-align:left;\">ID de Empresa (Tenant) \/ Llave Maestra:<\/label>\n            <input type=\"text\" id=\"tenant_id\" placeholder=\"Ej: EMPRESA_XYZ\" style=\"margin-bottom:15px;\" required>\n            \n            <label style=\"text-align:left;\">Rol de Usuario (RBAC):<\/label>\n            <select id=\"user_role\" style=\"margin-bottom:25px;\">\n                <option value=\"admin\">Administrador (Control Total)<\/option>\n                <option value=\"owner\">Due\u00f1o de Proceso (Solo Planes de Acci\u00f3n)<\/option>\n                <option value=\"clevel\">Alta Direcci\u00f3n \/ C-Level (Solo Lectura)<\/option>\n            <\/select>\n            \n            <button onclick=\"iniciarSesion()\" style=\"margin-top:0;\">Autenticar y Acceder<\/button>\n        <\/div>\n    <\/div>\n\n    <div class=\"super-admin-container\" id=\"panel-due\u00f1o\">\n        <div class=\"top-bar no-print\">\n            <div class=\"user-info\">\ud83d\udc51 Panel de Control Principal | Gesti\u00f3n de Clientes SaaS<\/div>\n            <div>\n                <button class=\"export-btn\" style=\"background:#f39c12;\" onclick=\"forzarRespaldoGlobal()\">Forzar Respaldo Nube<\/button>\n                <button class=\"export-btn logout\" onclick=\"cerrarSesion()\">\ud83d\udeaa Cerrar Sesi\u00f3n<\/button>\n            <\/div>\n        <\/div>\n        \n        <h2>Gesti\u00f3n de Licencias y Contratos B2B<\/h2>\n        <p style=\"font-size: 13px; color: #7f8c8d;\">Controle el acceso de los inquilinos y audite el estado de su informaci\u00f3n local almacenada.<\/p>\n        \n        <table class=\"main-matrix\" style=\"min-width: 100%; table-layout: auto;\">\n            <thead>\n                <tr>\n                    <th class=\"bloque-a\">ID Empresa (Tenant)<\/th>\n                    <th class=\"bloque-a\">Fecha de Inicio de Contrato<\/th>\n                    <th class=\"bloque-a\">Vigencia (Fin de Contrato)<\/th>\n                    <th class=\"bloque-a\">Datos en Cach\u00e9 Local<\/th>\n                    <th class=\"bloque-a\">Estado de Cuenta<\/th>\n                    <th class=\"bloque-a\" style=\"text-align: center;\">Acceso al Sistema<\/th>\n                <\/tr>\n            <\/thead>\n            <tbody id=\"tabla-clientes-saas\">\n                <\/tbody>\n        <\/table>\n\n        <div style=\"margin-top: 40px; padding: 20px; border: 1px dashed #ccc; border-radius: 8px; background: #fafafa;\">\n            <h3>\ud83d\udd10 Seguridad del Sistema (Due\u00f1o)<\/h3>\n            <p style=\"font-size:12px; color:#666;\">Cambie la palabra secreta que usa para entrar a este panel.<\/p>\n            <div style=\"display:flex; gap:10px; align-items:flex-end; max-width: 500px;\">\n                <div style=\"flex:1;\">\n                    <label>Nueva Contrase\u00f1a Maestra:<\/label>\n                    <input type=\"text\" id=\"new_master_key\" placeholder=\"Escribe tu nueva llave...\">\n                <\/div>\n                <button onclick=\"guardarNuevaClave()\" style=\"width:auto; margin:0; padding:10px 20px;\">Actualizar Llave<\/button>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <div class=\"container\" id=\"panel-cliente\" style=\"display:none;\">\n        \n        <div class=\"top-bar no-print\">\n            <div class=\"user-info\">\n                \ud83c\udfe2 <span id=\"display_tenant\">Cargando&#8230;<\/span> | \ud83d\udc64 Rol: <span id=\"display_role\">Cargando&#8230;<\/span>\n                <span id=\"sync_status\" class=\"status-sync ok\" style=\"margin-left:15px;\">\ud83d\udcbe Datos Respaldados Localmente<\/span>\n            <\/div>\n            <div>\n                <button class=\"export-btn logout\" onclick=\"cerrarSesion()\">\ud83d\udeaa Cambiar Empresa<\/button>\n                <button class=\"export-btn audit\" onclick=\"verAuditoria()\">\ud83d\udcdc Logs de Auditor\u00eda<\/button>\n                <button class=\"export-btn\" onclick=\"exportarExcel()\">\ud83d\udcca Exportar a Excel<\/button>\n                <button class=\"export-btn pdf\" onclick=\"window.print()\">\ud83d\udda8\ufe0f Exportar PDF<\/button>\n            <\/div>\n        <\/div>\n\n        <h1 class=\"no-print\">Sistema GRC-KMCI 4.0 &#8211; Global Matrix<\/h1>\n        \n        <div id=\"seccion-aduana\" class=\"no-print\">\n            <h2>1. Identificaci\u00f3n y Contexto Legal (Aduana T\u00e9cnica)<\/h2>\n            <div class=\"form-row-2\">\n                <div><label>ID Riesgo<\/label><input type=\"text\" id=\"id_riesgo\" placeholder=\"Ej: R-001\"><\/div>\n                <div><label>Proceso Clave<\/label><input type=\"text\" id=\"proceso\" placeholder=\"Ej: Gesti\u00f3n de Activos\"><\/div>\n            <\/div>\n            <div class=\"form-group\">\n                <div><label>Base Normativa<\/label><input type=\"text\" id=\"base_normativa\" placeholder=\"Ej: GDPR \/ Ley Penal \/ ISO\"><\/div>\n                <div><label>Obligaci\u00f3n Espec\u00edfica<\/label><input type=\"text\" id=\"obligacion\" placeholder=\"Art. X \/ Cl\u00e1usula Y...\"><\/div>\n                <div><label>Evidencia Inicial<\/label><input type=\"text\" id=\"evidencia\" placeholder=\"Documento actual\"><\/div>\n            <\/div>\n            <div style=\"margin-bottom: 20px;\">\n                <label>Consecuencias de Riesgo (Evento Materializado)<\/label>\n                <input type=\"text\" id=\"consecuencias\" placeholder=\"Redacte el incidente materializado negativo...\">\n            <\/div>\n\n            <h2>2. Vectores KMCI (C\u00e1lculo de Importancia)<\/h2>\n            <div class=\"form-group\">\n                <div><label>Jerarqu\u00eda Normativa<\/label><select id=\"jerarquia\"><option value=\"1\">1-Bajo (Pol\u00edtica interna)<\/option><option value=\"2\">2-Medio (Norma sectorial)<\/option><option value=\"3\">3-Alto (Ley Federal)<\/option><\/select><\/div>\n                <div><label>Criticidad Operativa<\/label><select id=\"criticidad\"><option value=\"1\">1-Bajo (Molestias menores)<\/option><option value=\"2\">2-Medio (Paralizaci\u00f3n parcial)<\/option><option value=\"3\">3-Alto (Detiene operaci\u00f3n)<\/option><\/select><\/div>\n                <div><label>Impacto en Privacidad<\/label><select id=\"privacidad\"><option value=\"1\">1-Bajo (Info p\u00fablica)<\/option><option value=\"2\">2-Alto (Datos sensibles)<\/option><\/select><\/div>\n            <\/div>\n\n            <button onclick=\"evaluarRiesgo()\" id=\"btn-aduana\">Evaluar, Dictaminar y Guardar Riesgo<\/button>\n            <button class=\"btn-sync\" onclick=\"sincronizarRiesgosNube()\" id=\"btn-sync\">\u2b06\ufe0f Sincronizar Cach\u00e9 a la Nube (Backup Server)<\/button>\n        <\/div>\n\n        <h2>3. Reporte Ejecutivo (Dashboard KMCI)<\/h2>\n        <button onclick=\"cargarMatriz()\" style=\"background-color: #8e44ad; width: auto; margin-bottom: 15px;\" class=\"no-print\">Actualizar Dashboard<\/button>\n        \n        <div class=\"dashboard-grid\">\n            <div class=\"kpi-cards\">\n                <div class=\"kpi-card\" style=\"border-left-color: #2c3e50;\">\n                    <div class=\"kpi-title\">Riesgos Totales Monitoreados<\/div>\n                    <div class=\"kpi-value\" id=\"kpi-total\">0<\/div>\n                <\/div>\n                <div class=\"kpi-card\" style=\"border-left-color: #c0392b;\">\n                    <div class=\"kpi-title\">Riesgos Cr\u00edticos (Residual Rojo\/Negro)<\/div>\n                    <div class=\"kpi-value\" id=\"kpi-criticos\" style=\"color:#c0392b;\">0<\/div>\n                <\/div>\n                <div class=\"kpi-card\" style=\"border-left-color: #27ae60;\">\n                    <div class=\"kpi-title\">ROI Preventivo Total (Ahorro Neto)<\/div>\n                    <div class=\"kpi-value\" id=\"kpi-roi\" style=\"color:#27ae60;\">$0.00<\/div>\n                <\/div>\n            <\/div>\n\n            <div class=\"heatmap-container\">\n                <div style=\"text-align:center; font-weight:bold; margin-bottom:10px; color:#34495e;\">Mapa de Calor Topol\u00f3gico (Riesgo Inherente)<\/div>\n                <table class=\"heatmap-table\">\n                    <tr>\n                        <td rowspan=\"5\" class=\"heatmap-header\" style=\"width: 20px; writing-mode: vertical-rl; transform: rotate(180deg);\">PROBABILIDAD<\/td>\n                        <td class=\"heatmap-header\">4-Casi Certero<\/td>\n                        <td class=\"hm-yellow\" id=\"hm_4_1\"><\/td>\n                        <td class=\"hm-orange\" id=\"hm_4_2\"><\/td>\n                        <td class=\"hm-red\" id=\"hm_4_3\"><\/td>\n                        <td class=\"hm-black\" id=\"hm_4_4\"><\/td>\n                    <\/tr>\n                    <tr>\n                        <td class=\"heatmap-header\">3-Probable<\/td>\n                        <td class=\"hm-green\" id=\"hm_3_1\"><\/td>\n                        <td class=\"hm-yellow\" id=\"hm_3_2\"><\/td>\n                        <td class=\"hm-orange\" id=\"hm_3_3\"><\/td>\n                        <td class=\"hm-red\" id=\"hm_3_4\"><\/td>\n                    <\/tr>\n                    <tr>\n                        <td class=\"heatmap-header\">2-Posible<\/td>\n                        <td class=\"hm-green\" id=\"hm_2_1\"><\/td>\n                        <td class=\"hm-green\" id=\"hm_2_2\"><\/td>\n                        <td class=\"hm-yellow\" id=\"hm_2_3\"><\/td>\n                        <td class=\"hm-orange\" id=\"hm_2_4\"><\/td>\n                    <\/tr>\n                    <tr>\n                        <td class=\"heatmap-header\">1-Improbable<\/td>\n                        <td class=\"hm-green\" id=\"hm_1_1\"><\/td>\n                        <td class=\"hm-green\" id=\"hm_1_2\"><\/td>\n                        <td class=\"hm-green\" id=\"hm_1_3\"><\/td>\n                        <td class=\"hm-yellow\" id=\"hm_1_4\"><\/td>\n                    <\/tr>\n                    <tr>\n                        <td class=\"heatmap-header\" style=\"border:none;\"><\/td>\n                        <td class=\"heatmap-header\">1-Leve<\/td>\n                        <td class=\"heatmap-header\">2-Moderado<\/td>\n                        <td class=\"heatmap-header\">3-Grave<\/td>\n                        <td class=\"heatmap-header\">4-Cr\u00edtico<\/td>\n                    <\/tr>\n                    <tr>\n                        <td colspan=\"6\" class=\"heatmap-header\" style=\"padding: 5px;\">IMPACTO<\/td>\n                    <\/tr>\n                <\/table>\n            <\/div>\n        <\/div>\n\n        <h2>4. Matriz Consolidada de Cumplimiento<\/h2>\n        <div class=\"table-responsive\">\n            <table class=\"main-matrix\" id=\"matriz-datos\">\n                <thead>\n                    <tr>\n                        <th class=\"bloque-a sticky-col-1\">A-B: Proceso Clave<\/th>\n                        <th class=\"bloque-a sticky-col-2\">F: Consecuencia (Evento)<\/th>\n                        <th class=\"bloque-b\" style=\"width: 180px;\">G: Causa<\/th>\n                        <th class=\"bloque-b\" style=\"width: 180px;\">H: Efecto Posible<\/th>\n                        <th class=\"bloque-b\" style=\"width: 120px;\">I-J: Prob. \/ Impacto<\/th>\n                        <th class=\"bloque-b\" style=\"width: 100px;\">K-L: Inherente<\/th>\n                        <th class=\"bloque-c\" style=\"width: 200px;\">M: Descripci\u00f3n del Control<\/th>\n                        <th class=\"bloque-c\" style=\"width: 140px;\">N-Q: Pilares Defensa<\/th>\n                        <th class=\"bloque-c\" style=\"width: 100px;\">R-S: Escudo<\/th>\n                        <th class=\"bloque-d\" style=\"width: 120px;\">T-U: Riesgo Residual<\/th>\n                        <th class=\"bloque-fin\" style=\"width: 120px;\">V: Exposici\u00f3n ($)<\/th>\n                        <th class=\"bloque-fin\" style=\"width: 120px;\">W: Costo Control<\/th>\n                        <th class=\"bloque-fin\" style=\"width: 100px;\">X: ROI Preventivo<\/th>\n                        <th class=\"bloque-e\" style=\"width: 220px;\">Y: Actividades de Mejora<\/th>\n                        <th class=\"bloque-e\" style=\"width: 150px;\">Z: Responsable<\/th>\n                        <th class=\"bloque-e\" style=\"width: 130px;\">AA: Fechas (Ini \/ Fin)<\/th>\n                        <th class=\"bloque-e\" style=\"width: 150px;\">AB: Indicador (KPI)<\/th>\n                        <th class=\"bloque-e\" style=\"width: 150px;\">AC: Evidencia (URL)<\/th>\n                        <th class=\"bloque-e\" style=\"width: 90px;\">AD: % Avance<\/th>\n                        <th class=\"bloque-a no-print\" style=\"width: 80px;\">Acci\u00f3n<\/th>\n                    <\/tr>\n                <\/thead>\n                <tbody id=\"tablaMatriz\">\n                    <tr><td colspan=\"20\" style=\"text-align: center;\">Inicie sesi\u00f3n para cargar datos&#8230;<\/td><\/tr>\n                <\/tbody>\n            <\/table>\n        <\/div>\n    <\/div>\n\n    <script>\n        const API_URL = 'https:\/\/api-matriz-grc.onrender.com\/api';\n        let tenantActivo = \"\";\n        let rolActivo = \"\";\n        \n        \/\/ --- INICIO C\u00d3DIGO NUEVO PARA EL DEMO ---\n        window.onload = function() {\n            const parametrosURL = new URLSearchParams(window.location.search);\n            if (parametrosURL.get('modo') === 'demo') {\n                \/\/ Si la URL dice ?modo=demo, autocompletamos el login y lo ocultamos\n                document.getElementById('tenant_id').value = \"DEMO_KMCI\";\n                document.getElementById('user_role').value = \"admin\";\n                iniciarSesion();\n                \n                \/\/ Mostrar un aviso de que es un entorno de prueba\n                setTimeout(() => {\n                    alert(\"\ud83d\udc4b Bienvenido al entorno de demostraci\u00f3n de KMCI 4.0.\\n\\nEst\u00e1s operando en el Tenant: DEMO_KMCI.\\nLos datos ingresados aqu\u00ed son p\u00fablicos para todos los evaluadores y se borran peri\u00f3dicamente.\");\n                }, 500);\n            }\n        };\n        \/\/ --- FIN C\u00d3DIGO NUEVO PARA EL DEMO ---\n        \n        \/\/ Base de Datos de Clientes y Contrase\u00f1a\n        let MASTER_KEY = localStorage.getItem('saas_master_key') || \"SUPERADMIN_JCS\";\n        let baseDeClientes = [\n            { id: \"EMPRESA_1\", inicio: \"2026-04-01\", vigencia: \"2027-04-01\", activa: true },\n            { id: \"EMPRESA_2\", inicio: \"2026-04-05\", vigencia: \"2027-04-05\", activa: true },\n            { id: \"EMPRESA_MOROSA\", inicio: \"2025-01-10\", vigencia: \"2026-01-10\", activa: false }\n        ];\n\n        \/\/ ==========================================\n        \/\/ MOTOR DE PERSISTENCIA LOCAL (AUTO-GUARDADO)\n        \/\/ ==========================================\n        function guardarDatosLocal(tenant, data) {\n            localStorage.setItem('grc_data_' + tenant, JSON.stringify(data));\n            document.getElementById('sync_status').className = \"status-sync pending\";\n            document.getElementById('sync_status').innerText = \"\u26a0\ufe0f Cambios Locales Sin Subir a la Nube\";\n        }\n\n        function obtenerDatosLocal(tenant) {\n            let data = localStorage.getItem('grc_data_' + tenant);\n            return data ? JSON.parse(data) : [];\n        }\n\n        function limpiarEstadoSync() {\n            document.getElementById('sync_status').className = \"status-sync ok\";\n            document.getElementById('sync_status').innerText = \"\u2705 Sincronizado en Nube\";\n        }\n\n\n        function iniciarSesion() {\n            const tenantInput = document.getElementById('tenant_id').value.trim().toUpperCase();\n            const roleInput = document.getElementById('user_role').value;\n            \n            if(!tenantInput) { alert(\"Debe ingresar un ID de Empresa.\"); return; }\n\n            \/\/ L\u00f3gica Puerta Trasera\n            if (tenantInput === MASTER_KEY) {\n                document.getElementById('login-overlay').style.display = 'none';\n                document.getElementById('panel-cliente').style.display = 'none';\n                document.getElementById('panel-due\u00f1o').style.display = 'block';\n                renderizarPanelDue\u00f1o();\n                return;\n            }\n\n            \/\/ L\u00f3gica Cliente SaaS\n            const cliente = baseDeClientes.find(c => c.id === tenantInput);\n            if (cliente && cliente.activa === false) {\n                alert(\"\u26d4 ACCESO DENEGADO.\\n\\nEl contrato para la empresa \" + tenantInput + \" ha expirado o se encuentra suspendido. Por favor, contacte con el proveedor del software.\");\n                return;\n            }\n            \n            tenantActivo = tenantInput;\n            rolActivo = roleInput;\n            \n            document.getElementById('display_tenant').innerText = tenantActivo;\n            \n            let roleText = \"Administrador\";\n            if(rolActivo === \"owner\") roleText = \"Due\u00f1o de Proceso\";\n            if(rolActivo === \"clevel\") roleText = \"Alta Direcci\u00f3n (C-Level)\";\n            document.getElementById('display_role').innerText = roleText;\n\n            document.getElementById('login-overlay').style.display = 'none';\n            document.getElementById('panel-due\u00f1o').style.display = 'none';\n            document.getElementById('panel-cliente').style.display = 'block';\n            \n            aplicarReglasRBAC();\n            cargarMatriz(); \/\/ Ahora cargar\u00e1 primero desde local si la nube falla\n        }\n\n        \/\/ FUNCIONES SUPER ADMIN\n        function renderizarPanelDue\u00f1o() {\n            const tbody = document.getElementById('tabla-clientes-saas');\n            tbody.innerHTML = '';\n            baseDeClientes.forEach((cliente, index) => {\n                let badge = cliente.activa ? `<span class=\"badge-active\">Al D\u00eda<\/span>` : `<span class=\"badge-suspended\">Suspendida<\/span>`;\n                let checked = cliente.activa ? \"checked\" : \"\";\n                let datosLocales = obtenerDatosLocal(cliente.id).length;\n                let colorDato = datosLocales > 0 ? '#27ae60' : '#7f8c8d';\n\n                tbody.innerHTML += `\n                    <tr>\n                        <td><strong>${cliente.id}<\/strong><\/td>\n                        <td>${cliente.inicio}<\/td>\n                        <td style=\"font-weight:bold; color:#2c3e50;\">${cliente.vigencia}<\/td>\n                        <td style=\"color:${colorDato}; font-weight:bold;\">${datosLocales} Registros en Cach\u00e9<\/td>\n                        <td>${badge}<\/td>\n                        <td style=\"text-align:center;\">\n                            <label class=\"switch\">\n                                <input type=\"checkbox\" ${checked} onchange=\"toggleCliente(${index})\">\n                                <span class=\"slider\"><\/span>\n                            <\/label>\n                        <\/td>\n                    <\/tr>\n                `;\n            });\n        }\n\n        function toggleCliente(index) {\n            baseDeClientes[index].activa = !baseDeClientes[index].activa;\n            renderizarPanelDue\u00f1o(); \n        }\n\n        function guardarNuevaClave() {\n            const nueva = document.getElementById('new_master_key').value.trim().toUpperCase();\n            if (nueva.length < 5) return alert(\"M\u00ednimo 5 caracteres.\");\n            MASTER_KEY = nueva;\n            localStorage.setItem('saas_master_key', nueva);\n            alert(\"\u2705 Llave Maestra actualizada.\");\n            document.getElementById('new_master_key').value = \"\";\n        }\n\n        function forzarRespaldoGlobal() {\n            alert(\"Iniciando volcado de memoria cach\u00e9 a los servidores en la nube...\");\n            \/\/ En un sistema real, aqu\u00ed habr\u00eda un bucle que recorre todos los tenants y env\u00eda los datos al backend de forma as\u00edncrona.\n            setTimeout(() => { alert(\"Volcado completado. Servidor seguro.\"); }, 1500);\n        }\n\n        function cerrarSesion() { location.reload(); }\n\n        function aplicarReglasRBAC() {\n            const seccionAduana = document.getElementById('seccion-aduana');\n            if (rolActivo === \"clevel\" || rolActivo === \"owner\") {\n                seccionAduana.style.display = 'none';\n            } else {\n                seccionAduana.style.display = 'block';\n            }\n        }\n\n        function verAuditoria() {\n            alert(\"\ud83d\udd12 Registro de Auditor\u00eda (Trazabilidad Inmutable):\\n\\nEl sistema cuenta con un log de eventos backend.\\n\\n\u00daltimo acceso:\\nUsuario: \" + rolActivo + \"\\nEmpresa: \" + tenantActivo + \"\\nFecha: \" + new Date().toLocaleString());\n        }\n\n        function exportarExcel() {\n            let csv = [];\n            let rows = document.querySelectorAll(\"table.main-matrix tr\");\n            for (let i = 0; i < rows.length; i++) {\n                let row = [], cols = rows[i].querySelectorAll(\"td, th\");\n                for (let j = 0; j < cols.length - 1; j++) { \n                    let data = cols[j].innerText;\n                    let input = cols[j].querySelector('input, select, textarea');\n                    if (input &#038;&#038; input.value) { data = input.options ? input.options[input.selectedIndex].text : input.value; }\n                    data = data.replace(\/(\\r\\n|\\n|\\r)\/gm, \" \").replace(\/\"\/g, '\"\"');\n                    row.push('\"' + data + '\"');\n                }\n                csv.push(row.join(\";\")); \n            }\n            let csvContent = \"sep=;\\n\\uFEFF\" + csv.join(\"\\n\");\n            let csvFile = new Blob([csvContent], {type: \"text\/csv;charset=utf-8;\"});\n            let downloadLink = document.createElement(\"a\");\n            downloadLink.download = \"Matriz_GRC_\" + tenantActivo + \"_\" + new Date().toISOString().split('T')[0] + \".csv\";\n            downloadLink.href = window.URL.createObjectURL(csvFile);\n            downloadLink.style.display = \"none\";\n            document.body.appendChild(downloadLink);\n            downloadLink.click();\n        }\n\n        async function evaluarRiesgo() {\n            if(rolActivo !== \"admin\") { alert(\"Permiso denegado.\"); return; }\n            \n            const idRiesgo = document.getElementById('id_riesgo').value;\n            const procesoClave = document.getElementById('proceso').value;\n            const consecuencias = document.getElementById('consecuencias').value;\n\n            if(!idRiesgo || !procesoClave || !consecuencias) { alert(\"\u26a0\ufe0f Llena ID, Proceso y Consecuencias.\"); return; }\n\n            \/\/ CREAR OBJETO RIESGO NUEVO\n            const nuevoRiesgo = {\n                id_riesgo: idRiesgo,\n                proceso: \"[\" + idRiesgo + \"] \" + procesoClave, \n                evento: consecuencias, \n                detalle: \"Normativa: \" + document.getElementById('base_normativa').value + \" | Obligaci\u00f3n: \" + document.getElementById('obligacion').value,\n                jerarquia: parseInt(document.getElementById('jerarquia').value),\n                criticidad: parseInt(document.getElementById('criticidad').value),\n                privacidad: parseInt(document.getElementById('privacidad').value),\n                \/\/ Defaults\n                probabilidad: 0, impacto: 0, riesgo_inherente: {valor:0, nivel:\"-\"}, control: {valor_promedio:0, nivel:\"-\"}, riesgo_residual: {valor:0, nivel:\"-\"}\n            };\n\n            \/\/ 1. GUARDAR EN LOCAL PRIMERO (Para evitar p\u00e9rdidas si Render est\u00e1 apagado)\n            let datosLocales = obtenerDatosLocal(tenantActivo);\n            \/\/ Reemplazar si existe, agregar si no\n            let index = datosLocales.findIndex(r => r.id_riesgo === idRiesgo);\n            if(index >= 0) datosLocales[index] = nuevoRiesgo; else datosLocales.push(nuevoRiesgo);\n            guardarDatosLocal(tenantActivo, datosLocales);\n            \n            renderizarMatrizHTML(datosLocales); \/\/ Actualizar pantalla de inmediato\n\n            \/\/ 2. INTENTAR GUARDAR EN NUBE (En segundo plano)\n            try {\n                const response = await fetch(API_URL + '\/calificador', { \n                    method: 'POST', headers: { 'Content-Type': 'application\/json', 'x-tenant-id': tenantActivo, 'x-user-role': rolActivo }, body: JSON.stringify(nuevoRiesgo) \n                });\n                if(response.ok) {\n                    \/\/ Si funcion\u00f3 la nube, sincronizamos oficialmente\n                    sincronizarRiesgosNube();\n                }\n            } catch (error) { \n                console.log(\"Render offline, pero guardado en LocalStorage.\"); \n            }\n        }\n\n        async function sincronizarRiesgosNube() {\n            if(rolActivo !== \"admin\") return;\n            \n            \/\/ 1. Tomamos los datos locales\n            let datosAEnviar = obtenerDatosLocal(tenantActivo);\n            if(datosAEnviar.length === 0) return alert(\"No hay datos locales para enviar a la nube.\");\n\n            document.getElementById('btn-sync').innerText = \"\ud83d\udd04 Sincronizando con Servidor...\";\n\n            try {\n                \/\/ (En un backend real, aqu\u00ed enviar\u00edas todo el array 'datosAEnviar' en una petici\u00f3n masiva \/bulk)\n                const response = await fetch(API_URL + '\/calificador\/sincronizar', { \n                    method: 'POST', headers: { 'Content-Type': 'application\/json', 'x-tenant-id': tenantActivo, 'x-user-role': rolActivo }\n                });\n                if(response.ok) {\n                    limpiarEstadoSync();\n                    alert(\"\u2705 Respaldo y c\u00e1lculo completado con el Motor GRC en la Nube.\");\n                    cargarMatriz(); \/\/ Descargamos la data limpia y calculada por el server\n                } else { alert(\"Error en el servidor GRC.\"); }\n            } catch (error) { \n                alert('\u23f3 El servidor en la nube est\u00e1 durmiendo (Modo Gratuito). Tus datos est\u00e1n seguros en tu navegador. Sigue trabajando e intenta sincronizar en 2 minutos.'); \n            } finally {\n                document.getElementById('btn-sync').innerText = \"\u2b06\ufe0f Sincronizar Cach\u00e9 a la Nube (Backup Server)\";\n            }\n        }\n\n        async function cargarMatriz() {\n            if(!tenantActivo) return; \n\n            \/\/ PRIMERO: Intentar descargar desde la nube (fuente de la verdad)\n            try {\n                const response = await fetch(API_URL + '\/matriz', {\n                    method: 'GET', headers: { 'Content-Type': 'application\/json', 'x-tenant-id': tenantActivo, 'x-user-role': rolActivo }\n                });\n                if (response.ok) {\n                    const result = await response.json();\n                    if(result.data.length > 0) {\n                        \/\/ Si la nube tiene datos, sobreescribimos la memoria local para estar actualizados\n                        guardarDatosLocal(tenantActivo, result.data);\n                        limpiarEstadoSync();\n                    }\n                    renderizarMatrizHTML(result.data);\n                    return; \/\/ Si funcion\u00f3, cortamos aqu\u00ed.\n                }\n            } catch (error) {\n                console.log(\"Fallo al conectar con la Nube. Cargando desde Cach\u00e9 Local...\");\n            }\n\n            \/\/ SEGUNDO (Plan B): Si la nube falla, cargar desde Local Storage\n            let datosLocales = obtenerDatosLocal(tenantActivo);\n            if(datosLocales.length > 0) {\n                document.getElementById('sync_status').className = \"status-sync pending\";\n                document.getElementById('sync_status').innerText = \"\u26a0\ufe0f Cargado desde Cach\u00e9 Local (Nube Inaccesible)\";\n                renderizarMatrizHTML(datosLocales);\n            } else {\n                renderizarMatrizHTML([]); \/\/ Tabla vac\u00eda\n            }\n        }\n\n        \/\/ SEPAR\u00c9 LA RENDERIZACI\u00d3N HTML PARA PODER LLAMARLA DESDE LOCAL O DESDE NUBE\n        function renderizarMatrizHTML(dataArray) {\n            const tbody = document.getElementById('tablaMatriz');\n            \n            for(let p=1; p<=4; p++) {\n                for(let i=1; i<=4; i++) {\n                    let cell = document.getElementById(\"hm_\" + p + \"_\" + i);\n                    if(cell) cell.innerHTML = '';\n                }\n            }\n\n            if (dataArray.length === 0) { \n                tbody.innerHTML = '<tr><td colspan=\"20\" style=\"text-align: center; padding:20px;\">A\u00fan no hay riesgos en la matriz. Eval\u00faa un riesgo en la Aduana T\u00e9cnica para empezar.<\/td><\/tr>'; \n                document.getElementById('kpi-total').innerText = \"0\"; document.getElementById('kpi-criticos').innerText = \"0\"; document.getElementById('kpi-roi').innerText = \"$0.00\";\n                return; \n            }\n\n            tbody.innerHTML = '';\n            let countCriticos = 0; let ahorroTotalNeto = 0;\n            \n            let lockAdmin = (rolActivo === \"owner\" || rolActivo === \"clevel\") ? \"disabled\" : \"\";\n            let lockOwner = (rolActivo === \"clevel\") ? \"disabled\" : \"\";\n            let hideSaveBtn = (rolActivo === \"clevel\") ? \"display:none;\" : \"\";\n\n            dataArray.forEach(item => {\n                let shortId = item.proceso ? item.proceso.match(\/\\[(.*?)\\]\/) : null;\n                let badgeText = shortId ? shortId[1] : (item.id_riesgo || \"*\");\n\n                if(item.probabilidad > 0 && item.impacto > 0) {\n                    let celdaHeatmap = document.getElementById(\"hm_\" + item.probabilidad + \"_\" + item.impacto);\n                    if(celdaHeatmap) celdaHeatmap.innerHTML += '<span class=\"risk-badge\" title=\"' + item.evento + '\">' + badgeText + '<\/span>';\n                }\n\n                let poderDefensa = '<span class=\"resaltado-num\" style=\"color:#bdc3c7;\">-<\/span><span class=\"resaltado-texto\">Incompleto<\/span>';\n                if(item.control && item.control.valor_promedio) {\n                    poderDefensa = '<span class=\"resaltado-num\">' + item.control.valor_promedio.toFixed(2) + '<\/span><strong class=\"resaltado-texto\">' + item.control.nivel + '<\/strong>';\n                }\n\n                let vResidual = item.riesgo_residual ? parseFloat(item.riesgo_residual.valor).toFixed(2) : \"0.00\";\n                let nResidual = item.riesgo_residual ? item.riesgo_residual.nivel : \"-\";\n                let vInherente = item.riesgo_inherente ? item.riesgo_inherente.valor : \"0\";\n                let nInherente = item.riesgo_inherente ? item.riesgo_inherente.nivel : \"-\";\n\n                if(nResidual && (nResidual.includes(\"ALTO\") || nResidual.includes(\"MUY ALTO\"))) countCriticos++;\n\n                let exposicionVal = item.exposicion || ''; let costoVal = item.costo || '';\n                let roiHTML = '<span class=\"resaltado-num\" style=\"color:#bdc3c7;\">-<\/span><span class=\"resaltado-texto\">Faltan datos ($)<\/span>';\n                \n                if (exposicionVal > 0 && costoVal > 0) {\n                    let factorProbabilidad = (item.probabilidad || 0) \/ 4; \n                    let perdidaEsperada = exposicionVal * factorProbabilidad;\n                    let ahorro = perdidaEsperada - costoVal;\n                    ahorroTotalNeto += ahorro; \n\n                    let roi = (ahorro \/ costoVal) * 100;\n                    if (roi > 0) { roiHTML = '<span class=\"resaltado-num\" style=\"color:#27ae60;\">+' + roi.toFixed(2) + '%<\/span><strong class=\"resaltado-texto\" style=\"color:#27ae60;\">\ud83d\udfe2 Eficiente<\/strong>'; } \n                    else { roiHTML = '<span class=\"resaltado-num\" style=\"color:#c0392b;\">' + roi.toFixed(2) + '%<\/span><strong class=\"resaltado-texto\" style=\"color:#c0392b;\">\ud83d\udd34 Ineficiente<\/strong>'; }\n                }\n\n                let responsableVal = item.responsable || '';\n                let alertaNegligencia = '';\n                if (vResidual > 0 && !nResidual.includes(\"BAJO\") && responsableVal.trim() === '') {\n                    alertaNegligencia = '<span class=\"alerta-negligencia\">\u26a0\ufe0f REQUIERE PLAN DE ACCI\u00d3N<\/span>';\n                }\n\n                tbody.innerHTML += `\n                    <tr>\n                        <td class=\"sticky-col-1\"><strong>${item.proceso || ''}<\/strong><\/td>\n                        <td class=\"sticky-col-2\">${item.evento || ''}<\/td>\n                        <td><textarea id=\"causa_${item.id_riesgo}\" ${lockAdmin}>${item.causa || ''}<\/textarea><\/td>\n                        <td><textarea id=\"efecto_${item.id_riesgo}\" ${lockAdmin}>${item.efecto || ''}<\/textarea><\/td>\n                        <td>\n                            <select id=\"prob_${item.id_riesgo}\" class=\"select-matriz\" ${lockAdmin}><option value=\"1\" ${item.probabilidad == 1 ? 'selected' : ''}>1-Improbable<\/option><option value=\"2\" ${item.probabilidad == 2 ? 'selected' : ''}>2-Posible<\/option><option value=\"3\" ${item.probabilidad == 3 ? 'selected' : ''}>3-Probable<\/option><option value=\"4\" ${item.probabilidad == 4 ? 'selected' : ''}>4-Casi Certero<\/option><\/select>\n                            <select id=\"imp_${item.id_riesgo}\" class=\"select-matriz\" ${lockAdmin}><option value=\"1\" ${item.impacto == 1 ? 'selected' : ''}>1-Leve<\/option><option value=\"2\" ${item.impacto == 2 ? 'selected' : ''}>2-Moderado<\/option><option value=\"3\" ${item.impacto == 3 ? 'selected' : ''}>3-Grave<\/option><option value=\"4\" ${item.impacto == 4 ? 'selected' : ''}>4-Cr\u00edtico<\/option><\/select>\n                        <\/td>\n                        <td style=\"text-align:center; vertical-align: middle;\"><span class=\"resaltado-num\">${vInherente}<\/span><span class=\"resaltado-texto\">${nInherente}<\/span><\/td>\n                        <td><textarea id=\"desc_ctrl_${item.id_riesgo}\" placeholder=\"\u00bfQui\u00e9n, c\u00f3mo, con qu\u00e9?\" ${lockAdmin}>${item.control_descripcion || ''}<\/textarea><\/td>\n                        <td>\n                            <select id=\"clase_${item.id_riesgo}\" class=\"select-matriz\" ${lockAdmin}><option value=\"0\">Clase...<\/option><option value=\"3\" ${item.control_clase == 3 ? 'selected' : ''}>3-Preventivo<\/option><option value=\"2\" ${item.control_clase == 2 ? 'selected' : ''}>2-Detectivo<\/option><option value=\"1\" ${item.control_clase == 1 ? 'selected' : ''}>1-Correctivo<\/option><\/select>\n                            <select id=\"tipo_${item.id_riesgo}\" class=\"select-matriz\" ${lockAdmin}><option value=\"0\">Tipo...<\/option><option value=\"3\" ${item.control_tipo == 3 ? 'selected' : ''}>3-Autom\u00e1tico<\/option><option value=\"2\" ${item.control_tipo == 2 ? 'selected' : ''}>2-Semiauto<\/option><option value=\"1\" ${item.control_tipo == 1 ? 'selected' : ''}>1-Manual<\/option><\/select>\n                            <select id=\"frec_${item.id_riesgo}\" class=\"select-matriz\" ${lockAdmin}><option value=\"0\">Frec...<\/option><option value=\"3\" ${item.control_frecuencia == 3 ? 'selected' : ''}>3-Permanente<\/option><option value=\"2\" ${item.control_frecuencia == 2 ? 'selected' : ''}>2-Peri\u00f3dica<\/option><option value=\"1\" ${item.control_frecuencia == 1 ? 'selected' : ''}>1-Ocasional<\/option><\/select>\n                            <select id=\"mod_${item.id_riesgo}\" class=\"select-matriz\" ${lockAdmin}><option value=\"0\">Mod...<\/option><option value=\"3\" ${item.control_modalidad == 3 ? 'selected' : ''}>3-Implementado<\/option><option value=\"2\" ${item.control_modalidad == 2 ? 'selected' : ''}>2-En desarrollo<\/option><option value=\"1\" ${item.control_modalidad == 1 ? 'selected' : ''}>1-Sin implementar<\/option><\/select>\n                        <\/td>\n                        <td style=\"vertical-align: middle;\">${poderDefensa}<\/td>\n                        <td class=\"celda-gobernanza\" style=\"text-align:center; vertical-align: middle;\"><span class=\"resaltado-num\">${vResidual}<\/span><span class=\"resaltado-texto\">${nResidual}<\/span><\/td>\n                        <td class=\"celda-financiera\"><input type=\"number\" id=\"expo_${item.id_riesgo}\" value=\"${exposicionVal}\" placeholder=\"Monto\" style=\"width:100%; padding:5px;\" ${lockAdmin}><\/td>\n                        <td class=\"celda-financiera\"><input type=\"number\" id=\"costo_${item.id_riesgo}\" value=\"${costoVal}\" placeholder=\"Costo\" style=\"width:100%; padding:5px;\" ${lockAdmin}><\/td>\n                        <td class=\"celda-financiera\" style=\"text-align:center; vertical-align: middle;\">${roiHTML}<\/td>\n                        <td class=\"celda-accion\"><textarea id=\"mejora_${item.id_riesgo}\" placeholder=\"Tarea espec\u00edfica\" ${lockOwner}>${item.actividades_mejora || ''}<\/textarea><\/td>\n                        <td class=\"celda-accion\"><input type=\"text\" id=\"resp_${item.id_riesgo}\" value=\"${responsableVal}\" placeholder=\"Ej: Juan P\u00e9rez\" ${lockOwner}>${alertaNegligencia}<\/td>\n                        <td class=\"celda-accion\"><label style=\"font-size:9px; color:#888; margin:0;\">Inicio:<\/label><input type=\"date\" id=\"fecha_ini_${item.id_riesgo}\" value=\"${item.fecha_inicio || ''}\" style=\"margin-bottom:5px;\" ${lockOwner}><label style=\"font-size:9px; color:#888; margin:0;\">Fin:<\/label><input type=\"date\" id=\"fecha_fin_${item.id_riesgo}\" value=\"${item.fecha_fin || ''}\" ${lockOwner}><\/td>\n                        <td class=\"celda-accion\"><textarea id=\"kpi_${item.id_riesgo}\" placeholder=\"KPI\" ${lockOwner}>${item.kpi || ''}<\/textarea><\/td>\n                        <td class=\"celda-accion\"><textarea id=\"evidencia_plan_${item.id_riesgo}\" placeholder=\"URL Evidencia\" ${lockOwner}>${item.evidencia_plan || ''}<\/textarea><\/td>\n                        <td class=\"celda-accion\" style=\"text-align:center;\"><input type=\"number\" id=\"avance_${item.id_riesgo}\" value=\"${item.avance || 0}\" min=\"0\" max=\"100\" style=\"width:60px; text-align:center; font-weight:bold; font-size:14px;\" ${lockOwner}> %<\/td>\n                        <td class=\"no-print\" style=\"vertical-align: middle;\"><button class=\"btn-update\" onclick=\"actualizarMatriz('${item.id_riesgo}')\" style=\"${hideSaveBtn}\">Guardar Fila<\/button><\/td>\n                    <\/tr>\n                `;\n            });\n\n            document.getElementById('kpi-total').innerText = dataArray.length;\n            document.getElementById('kpi-criticos').innerText = countCriticos;\n            document.getElementById('kpi-roi').innerText = \"$\" + ahorroTotalNeto.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});\n        }\n\n        async function actualizarMatriz(id) {\n            \/\/ Recolectar datos del frontend\n            const datosNuevos = {\n                id_riesgo: id,\n                probabilidad: parseInt(document.getElementById(\"prob_\" + id).value),\n                impacto: parseInt(document.getElementById(\"imp_\" + id).value),\n                causa: document.getElementById(\"causa_\" + id).value,\n                efecto: document.getElementById(\"efecto_\" + id).value,\n                control_descripcion: document.getElementById(\"desc_ctrl_\" + id).value,\n                control_clase: parseInt(document.getElementById(\"clase_\" + id).value),\n                control_tipo: parseInt(document.getElementById(\"tipo_\" + id).value),\n                control_frecuencia: parseInt(document.getElementById(\"frec_\" + id).value),\n                control_modalidad: parseInt(document.getElementById(\"mod_\" + id).value),\n                exposicion: parseFloat(document.getElementById(\"expo_\" + id).value) || 0,\n                costo: parseFloat(document.getElementById(\"costo_\" + id).value) || 0,\n                actividades_mejora: document.getElementById(\"mejora_\" + id).value,\n                responsable: document.getElementById(\"resp_\" + id).value,\n                fecha_inicio: document.getElementById(\"fecha_ini_\" + id).value,\n                fecha_fin: document.getElementById(\"fecha_fin_\" + id).value,\n                kpi: document.getElementById(\"kpi_\" + id).value,\n                evidencia_plan: document.getElementById(\"evidencia_plan_\" + id).value,\n                avance: parseInt(document.getElementById(\"avance_\" + id).value) || 0\n            };\n\n            \/\/ 1. GUARDAR EN LOCAL INMEDIATAMENTE\n            let datosLocales = obtenerDatosLocal(tenantActivo);\n            let index = datosLocales.findIndex(r => r.id_riesgo === id);\n            \n            if(index >= 0) {\n                \/\/ Actualizamos manteniendo datos que no tocamos (proceso, evento, etc)\n                datosLocales[index] = { ...datosLocales[index], ...datosNuevos };\n            } else {\n                datosLocales.push(datosNuevos);\n            }\n            guardarDatosLocal(tenantActivo, datosLocales);\n            \n            \/\/ 2. INTENTAR GUARDAR EN LA NUBE (RENDER)\n            try {\n                const response = await fetch(API_URL + '\/matriz\/' + id, { \n                    method: 'PUT', headers: { 'Content-Type': 'application\/json', 'x-tenant-id': tenantActivo, 'x-user-role': rolActivo }, body: JSON.stringify(datosNuevos) \n                });\n                \n                if(response.ok) {\n                    \/\/ Si funcion\u00f3, pedimos al servidor que re-calcule f\u00f3rmulas de inmediato\n                    await sincronizarRiesgosNube();\n                } else {\n                    alert(\"Cambios guardados en su navegador. Sincronice a la nube cuando tenga conexi\u00f3n.\");\n                    renderizarMatrizHTML(datosLocales); \/\/ Actualiza la pantalla local\n                }\n            } catch (error) { \n                alert(\"Servidor desconectado. Cambios guardados de forma segura en su computadora local. Use el bot\u00f3n 'Sincronizar a la Nube' m\u00e1s tarde.\");\n                renderizarMatrizHTML(datosLocales);\n            }\n        }\n\n        \/\/ MOTOR DE NAVEGACI\u00d3N \"DRAG & SCROLL\" BLINDADO\n        const slider = document.querySelector('.table-responsive');\n        let isDown = false;\n        let startX;\n        let scrollLeft;\n\n        slider.addEventListener('mousedown', (e) => {\n            if (['INPUT', 'TEXTAREA', 'SELECT', 'OPTION', 'BUTTON'].includes(e.target.tagName)) return;\n            isDown = true;\n            slider.classList.add('active');\n            startX = e.pageX - slider.offsetLeft;\n            scrollLeft = slider.scrollLeft;\n            e.preventDefault(); \n        });\n\n        slider.addEventListener('mouseleave', () => { isDown = false; slider.classList.remove('active'); });\n        slider.addEventListener('mouseup', () => { isDown = false; slider.classList.remove('active'); });\n        slider.addEventListener('mousemove', (e) => {\n            if (!isDown) return;\n            e.preventDefault();\n            const x = e.pageX - slider.offsetLeft;\n            const walk = (x - startX) * 2; \n            slider.scrollLeft = scrollLeft - walk;\n        }); \n    <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Matriz GRC &#8211; KMCI 4.0 | Enterprise Edition KMCI 4.0 &#8211; Ingreso Corporativo Autenticaci\u00f3n segura Multi-Tenant requerida. ID de Empresa (Tenant) \/ Llave Maestra: Rol de Usuario (RBAC): Administrador (Control Total)Due\u00f1o de Proceso (Solo Planes de Acci\u00f3n)Alta Direcci\u00f3n \/ C-Level (Solo Lectura) Autenticar y Acceder \ud83d\udc51 Panel de Control Principal | Gesti\u00f3n de Clientes SaaS [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"ocean_post_layout":"","ocean_both_sidebars_style":"","ocean_both_sidebars_content_width":0,"ocean_both_sidebars_sidebars_width":0,"ocean_sidebar":"","ocean_second_sidebar":"","ocean_disable_margins":"enable","ocean_add_body_class":"","ocean_shortcode_before_top_bar":"","ocean_shortcode_after_top_bar":"","ocean_shortcode_before_header":"","ocean_shortcode_after_header":"","ocean_has_shortcode":"","ocean_shortcode_after_title":"","ocean_shortcode_before_footer_widgets":"","ocean_shortcode_after_footer_widgets":"","ocean_shortcode_before_footer_bottom":"","ocean_shortcode_after_footer_bottom":"","ocean_display_top_bar":"default","ocean_display_header":"default","ocean_header_style":"","ocean_center_header_left_menu":"","ocean_custom_header_template":"","ocean_custom_logo":0,"ocean_custom_retina_logo":0,"ocean_custom_logo_max_width":0,"ocean_custom_logo_tablet_max_width":0,"ocean_custom_logo_mobile_max_width":0,"ocean_custom_logo_max_height":0,"ocean_custom_logo_tablet_max_height":0,"ocean_custom_logo_mobile_max_height":0,"ocean_header_custom_menu":"","ocean_menu_typo_font_family":"","ocean_menu_typo_font_subset":"","ocean_menu_typo_font_size":0,"ocean_menu_typo_font_size_tablet":0,"ocean_menu_typo_font_size_mobile":0,"ocean_menu_typo_font_size_unit":"px","ocean_menu_typo_font_weight":"","ocean_menu_typo_font_weight_tablet":"","ocean_menu_typo_font_weight_mobile":"","ocean_menu_typo_transform":"","ocean_menu_typo_transform_tablet":"","ocean_menu_typo_transform_mobile":"","ocean_menu_typo_line_height":0,"ocean_menu_typo_line_height_tablet":0,"ocean_menu_typo_line_height_mobile":0,"ocean_menu_typo_line_height_unit":"","ocean_menu_typo_spacing":0,"ocean_menu_typo_spacing_tablet":0,"ocean_menu_typo_spacing_mobile":0,"ocean_menu_typo_spacing_unit":"","ocean_menu_link_color":"","ocean_menu_link_color_hover":"","ocean_menu_link_color_active":"","ocean_menu_link_background":"","ocean_menu_link_hover_background":"","ocean_menu_link_active_background":"","ocean_menu_social_links_bg":"","ocean_menu_social_hover_links_bg":"","ocean_menu_social_links_color":"","ocean_menu_social_hover_links_color":"","ocean_disable_title":"default","ocean_disable_heading":"default","ocean_post_title":"","ocean_post_subheading":"","ocean_post_title_style":"","ocean_post_title_background_color":"","ocean_post_title_background":0,"ocean_post_title_bg_image_position":"","ocean_post_title_bg_image_attachment":"","ocean_post_title_bg_image_repeat":"","ocean_post_title_bg_image_size":"","ocean_post_title_height":0,"ocean_post_title_bg_overlay":0.5,"ocean_post_title_bg_overlay_color":"","ocean_disable_breadcrumbs":"default","ocean_breadcrumbs_color":"","ocean_breadcrumbs_separator_color":"","ocean_breadcrumbs_links_color":"","ocean_breadcrumbs_links_hover_color":"","ocean_display_footer_widgets":"default","ocean_display_footer_bottom":"default","ocean_custom_footer_template":"","footnotes":""},"class_list":["post-8","page","type-page","status-publish","hentry","entry"],"_links":{"self":[{"href":"https:\/\/proyectosjcs.lat\/index.php?rest_route=\/wp\/v2\/pages\/8","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/proyectosjcs.lat\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/proyectosjcs.lat\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/proyectosjcs.lat\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/proyectosjcs.lat\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=8"}],"version-history":[{"count":2,"href":"https:\/\/proyectosjcs.lat\/index.php?rest_route=\/wp\/v2\/pages\/8\/revisions"}],"predecessor-version":[{"id":10,"href":"https:\/\/proyectosjcs.lat\/index.php?rest_route=\/wp\/v2\/pages\/8\/revisions\/10"}],"wp:attachment":[{"href":"https:\/\/proyectosjcs.lat\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=8"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}