RetinAI / web /diagnosis.html
ViannyCruz's picture
Update web/diagnosis.html
517090d verified
Raw
History Blame Contribute Delete
73 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="favicon.ico">
<title>Diagnóstico IA</title>
<link rel="stylesheet" href="css/simplebar.css">
<link href="https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,100;0,200;0,300;0,400;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<link rel="stylesheet" href="css/select2.css">
<link rel="stylesheet" href="css/dropzone.css">
<link rel="stylesheet" href="css/uppy.min.css">
<link rel="stylesheet" href="css/jquery.steps.css">
<link rel="stylesheet" href="css/jquery.timepicker.css">
<link rel="stylesheet" href="css/quill.snow.css">
<link rel="stylesheet" href="css/daterangepicker.css">
<script>
(function() {
var dark = localStorage.getItem('theme') === 'dark';
document.write(
'<link rel="stylesheet" href="css/app-' + (dark ? 'dark' : 'light') + '.css" id="' + (dark ? 'dark' : 'light') + 'Theme">' +
'<link rel="stylesheet" href="css/app-' + (dark ? 'light' : 'dark') + '.css" id="' + (dark ? 'light' : 'dark') + 'Theme" disabled>'
);
})();
</script>
<style>
.upload-zone {
border: 3px dashed #dee2e6;
border-radius: 12px;
padding: 3rem 2rem;
text-align: center;
background: #f8f9fa;
transition: all 0.3s ease;
cursor: pointer;
min-height: 300px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
user-select: none;
}
.upload-zone:hover {
background: #f3f0ff;
border-color: #6c757d;
transform: translateY(-2px);
}
.upload-zone.dragover {
border-color: #28a745;
background: #f0fff4;
transform: scale(1.02);
}
.upload-zone * {
pointer-events: none;
}
.image-preview {
max-width: 100%;
max-height: 400px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.analysis-card {
color: white;
border-radius: 12px;
overflow: hidden;
}
.result-positive {
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
border-left: 4px solid #dc3545;
}
.result-negative {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
border-left: 4px solid #28a745;
}
.confidence-meter {
height: 8px;
background: #e9ecef;
border-radius: 4px;
overflow: hidden;
}
.confidence-fill {
height: 100%;
background: linear-gradient(90deg, #28a745, #ffc107, #dc3545);
transition: width 0.8s ease;
}
.patient-search {
position: relative;
}
.patient-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #dee2e6;
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
display: none;
}
.patient-item {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #f8f9fa;
}
.patient-item:hover {
background: #f8f9fa;
}
.model-status {
position: fixed;
top: 20px;
right: 20px;
z-index: 1050;
}
.pulse-animation {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.fade-in {
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.gradcam-btn {
background-color: #dc3545;
border: 1px solid #dc3545;
color: white;
}
.gradcam-btn:hover {
background-color: #c82333;
border-color: #bd2130;
color: white;
}
.gradcam-modal .modal-dialog {
max-width: 95vw;
}
.gradcam-modal .modal-content {
border-radius: 12px;
overflow: hidden;
}
.dual-analysis-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.analysis-column {
flex: 1;
min-width: 300px;
}
@media (max-width: 992px) {
.dual-analysis-container {
flex-direction: column;
}
}
.hidden-file-input {
position: absolute;
left: -9999px;
opacity: 0;
pointer-events: none;
}
/* ── TOAST NOTIFICATIONS ─────────────────────────── */
#toast-container {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
z-index: 99999;
display: flex;
flex-direction: column;
gap: 0.6rem;
pointer-events: none;
}
.toast-notify {
display: flex;
align-items: flex-start;
gap: 12px;
min-width: 300px;
max-width: 380px;
padding: 14px 16px;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
background: #fff;
border-left: 5px solid #ccc;
pointer-events: all;
opacity: 0;
transform: translateX(60px);
transition: opacity 0.3s ease, transform 0.3s ease;
position: relative;
overflow: hidden;
}
.toast-notify.show { opacity: 1; transform: translateX(0); }
.toast-notify.hide { opacity: 0; transform: translateX(60px); }
.toast-notify.toast-success { border-left-color: #28a745; }
.toast-notify.toast-error { border-left-color: #dc3545; }
.toast-notify.toast-warning { border-left-color: #ffc107; }
.toast-notify.toast-info { border-left-color: #17a2b8; }
.toast-icon { flex-shrink: 0; margin-top: 1px; }
.toast-success .toast-icon svg { stroke: #28a745; }
.toast-error .toast-icon svg { stroke: #dc3545; }
.toast-warning .toast-icon svg { stroke: #ffc107; }
.toast-info .toast-icon svg { stroke: #17a2b8; }
.toast-body { flex: 1; }
.toast-title { font-weight: 600; font-size: 0.875rem; margin-bottom: 2px; }
.toast-success .toast-title { color: #155724; }
.toast-error .toast-title { color: #721c24; }
.toast-warning .toast-title { color: #856404; }
.toast-info .toast-title { color: #0c5460; }
.toast-msg { font-size: 0.825rem; color: #555; line-height: 1.4; }
.toast-close {
background: none; border: none; font-size: 1rem;
line-height: 1; color: #aaa; cursor: pointer; padding: 0; flex-shrink: 0;
}
.toast-close:hover { color: #333; }
.toast-progress {
position: absolute;
bottom: 0; left: 0;
height: 3px;
border-radius: 0 0 0 10px;
}
.toast-success .toast-progress { background: #28a745; }
.toast-error .toast-progress { background: #dc3545; }
.toast-warning .toast-progress { background: #ffc107; }
.toast-info .toast-progress { background: #17a2b8; }
.alert-info h6,
.alert-info p,
.alert-info strong {
color: #212529 !important;
}
.alert-light small,
.alert-light strong {
color: #212529 !important;
}
.patient-results {
color: #212529 !important;
}
.patient-item,
.patient-item strong,
.patient-item small {
color: #212529 !important;
}
</style>
</head>
<body class="vertical light">
<div class="wrapper">
<!-- Navegación superior -->
<nav class="topnav navbar navbar-light">
<button type="button" class="navbar-toggler text-muted mt-2 p-0 mr-3 collapseSidebar">
<i data-feather="menu" class="navbar-toggler-icon"></i>
</button>
<ul class="nav">
<li class="nav-item">
<a class="nav-link text-muted my-2" href="#" id="modeSwitcher" data-mode="light">
<i data-feather="sun"></i>
</a>
<li class="nav-item">
<a href="#" onclick="confirmLogout()" class="nav-link text-muted" title="Cerrar sesión" style="display: flex; align-items: center; gap: 6px;">
<span class="avatar avatar-sm mt-2">
<img src="./assets/images/persona.png" alt="..." class="avatar-img rounded-circle">
</span>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" title="Cerrar sesión">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
</a>
</li>
</ul>
</nav>
<aside class="sidebar-left border-right bg-white shadow" id="leftSidebar" data-simplebar>
<a href="#" class="btn collapseSidebar toggle-btn d-lg-none text-muted ml-2 mt-3" data-toggle="toggle">
<i data-feather="x"><span class="sr-only"></span></i>
</a>
<nav class="vertnav navbar navbar-light">
<!-- nav bar -->
<div class="w-100 mb-4 d-flex">
<a class="navbar-brand mx-auto mt-2 flex-fill text-center">
<img src="assets/images/RetinAILogo.png" alt="Logo" id="sidebarLogo" style="max-width:140px; transition: opacity 0.3s ease;">
<img src="assets/images/LogoMini.png" alt="Logo Mini" id="sidebarLogoMini" style="max-width:44px; display:none; transition: opacity 0.3s ease;">
</a>
</div>
<ul class="navbar-nav flex-fill w-100 mb-2">
<li class="nav-item">
<a class="nav-link" href="./index.html">
<i data-feather="home" class="fe-16"></i>
<span class="ml-3 item-text">Dashboard</span>
</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="./diagnosis.html">
<i data-feather="eye" class="fe-16"></i>
<span class="ml-3 item-text">Diagnóstico</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="./patients.html">
<i data-feather="users" class="fe-16"></i>
<span class="ml-3 item-text">Pacientes</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="./historial-consultas.html">
<i data-feather="file-text" class="fe-16"></i>
<span class="ml-3 item-text">Consultas</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="./users.html">
<i data-feather="user" class="fe-16"></i>
<span class="ml-3 item-text">Usuarios</span>
</a>
</li>
</ul>
</nav>
</aside>
<!-- Contenido principal -->
<main role="main" class="main-content">
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-12">
<!-- Header -->
<div class="row align-items-center mb-4">
<div class="col">
<h2 class="h3 page-title">
<i data-feather="eye" class="fe-24 mr-2 text-primary"></i>
Diagnóstico con Inteligencia Artificial
</h2>
<p class="text-muted">Diagnostico de retinopatía diabética mediante análisis de imágenes</p>
</div>
</div>
<!-- Formulario principal -->
<div class="row">
<div class="col-lg-8">
<div class="card shadow mb-4">
<div class="card-header">
<h5 class="card-title mb-0">
<i data-feather="upload" class="fe-16 mr-2"></i>Cargar Imágenes para Análisis
</h5>
</div>
<div class="card-body">
<div class="dual-analysis-container">
<!-- Columna 1 - Ojo Derecho -->
<div class="analysis-column">
<h6 class="text-center mb-3">Ojo Derecho</h6>
<!-- Input oculto para zona 2 -->
<input type="file" id="imageInput2" class="hidden-file-input" accept="image/*">
<!-- Zona de carga 2 -->
<div id="uploadZone2" class="upload-zone" data-zone="2">
<i data-feather="camera" class="fe-48 text-muted mb-3"></i>
<h5 class="text-muted">Arrastra una imagen aquí o haz clic para seleccionar</h5>
<p class="text-muted mb-0">Formatos soportados: JPG, PNG, JPEG (Máx. 10MB)</p>
</div>
<!-- Preview de imagen 2 -->
<div id="imagePreview2" style="display: none;" class="text-center mt-4">
<img id="previewImg2" class="image-preview" alt="Vista previa">
<div class="mt-3">
<button class="btn btn-success btn-lg mr-2" id="analyzeBtn2">
<i data-feather="zap" class="fe-16 mr-2"></i>Analizar con IA
</button>
<button class="btn btn-outline-secondary mr-2" id="clearBtn2">
<i data-feather="x" class="fe-16 mr-2"></i>Limpiar
</button>
<button class="btn btn-warning gradcam-btn" id="gradcamBtn2" style="display: none;">
<i data-feather="eye" class="fe-16 mr-2"></i>Ver Grad-CAM
</button>
</div>
</div>
<!-- Resultados del analisis 2 -->
<div id="analysisResults2" style="display: none;" class="mt-4">
<div class="card shadow mb-4 analysis-card">
<div class="card-body">
<h5 class="mb-4">
<i data-feather="activity" class="fe-16 mr-2"></i>Resultados - Ojo Derecho
</h5>
<div id="resultContent2"></div>
</div>
</div>
</div>
</div>
<!-- Columna 2 - Ojo Izquierdo -->
<div class="analysis-column">
<h6 class="text-center mb-3">Ojo Izquierdo</h6>
<!-- Input oculto para zona 1 -->
<input type="file" id="imageInput1" class="hidden-file-input" accept="image/*">
<!-- Zona de carga 1 -->
<div id="uploadZone1" class="upload-zone" data-zone="1">
<i data-feather="camera" class="fe-48 text-muted mb-3"></i>
<h5 class="text-muted">Arrastra una imagen aquí o haz clic para seleccionar</h5>
<p class="text-muted mb-0">Formatos soportados: JPG, PNG, JPEG (Máx. 10MB)</p>
</div>
<!-- Preview de imagen 1 -->
<div id="imagePreview1" style="display: none;" class="text-center mt-4">
<img id="previewImg1" class="image-preview" alt="Vista previa">
<div class="mt-3">
<button class="btn btn-success btn-lg mr-2" id="analyzeBtn1">
<i data-feather="zap" class="fe-16 mr-2"></i>Analizar con IA
</button>
<button class="btn btn-outline-secondary mr-2" id="clearBtn1">
<i data-feather="x" class="fe-16 mr-2"></i>Limpiar
</button>
<button class="btn btn-warning gradcam-btn" id="gradcamBtn1" style="display: none;">
<i data-feather="eye" class="fe-16 mr-2"></i>Ver Grad-CAM
</button>
</div>
</div>
<!-- Resultados del analisis 1 -->
<div id="analysisResults1" style="display: none;" class="mt-4">
<div class="card shadow mb-4 analysis-card">
<div class="card-body">
<h5 class="mb-4">
<i data-feather="activity" class="fe-16 mr-2"></i>Resultados - Ojo Izquierdo
</h5>
<div id="resultContent1"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Panel lateral -->
<div class="col-lg-4">
<div class="card shadow mb-4">
<div class="card-header">
<h6 class="card-title mb-0">
<i data-feather="user" class="fe-16 mr-2"></i>Información del Paciente y Consulta
</h6>
</div>
<div class="card-body">
<div class="form-group patient-search">
<label for="patientSearch">Buscar Paciente</label>
<input type="text" class="form-control" id="patientSearch" placeholder="Nombre del paciente...">
<div id="patientResults" class="patient-results"></div>
</div>
<div id="selectedPatientInfo" style="display: none;">
<div class="alert alert-info">
<h6 id="patientName"></h6>
<p class="mb-0" id="patientDetails"></p>
<input type="hidden" id="selectedPatientId">
</div>
</div>
<hr>
<div class="card">
<div class="card-header bg-light">
<h5 class="mr-2">Ojo Derecho</h5>
</div>
<div class="form-group" style="margin: 20px">
<label for="diabeticRetinopathy_right">Retinopatía Diabética</label>
<select class="form-control" id="diabeticRetinopathy_right">
<option value="false">No</option>
<option value="true"></option>
</select>
</div>
</div>
<hr>
<div class="card">
<div class="card-header bg-light">
<h5 class="mr-2">Ojo Izquierdo</h5>
</div>
<div class="form-group" style="margin: 20px">
<label for="diabeticRetinopathy_left">Retinopatía Diabética</label>
<select class="form-control" id="diabeticRetinopathy_left">
<option value="false">No</option>
<option value="true"></option>
</select>
</div>
</div>
<hr>
<div class="form-group">
<label for="consultationNotes">Notas de la Consulta</label>
<textarea class="form-control" id="consultationNotes" rows="3" placeholder="Observaciones adicionales..."></textarea>
</div>
<button class="btn btn-primary btn-block" id="saveConsultationBtn" type="button">
<i data-feather="save" class="fe-16 mr-2"></i>Guardar Consulta
</button>
</div>
</div>
<!-- Importante -->
<div class="card shadow mb-4">
<div class="card-header">
<h6 class="card-title mb-0">
<i data-feather="info" class="fe-16 mr-2"></i>Importante
</h6>
</div>
<div class="card-body">
<div class="alert alert-light">
<small>
<strong>Recuerda:</strong> La IA es una herramienta de apoyo.
Siempre confirma el diagnóstico con examen clínico completo.
</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="js/jquery.min.js"></script>
<script src="js/popper.min.js"></script>
<script src="js/moment.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/simplebar.min.js"></script>
<script src="js/daterangepicker.js"></script>
<script src="js/jquery.stickOnScroll.js"></script>
<script src="js/tinycolor-min.js"></script>
<script src="js/config.js"></script>
<script src="api.js"></script>
<script>
$(document).ready(function() {
console.log('Sistema de diagnostico iniciado');
// Variables globales
let currentUser = null;
let selectedPatient = null;
let lastAnalysisResult1 = null; // Ojo IZQUIERDO (zona 1)
let lastAnalysisResult2 = null; // Ojo DERECHO (zona 2)
let todayStats = { analysis: 0, positive: 0 };
// Inicializar aplicacion
initializeDiagnosisApp();
// Auto-seleccionar paciente si viene por URL (?patient=ID)
async function autoSelectPatientFromURL() {
const params = new URLSearchParams(window.location.search);
const patientId = params.get('patient');
if (!patientId) return;
const response = await api.getPatient(parseInt(patientId)).catch(() => null);
if (!response || !response.success) return;
const patientData = response.patient;
selectedPatient = patientData;
$('#patientSearch').val(patientData.name);
$('#patientResults').hide();
const age = patientData.birthDate
? (moment ? moment().diff(moment(patientData.birthDate), 'years') : 'N/A')
: 'N/A';
$('#selectedPatientInfo').show();
$('#patientName').text(patientData.name);
$('#patientDetails').html(`
<strong>ID:</strong> ${patientData.patientID} |
<strong>Edad:</strong> ${age} años
${patientData.diabetesType ? `| <strong>Diabetes:</strong> ${patientData.diabetesType}` : ''}
`);
$('#selectedPatientId').val(patientData.patientID);
enableSaveButton();
showAlert('info', `Paciente ${patientData.name} asociado al diagnóstico`);
}
// Esperar a que jQuery esté listo antes de autoseleccionar
$(document).ready(function() {
autoSelectPatientFromURL();
});
// FUNCION DE INICIALIZACION
function initializeDiagnosisApp() {
console.log('Inicializando aplicacion de diagnostico...');
checkAuthentication();
checkModelStatus();
setupEventListeners();
loadTodayStats();
updateLastUpdate();
setInterval(updateLastUpdate, 60000);
}
// AUTENTICACION
function checkAuthentication() {
api.getSession().then(function(response) {
if (response && response.success) {
currentUser = response.user;
} else {
window.location.href = 'auth-login.html';
}
}).catch(() => { window.location.href = 'auth-login.html'; });
}
// VERIFICAR ESTADO DEL MODELO
function checkModelStatus() {
api.getModelInfo().then(function(response) {
if (response && response.loaded) {
$('#modelStatusBadge').removeClass('badge-danger').addClass('badge-success');
$('#modelStatusText').text('Modelo Cargado');
} else {
$('#modelStatusBadge').removeClass('badge-success').addClass('badge-danger');
$('#modelStatusText').text('Modelo No Disponible');
showAlert('warning', 'El modelo de IA no está disponible.');
}
});
}
// EVENT LISTENERS
function setupEventListeners() {
console.log('Configurando event listeners...');
$(document).on('click', '#uploadZone1', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('Click detectado en zona 1');
$('#imageInput1')[0].click();
});
$(document).on('click', '#uploadZone2', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('Click detectado en zona 2');
$('#imageInput2')[0].click();
});
// Carga de archivos para ambas zonas
$(document).on('change', '#imageInput1', function(e) {
console.log('Archivo seleccionado en zona 1');
handleImageSelect(e, 1);
});
$(document).on('change', '#imageInput2', function(e) {
console.log('Archivo seleccionado en zona 2');
handleImageSelect(e, 2);
});
// Drag and drop para ambas zonas
setupDragAndDrop(1);
setupDragAndDrop(2);
// Analisis y limpieza para ambas zonas
$(document).on('click', '#analyzeBtn1', function() { handleImageAnalysis(1); });
$(document).on('click', '#analyzeBtn2', function() { handleImageAnalysis(2); });
$(document).on('click', '#clearBtn1', function() { clearImagePreview(1); });
$(document).on('click', '#clearBtn2', function() { clearImagePreview(2); });
// Grad-CAM para ambas zonas
$(document).on('click', '#gradcamBtn1', function() { showGradCAMAnalysis(1); });
$(document).on('click', '#gradcamBtn2', function() { showGradCAMAnalysis(2); });
// Busqueda de pacientes
$(document).on('input', '#patientSearch', handlePatientSearch);
$(document).on('click', '.patient-item', handlePatientSelection);
// Guardar consulta
$(document).on('click', '#saveConsultationBtn', function(e) {
e.preventDefault();
console.log('Boton guardar consulta clickeado');
handleSaveConsultation();
});
// Eventos para selectores manuales
$(document).on('change', '#diabeticRetinopathy_right, #diabeticRetinopathy_left', function() {
console.log('Diagnostico manual cambiado');
enableSaveButton();
});
// Cerrar dropdowns al hacer click fuera
$(document).on('click', function(e) {
if (!$(e.target).closest('.patient-search').length) {
$('#patientResults').hide();
}
});
console.log('Event listeners configurados correctamente');
}
// DRAG AND DROP
function setupDragAndDrop(zoneNumber) {
const uploadZone = document.getElementById(`uploadZone${zoneNumber}`);
if (!uploadZone) return;
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
uploadZone.addEventListener(eventName, preventDefaults, false);
});
['dragenter', 'dragover'].forEach(eventName => {
uploadZone.addEventListener(eventName, function() { highlight(zoneNumber); }, false);
});
['dragleave', 'drop'].forEach(eventName => {
uploadZone.addEventListener(eventName, function() { unhighlight(zoneNumber); }, false);
});
uploadZone.addEventListener('drop', function(e) { handleDrop(e, zoneNumber); }, false);
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
function highlight(zoneNumber) {
$(`#uploadZone${zoneNumber}`).addClass('dragover');
}
function unhighlight(zoneNumber) {
$(`#uploadZone${zoneNumber}`).removeClass('dragover');
}
function handleDrop(e, zoneNumber) {
const files = e.dataTransfer.files;
if (files.length > 0) {
console.log(`Archivo arrastrado a zona ${zoneNumber}`);
handleImageFile(files[0], zoneNumber);
}
}
}
// MANEJO DE ARCHIVOS
function handleImageSelect(e, zoneNumber) {
const file = e.target.files[0];
if (file) {
console.log(`Imagen seleccionada para zona ${zoneNumber}:`, file.name);
handleImageFile(file, zoneNumber);
}
}
function handleImageFile(file, zoneNumber) {
// Validaciones
if (!file.type.startsWith('image/')) {
showAlert('error', 'Por favor selecciona un archivo de imagen valido');
return;
}
if (file.size > 20 * 1024 * 1024) {
showAlert('error', 'La imagen es demasiado grande. Máximo 20MB');
return;
}
// Mostrar preview
const reader = new FileReader();
reader.onload = function(e) {
$(`#previewImg${zoneNumber}`).attr('src', e.target.result);
$(`#imagePreview${zoneNumber}`).show().addClass('fade-in');
$(`#uploadZone${zoneNumber}`).hide();
$(`#analysisResults${zoneNumber}`).hide();
$(`#analyzeBtn${zoneNumber}`).prop('disabled', false);
$(`#gradcamBtn${zoneNumber}`).hide();
};
reader.readAsDataURL(file);
console.log(`Imagen cargada en zona ${zoneNumber}:`, file.name);
showAlert('success', `Imagen cargada correctamente en ojo ${zoneNumber === 1 ? 'izquierdo' : 'derecho'}. Procede al analisis.`);
}
// ANALISIS DE IMAGEN
function handleImageAnalysis(zoneNumber) {
if (!$(`#previewImg${zoneNumber}`).attr('src')) {
showAlert('error', 'No hay imagen para analizar');
return;
}
// Mostrar loading
$(`#analyzeBtn${zoneNumber}`).prop('disabled', true)
.html('<i class="spinner-border spinner-border-sm mr-2"></i>Analizando...')
.addClass('pulse-animation');
// Obtener datos
const imageData = $(`#previewImg${zoneNumber}`).attr('src');
const fileName = $(`#imageInput${zoneNumber}`)[0].files[0]?.name || `analisis_${Date.now()}.jpg`;
const patientId = selectedPatient ? selectedPatient.patientID : null;
const notes = $('#consultationNotes').val().trim();
console.log(`Iniciando analisis zona ${zoneNumber}:`, {
fileName,
patientId,
hasNotes: !!notes
});
if (false) { // simulation removed
console.log('Eel no disponible - simulando analisis');
setTimeout(() => {
const mockResult = {
success: true,
prediction: {
class: Math.random() > 0.5 ? 'Retinopatía Diabética' : 'Normal',
class_index: Math.random() > 0.5 ? 1 : 0,
confidence: Math.floor(Math.random() * 30 + 70) // 70-100%
},
timestamp: new Date().toISOString(),
filename: fileName
};
if (zoneNumber === 1) {
lastAnalysisResult1 = mockResult;
} else {
lastAnalysisResult2 = mockResult;
}
$(`#analyzeBtn${zoneNumber}`).prop('disabled', false)
.html('<i data-feather="zap" class="fe-16 mr-2"></i>Analizar con IA')
.removeClass('pulse-animation');
showAnalysisResults(mockResult, zoneNumber);
enableSaveButton();
}, 2000);
return;
}
// Llamar al modelo
api.predict(imageData, fileName).then(function(result) {
// Restaurar boton
$(`#analyzeBtn${zoneNumber}`).prop('disabled', false)
.html('<i data-feather="zap" class="fe-16 mr-2"></i>Analizar con IA')
.removeClass('pulse-animation');
if (result && result.success) {
if (zoneNumber === 1) {
lastAnalysisResult1 = result;
} else {
lastAnalysisResult2 = result;
}
showAnalysisResults(result, zoneNumber);
updateTodayStats();
// Habilitar boton guardar siempre que haya analisis
enableSaveButton();
console.log(`Analisis completado zona ${zoneNumber}:`, result.prediction.class);
} else {
showAlert('error', result?.error || 'Error en el analisis de la imagen');
console.log(`Error en analisis zona ${zoneNumber}:`, result);
}
});
}
// === FUNCION PARA HABILITAR BOTON GUARDAR ===
function enableSaveButton() {
// Habilitar si hay paciente seleccionado Y (analisis o diagoóstico manual)
const hasPatient = selectedPatient !== null;
const hasAnalysis = lastAnalysisResult1 !== null || lastAnalysisResult2 !== null;
const hasManualDiagnosis = $('#diabeticRetinopathy_right').val() !== '' || $('#diabeticRetinopathy_left').val() !== '';
const shouldEnable = hasPatient && (hasAnalysis || hasManualDiagnosis);
console.log('Verificando boton guardar:', {
hasPatient,
hasAnalysis,
hasManualDiagnosis,
shouldEnable
});
$('#saveConsultationBtn').prop('disabled', !shouldEnable);
if (shouldEnable) {
$('#saveConsultationBtn').removeClass('btn-secondary').addClass('btn-primary');
} else {
$('#saveConsultationBtn').removeClass('btn-primary').addClass('btn-secondary');
}
}
// === MOSTRAR RESULTADOS ===
function showAnalysisResults(result, zoneNumber) {
const prediction = result.prediction;
const isPositive = prediction.class_index === 1;
const confidence = prediction.confidence;
// Determinar clase de resultado y colores
const resultClass = isPositive ? 'border-warning' : 'border-success';
const iconClass = isPositive ? 'fe-alert-triangle' : 'fe-check-circle';
const iconColor = isPositive ? 'text-warning' : 'text-success';
const resultText = isPositive ? 'Retinopatía Diabética Detectada' : 'No se Detectó Retinopatía Diabética';
const eyeText = zoneNumber === 1 ? 'Ojo Izquierdo' : 'Ojo Derecho';
const resultHtml = `
<!-- Resultado Principal -->
<div class="card shadow-sm ${resultClass} border-2">
<div class="card-body">
<div class="row align-items-center">
<div class="col-2 text-center">
<i class="fe ${iconClass} fe-48 ${iconColor}"></i>
</div>
<div class="col-10">
<h5 class="card-title mb-1">${resultText}</h5>
<p class="text-muted mb-0">${eyeText}</p>
</div>
</div>
</div>
</div>
<!-- Detalles del Analisis -->
<div class="card shadow-sm mt-3">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i data-feather="bar-chart-2" class="fe-16 mr-2"></i>Detalles del Análisis
</h6>
<span class="badge badge-pill ${isPositive ? 'badge-warning' : 'badge-success'}">${confidence}%</span>
</div>
<div class="card-body">
<!-- Medidor de Confianza -->
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<small class="text-muted">Nivel de Confianza</small>
<small class="font-weight-bold">${confidence}%</small>
</div>
<div class="progress" style="height: 8px;">
<div class="progress-bar ${isPositive ? 'bg-warning' : 'bg-success'}"
role="progressbar"
style="width: ${confidence}%"
aria-valuenow="${confidence}"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
</div>
<!-- Informacion Temporal -->
<div class="row text-center">
<div class="col-6">
<div class="border-right">
<h6 class="text-muted mb-0">Análisis realizado</h6>
<small class="text-muted">
<i data-feather="clock" class="fe-12 mr-1"></i>
${moment ? moment(result.timestamp).format('DD/MM/YYYY HH:mm') : new Date(result.timestamp).toLocaleString()}
</small>
</div>
</div>
<div class="col-6">
<h6 class="text-muted mb-0">Modelo utilizado</h6>
<small class="text-muted">
<i data-feather="cpu" class="fe-12 mr-1"></i>
EfficientNetB0
</small>
</div>
</div>
</div>
</div>
`;
$(`#resultContent${zoneNumber}`).html(resultHtml);
$(`#analysisResults${zoneNumber}`).show().addClass('fade-in');
// Cambiar el fondo del contenedor de resultados
$(`#analysisResults${zoneNumber} .analysis-card`).css({
'background': '#f8f9fa',
'color': '#495057'
});
// Scroll suave hacia los resultados
$('html, body').animate({
scrollTop: $(`#analysisResults${zoneNumber}`).offset().top - 100
}, 800);
// Actualizar selectores correctamente
if(zoneNumber === 1) {
// Zona 1 = Ojo IZQUIERDO
document.getElementById('diabeticRetinopathy_left').value = isPositive.toString();
} else {
// Zona 2 = Ojo DERECHO
document.getElementById('diabeticRetinopathy_right').value = isPositive.toString();
}
// Mostrar boton Grad-CAM solo si se detecto retinopatia diabetica
if (isPositive) {
$(`#gradcamBtn${zoneNumber}`).show().addClass('pulse-animation');
console.log(`Retinopatia detectada en zona ${zoneNumber} - Grad-CAM disponible`);
} else {
$(`#gradcamBtn${zoneNumber}`).hide();
}
// Habilitar boton guardar
enableSaveButton();
}
// === BUSQUEDA DE PACIENTES ===
function handlePatientSearch() {
const searchTerm = $('#patientSearch').val().trim();
if (searchTerm.length < 1) {
// Mostrar todos los pacientes si el campo está vacío
api.getPatients('').then(function(response) {
if (response && response.success && response.patients.length > 0) {
displayPatientResults(response.patients);
} else {
$('#patientResults').hide();
}
});
return;
}
api.getPatients(searchTerm).then(function(response) {
if (response && response.success) {
displayPatientResults(response.patients);
} else {
$('#patientResults').hide();
}
});
}
function displayPatientResults(patients) {
const resultsHtml = patients.map(patient => `
<div class="patient-item" data-patient='${JSON.stringify(patient)}'>
<strong>${patient.name}</strong>
${patient.birthDate ? `<br><small class="text-muted">Nacimiento: ${moment ? moment(patient.birthDate).format('DD/MM/YYYY') : patient.birthDate}</small>` : ''}
${patient.diabetesType ? `<br><small class="text-muted">Diabetes: ${patient.diabetesType}</small>` : ''}
</div>
`).join('');
$('#patientResults').html(resultsHtml).show();
}
function handlePatientSelection() {
const patientData = $(this).data('patient');
selectedPatient = patientData;
$('#patientSearch').val(patientData.name);
$('#patientResults').hide();
// Mostrar informacion del paciente
const age = patientData.birthDate ? (moment ? moment().diff(moment(patientData.birthDate), 'years') : 'N/A') : 'N/A';
$('#selectedPatientInfo').show();
$('#patientName').text(patientData.name);
$('#patientDetails').html(`
<strong>ID:</strong> ${patientData.patientID} |
<strong>Edad:</strong> ${age} años
${patientData.diabetesType ? `| <strong>Diabetes:</strong> ${patientData.diabetesType}` : ''}
`);
$('#selectedPatientId').val(patientData.patientID);
console.log('Paciente seleccionado:', patientData.name);
showAlert('info', `Paciente ${patientData.name} seleccionado correctamente`);
// Habilitar boton de guardar
enableSaveButton();
}
// === FUNCION CORREGIDA GUARDAR CONSULTA ===
function handleSaveConsultation() {
console.log('Iniciando guardado de consulta...');
if (!selectedPatient) {
showAlert('error', 'Selecciona un paciente antes de guardar');
return;
}
const notes = $('#consultationNotes').val().trim();
// Datos correctamente estructurados
const consultationData = {
patientId: selectedPatient.patientID,
notes: notes,
rightEye: {
hasAnalysis: lastAnalysisResult2 !== null, // Zona 2 = Ojo DERECHO
diagnosis: lastAnalysisResult2 ?
(lastAnalysisResult2.prediction.class_index === 1) :
($('#diabeticRetinopathy_right').val() === 'true'),
confidence: lastAnalysisResult2 ? lastAnalysisResult2.prediction.confidence : null,
imagePath: lastAnalysisResult2 ? lastAnalysisResult2.filename : null
},
leftEye: {
hasAnalysis: lastAnalysisResult1 !== null, // Zona 1 = Ojo IZQUIERDO
diagnosis: lastAnalysisResult1 ?
(lastAnalysisResult1.prediction.class_index === 1) :
($('#diabeticRetinopathy_left').val() === 'true'),
confidence: lastAnalysisResult1 ? lastAnalysisResult1.prediction.confidence : null,
imagePath: lastAnalysisResult1 ? lastAnalysisResult1.filename : null
}
};
// Log detallado para debug
console.log('Datos de consulta a guardar:', {
paciente: selectedPatient.name,
rightEye: consultationData.rightEye,
leftEye: consultationData.leftEye,
notes: notes
});
// Mostrar loading
$('#saveConsultationBtn').prop('disabled', true)
.html('<i class="spinner-border spinner-border-sm mr-2"></i>Guardando...');
if (false) { // simulation removed
console.log('Eel no disponible - simulando guardado');
setTimeout(() => {
$('#saveConsultationBtn').prop('disabled', false)
.html('<i data-feather="save" class="fe-16 mr-2"></i>Guardar Consulta');
showAlert('success', 'Consulta guardada correctamente (simulación)');
}, 1500);
return;
}
// Llamada correcta a EEL
api.saveConsultation(consultationData).then(function(response) {
console.log('Respuesta del servidor:', response);
// Restaurar boton
$('#saveConsultationBtn').prop('disabled', false)
.html('<i data-feather="save" class="fe-16 mr-2"></i>Guardar Consulta');
if (response && response.success) {
showAlert('success', 'Consulta guardada correctamente');
console.log('Consulta guardada exitosamente:', response);
} else {
const errorMsg = response?.message || 'Error al guardar la consulta';
showAlert('error', errorMsg);
console.error('Error guardando consulta:', response);
}
});
}
// === FUNCION GRAD-CAM ===
function showGradCAMAnalysis(zoneNumber) {
const lastResult = zoneNumber === 1 ? lastAnalysisResult1 : lastAnalysisResult2;
if (!lastResult) {
showAlert('error', 'No hay analisis disponible para generar Grad-CAM');
return;
}
// Solo generar si se detecto retinopatia
if (lastResult.prediction.class_index !== 1) {
showAlert('info', 'Grad-CAM solo se genera para casos positivos de retinopatia diabetica');
return;
}
const imageData = $(`#previewImg${zoneNumber}`).attr('src');
const fileName = lastResult.filename;
const eyeName = zoneNumber === 1 ? 'Ojo Izquierdo' : 'Ojo Derecho';
// Mostrar loading en el boton
$(`#gradcamBtn${zoneNumber}`).prop('disabled', true)
.html('<i class="spinner-border spinner-border-sm mr-2"></i>Generando...');
console.log(`Generando Grad-CAM para zona ${zoneNumber}:`, fileName);
if (false) { /* removed */ }
// Llamar a la funcion de backend
api.gradcam(imageData, fileName, lastResult).then(function(response) {
// Restaurar boton
$(`#gradcamBtn${zoneNumber}`).prop('disabled', false)
.html('<i data-feather="eye" class="fe-16 mr-2"></i>Ver Grad-CAM');
if (response && response.success) {
console.log('Grad-CAM generado exitosamente');
showGradCAMModal(response, eyeName, zoneNumber);
} else {
const errorMsg = response?.error || 'Error generando analisis Grad-CAM';
showAlert('error', errorMsg);
console.error('Error Grad-CAM:', response);
}
});
}
function showGradCAMModal(gradcamData, eyeName, zoneNumber) {
const analysis = gradcamData.analysis || {};
// Campos con valores por defecto para compatibilidad
const maxAct = (analysis.max_activation || 0).toFixed(3);
const avgAct = (analysis.avg_activation || 0).toFixed(3);
const highPct = (analysis.high_activation_pct || analysis.zoom_high_activation_pct || 0).toFixed(1);
const clinical = analysis.clinical_info || 'Análisis completado';
const region = analysis.zoom_region_hd || [0,0,0,0];
const alertType = parseFloat(highPct) > 20 ? 'danger' : parseFloat(highPct) > 10 ? 'warning' : 'info';
const modalHtml = `
<div class="modal fade gradcam-modal" id="gradcamModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title">
<i data-feather="eye" class="mr-2"></i>Zoom HD - ${eyeName}
<span class="badge badge-light text-danger ml-2">Retinopatía Detectada</span>
</h5>
<button type="button" class="close text-white" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body p-0">
<div class="text-center bg-white p-4">
<img src="${gradcamData.gradcam_image}"
class="img-fluid"
style="max-width: 100%; border-radius: 8px; border: 2px solid #dc3545;"
alt="Zoom HD - Región Crítica">
<div class="mt-3">
<h6 class="text-danger mb-2">
<i data-feather="zoom-in" class="mr-2"></i>Zona Crítica en Alta Resolución
</h6>
<p class="text-muted mb-0">
<strong>Activación máxima:</strong> ${maxAct} |
<strong>Activación promedio:</strong> ${avgAct}
</p>
</div>
</div>
<div class="p-4">
<div class="alert alert-info">
<h6 class="text-info mb-2">Coordenadas en imagen original:</h6>
<p class="mb-1">Región HD: [x: ${region[0]}${region[2]}, y: ${region[1]}${region[3]}]</p>
<p class="mb-0"><strong>Área de alta activación:</strong> ${highPct}% del total</p>
</div>
<div class="alert alert-${alertType}">
<small>
<i data-feather="info" class="mr-1"></i>
${clinical}
</small>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary" onclick="downloadGradCAMImage(${zoneNumber})">
<i data-feather="download" class="mr-2"></i>Descargar Imagen
</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">
<i data-feather="x" class="mr-2"></i>Cerrar
</button>
</div>
</div>
</div>
</div>
`;
// Remover modal existente si lo hay
$('#gradcamModal').remove();
// Agregar y mostrar nuevo modal
$('body').append(modalHtml);
$('#gradcamModal').modal('show').on('hidden.bs.modal', function() {
$(this).remove();
});
// Guardar datos para descarga
window.currentGradCAMData = gradcamData;
}
// === FUNCIONES DE UTILIDAD ===
function clearImagePreview(zoneNumber) {
$(`#imagePreview${zoneNumber}`).hide();
$(`#analysisResults${zoneNumber}`).hide();
$(`#uploadZone${zoneNumber}`).show();
$(`#imageInput${zoneNumber}`).val('');
$(`#analyzeBtn${zoneNumber}`).prop('disabled', false);
$(`#gradcamBtn${zoneNumber}`).hide().removeClass('pulse-animation');
if (zoneNumber === 1) {
lastAnalysisResult1 = null;
} else {
lastAnalysisResult2 = null;
}
// Verificar si debe deshabilitar guardar
enableSaveButton();
}
function loadTodayStats() {
api.getConsultations(1, 50).then(function(r) {
const history = (r.consultations || []).map(c => ({
timestamp: c.consultationDate,
prediction: { class_index: c.diabeticRetinopathy ? 1 : 0 }
}));
if (history.length > 0) {
const today = moment ? moment().format('YYYY-MM-DD') : new Date().toISOString().split('T')[0];
const todayAnalyses = history.filter(item =>
(item.timestamp || '').substring(0, 10) === today
);
todayStats.analysis = todayAnalyses.length;
todayStats.positive = todayAnalyses.filter(i => i.prediction.class_index === 1).length;
updateStatsDisplay();
}
});
}
function updateTodayStats() {
if (lastAnalysisResult1 || lastAnalysisResult2) {
todayStats.analysis++;
const hasPositive = (lastAnalysisResult1 && lastAnalysisResult1.prediction.class_index === 1) ||
(lastAnalysisResult2 && lastAnalysisResult2.prediction.class_index === 1);
if (hasPositive) {
todayStats.positive++;
}
updateStatsDisplay();
}
}
function updateStatsDisplay() {
$('#todayAnalysis').text(todayStats.analysis);
$('#todayPositive').text(todayStats.positive);
}
function updateLastUpdate() {
const now = moment ? moment().format('HH:mm') : new Date().toLocaleTimeString().slice(0,5);
$('#lastUpdate').text(now);
}
function showAlert(type, message) {
if (!document.getElementById('toast-container')) {
const container = document.createElement('div');
container.id = 'toast-container';
document.body.appendChild(container);
}
const container = document.getElementById('toast-container');
const icons = {
success: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
error: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
warning: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
info: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>'
};
const titles = { success: 'Éxito', error: 'Error', warning: 'Advertencia', info: 'Información' };
const duration = type === 'error' ? 6000 : 4000;
const toast = document.createElement('div');
toast.className = `toast-notify toast-${type}`;
toast.innerHTML = `
<div class="toast-icon">${icons[type] || icons.info}</div>
<div class="toast-body">
<div class="toast-title">${titles[type] || 'Aviso'}</div>
<div class="toast-msg">${message}</div>
</div>
<button class="toast-close" onclick="this.closest('.toast-notify').remove()">&times;</button>
<div class="toast-progress" style="width:100%;transition:width ${duration}ms linear;"></div>
`;
container.appendChild(toast);
requestAnimationFrame(() => requestAnimationFrame(() => {
toast.classList.add('show');
const bar = toast.querySelector('.toast-progress');
if (bar) requestAnimationFrame(() => { bar.style.width = '0%'; });
}));
const timer = setTimeout(() => {
toast.classList.add('hide');
toast.addEventListener('transitionend', () => toast.remove(), { once: true });
}, duration);
toast.querySelector('.toast-close').addEventListener('click', () => clearTimeout(timer));
}
// === FUNCIONES GLOBALES ===
window.downloadGradCAMImage = function(zoneNumber) {
if (!window.currentGradCAMData) {
showAlert('error', 'No hay imagen Grad-CAM para descargar');
return;
}
try {
const eyeName = zoneNumber === 1 ? 'izquierdo' : 'derecho';
const filename = `gradcam_${eyeName}_${window.currentGradCAMData.filename}_${new Date().getTime()}.png`;
// Crear enlace de descarga
const link = document.createElement('a');
link.href = window.currentGradCAMData.gradcam_image;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showAlert('success', `Imagen Grad-CAM descargada: ${filename}`);
} catch (error) {
showAlert('error', 'Error al descargar la imagen');
console.error('Error descarga:', error);
}
};
console.log('Sistema de diagnostico completamente inicializado');
});
</script>
<script>
function confirmLogout() {
executeLogout();
}
function executeLogout() {
api.logout().then(() => { window.location.href = 'auth-login.html'; });
}
</script>
<script>
// Centrar ventana al cargar pagina
(function() {
function centerWindow() {
try {
const screenWidth = window.screen.availWidth;
const screenHeight = window.screen.availHeight;
const x = Math.floor((screenWidth - 1200) / 2);
const y = Math.floor((screenHeight - 800) / 2);
if (window.moveTo) {
window.moveTo(Math.max(0, x), Math.max(0, y));
}
} catch (e) {
console.warn('No se pudo centrar ventana:', e);
}
}
// Centrar al cargar
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', centerWindow);
} else {
centerWindow();
}
window.addEventListener('load', centerWindow);
window.addEventListener('focus', centerWindow);
})();
</script>
<script>
function checkUserRoleAndHideNavigation() {
api.getSession().then(function(response) {
if (response.success && response.user) {
const userRole = response.user.role;
const usersNavItem = document.querySelector('a[href="./users.html"]');
if (usersNavItem) {
const navItemContainer = usersNavItem.closest('li.nav-item');
if (userRole === 'Admin') {
// Usuario es Admin - mostrar elemento
if (navItemContainer) {
navItemContainer.style.display = 'block';
}
console.log('Navegacion de usuarios visible para administrador:', response.user.username);
} else {
// Usuario NO es Admin - ocultar elemento
if (navItemContainer) {
navItemContainer.style.display = 'none';
}
console.log('Navegacion de usuarios oculta para usuario:', response.user.username, '(Rol:', userRole + ')');
}
} else {
console.warn('No se encontró el elemento de navegación de usuarios');
}
} else {
console.warn('No se pudo obtener información del usuario actual');
}
});
}
// Funcion para verificar si el usuario actual esta intentando acceder a usuarios.html
function checkUsersPageAccess() {
// Solo ejecutar en la pagina de usuarios
if (window.location.pathname.includes('users.html') || window.location.href.includes('users.html')) {
api.getSession().then(function(response) {
if (!response.success) { window.location.href = 'auth-login.html'; return; }
if (response.user.role !== 'Admin') { window.location.href = 'index.html'; return;
}
});
}
}
// Ejecutar al cargar la pagina
$(document).ready(function() {
// Pequeno delay para asegurar que Eel este listo
setTimeout(() => {
checkUserRoleAndHideNavigation();
checkUsersPageAccess();
}, 100);
});
// Tambien ejecutar cuando cambie el estado de autenticacion
function updateNavigationForUser() {
checkUserRoleAndHideNavigation();
}
// Funcion para mostrar informacion del usuario en el dropdown (opcional)
function updateUserInfo() {
api.getSession().then(function(response) {
if (response.success && response.user) {
const userDropdown = document.querySelector('#navbarDropdownMenuLink');
if (userDropdown) {
const roleBadge = response.user.role === 'Admin' ?
'<span class="badge badge-primary badge-sm ml-1">Admin</span>' :
'<span class="badge badge-secondary badge-sm ml-1">Doctor</span>';
if (!userDropdown.querySelector('.badge')) userDropdown.innerHTML += roleBadge;
}
}
});
}
// Llamar tambien a updateUserInfo al cargar
$(document).ready(function() {
setTimeout(() => {
updateUserInfo();
}, 200);
});
</script>
<script src="js/gauge.min.js"></script>
<script src="js/jquery.sparkline.min.js"></script>
<script src="js/apps.js"></script>
<script>
function _initFeather() {
if (typeof feather !== 'undefined') feather.replace({ 'stroke-width': 1.8 });
}
document.addEventListener('DOMContentLoaded', _initFeather);
var _fObserver = new MutationObserver(function(mutations) {
if (mutations.some(function(m){ return m.addedNodes.length > 0; })) _initFeather();
});
document.addEventListener('DOMContentLoaded', function() {
_fObserver.observe(document.body, { childList: true, subtree: true });
});
</script>
<script>
(function () {
const overlay = document.createElement('div');
overlay.id = 'theme-overlay';
overlay.style.cssText = [
'position:fixed','inset:0','z-index:99999',
'pointer-events:none','opacity:0',
'transition:opacity 0.18s ease','background:#fff'
].join(';');
document.body.appendChild(overlay);
document.addEventListener('DOMContentLoaded', function () {
const btn = document.getElementById('modeSwitcher');
if (!btn) return;
const newBtn = btn.cloneNode(true);
btn.parentNode.replaceChild(newBtn, btn);
newBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
switchTheme();
});
});
})();
</script>
<script>
(function () {
function applyTheme(theme) {
const lightTheme = document.getElementById('lightTheme');
const darkTheme = document.getElementById('darkTheme');
if (theme === 'dark') {
lightTheme.disabled = true;
darkTheme.disabled = false;
document.documentElement.style.backgroundColor = '#1a1a2e';
} else {
lightTheme.disabled = false;
darkTheme.disabled = true;
document.documentElement.style.backgroundColor = '';
}
document.documentElement.style.visibility = 'visible';
}
applyTheme(localStorage.getItem('theme') || 'light');
window.addEventListener('storage', function(e) {
if (e.key === 'theme') applyTheme(e.newValue);
});
const overlay = document.createElement('div');
overlay.id = 'theme-overlay';
overlay.style.cssText = [
'position:fixed','inset:0','z-index:99999',
'pointer-events:none','opacity:0',
'transition:opacity 0.18s ease','background:#fff'
].join(';');
document.body.appendChild(overlay);
function switchTheme() {
const darkTheme = document.getElementById('darkTheme');
const goingDark = darkTheme.disabled;
overlay.style.background = goingDark ? '#fff' : '#1a1a2e';
overlay.style.opacity = '1';
setTimeout(function () {
const newTheme = goingDark ? 'dark' : 'light';
applyTheme(newTheme);
localStorage.setItem('theme', newTheme);
setTimeout(function () { overlay.style.opacity = '0'; }, 50);
}, 180);
}
document.addEventListener('DOMContentLoaded', function () {
const btn = document.getElementById('modeSwitcher');
if (!btn) return;
const newBtn = btn.cloneNode(true);
btn.parentNode.replaceChild(newBtn, btn);
newBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
switchTheme();
});
});
})();
(function() {
var sidebar = document.getElementById('leftSidebar');
var logo = document.getElementById('sidebarLogo');
var logoMini = document.getElementById('sidebarLogoMini');
if (!sidebar || !logo || !logoMini) return;
function updateLogo() {
var isCollapsed = sidebar.classList.contains('collapsed')
|| document.body.classList.contains('collapsed')
|| sidebar.offsetWidth < 100;
logo.style.display = isCollapsed ? 'none' : '';
logoMini.style.display = isCollapsed ? 'block' : 'none';
}
var observer = new MutationObserver(updateLogo);
observer.observe(sidebar, { attributes: true, attributeFilter: ['class', 'style'] });
observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
updateLogo();
})();
</script>
</body>
</html>