Chatbot-RAG-v4 / static /index.html
NoeMartinezSanchez
modificacion del index para mostrar la navegacion gerarquica
702f36e
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chatbot RAG - Prepa en Línea SEP</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
:root {
--primary-color: #2c3e50;
--secondary-color: #3498db;
--accent-color: #1abc9c;
--light-color: #ecf0f1;
--dark-color: #2c3e50;
--success-color: #27ae60;
--warning-color: #f39c12;
--danger-color: #e74c3c;
--sidebar-width: 280px;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
}
.chat-container {
width: 100%;
max-width: 1400px;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
display: flex;
height: 92vh;
}
/* Panel lateral */
.sidebar {
width: var(--sidebar-width);
background: var(--primary-color);
color: white;
padding: 20px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.logo {
text-align: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.logo h1 {
font-size: 20px;
margin-bottom: 5px;
color: var(--accent-color);
}
.logo p {
font-size: 12px;
opacity: 0.8;
}
.stats {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 12px;
margin-bottom: 15px;
}
.stats h3 {
font-size: 14px;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.stat-item {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
font-size: 12px;
}
.stat-item span:last-child {
font-weight: bold;
color: var(--accent-color);
}
/* Menú jerárquico - PRINCIPAL */
.menu-section {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
margin-bottom: 15px;
}
.menu-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.menu-header h3 {
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.menu-toggle-btn {
background: rgba(255, 255, 255, 0.15);
border: none;
border-radius: 6px;
padding: 6px 10px;
color: white;
cursor: pointer;
font-size: 12px;
transition: all 0.3s;
}
.menu-toggle-btn:hover {
background: rgba(255, 255, 255, 0.25);
}
.menu-container {
flex: 1;
overflow-y: auto;
padding-right: 5px;
}
.menu-container::-webkit-scrollbar {
width: 6px;
}
.menu-container::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
.menu-container::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
.menu-category {
margin-bottom: 6px;
}
.menu-btn {
width: 100%;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 10px 12px;
color: white;
cursor: pointer;
text-align: left;
font-size: 13px;
display: flex;
align-items: center;
gap: 10px;
transition: all 0.2s;
}
.menu-btn:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateX(3px);
}
.menu-btn.active {
background: var(--accent-color);
border-color: var(--accent-color);
}
.menu-btn i.folder-icon {
color: var(--accent-color);
font-size: 14px;
transition: transform 0.2s;
}
.menu-btn.expanded i.folder-icon {
transform: rotate(15deg);
}
.menu-btn .chevron {
margin-left: auto;
font-size: 10px;
opacity: 0.7;
transition: transform 0.2s;
}
.menu-btn.expanded .chevron {
transform: rotate(90deg);
}
.menu-subcategory {
display: none;
margin-left: 15px;
margin-top: 5px;
}
.menu-subcategory.visible {
display: block;
}
.menu-question {
display: none;
margin-left: 10px;
margin-top: 4px;
}
.menu-question.visible {
display: block;
}
.subcategory-btn {
width: 100%;
background: rgba(255, 255, 255, 0.08);
border: none;
border-radius: 6px;
padding: 8px 10px;
color: rgba(255, 255, 255, 0.85);
cursor: pointer;
text-align: left;
font-size: 12px;
margin-bottom: 4px;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s;
}
.subcategory-btn:hover {
background: rgba(255, 255, 255, 0.15);
}
.subcategory-btn i {
color: var(--secondary-color);
font-size: 11px;
}
.subcategory-btn .chevron {
margin-left: auto;
font-size: 9px;
opacity: 0.6;
transition: transform 0.2s;
}
.subcategory-btn.expanded .chevron {
transform: rotate(90deg);
}
.question-btn {
width: 100%;
background: rgba(26, 188, 156, 0.15);
border: 1px solid rgba(26, 188, 156, 0.25);
border-radius: 5px;
padding: 7px 10px;
color: rgba(255, 255, 255, 0.9);
cursor: pointer;
text-align: left;
font-size: 11px;
margin-bottom: 3px;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.question-btn:hover {
background: rgba(26, 188, 156, 0.35);
transform: translateX(3px);
}
.question-btn i {
color: var(--warning-color);
font-size: 10px;
flex-shrink: 0;
}
.question-btn span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.menu-empty {
text-align: center;
padding: 20px;
color: rgba(255, 255, 255, 0.5);
font-size: 13px;
}
/* Breadcrumb de navegación */
.menu-breadcrumb {
display: flex;
align-items: center;
gap: 5px;
margin-bottom: 10px;
font-size: 11px;
color: rgba(255, 255, 255, 0.6);
}
.menu-breadcrumb span {
background: rgba(255, 255, 255, 0.1);
padding: 3px 8px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.menu-breadcrumb span:hover {
background: rgba(255, 255, 255, 0.2);
color: white;
}
.menu-breadcrumb .current {
background: var(--accent-color);
color: white;
cursor: default;
}
.menu-breadcrumb .current:hover {
background: var(--accent-color);
color: white;
}
/* Área principal del chat */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Panel de categorías rápidas - ARRIBA DEL CHAT */
.quick-menu {
background: linear-gradient(135deg, var(--primary-color) 0%, #34495e 100%);
padding: 15px 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.quick-menu h4 {
color: white;
font-size: 12px;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.quick-categories {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.quick-category-btn {
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 20px;
padding: 8px 14px;
color: white;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.quick-category-btn:hover {
background: var(--accent-color);
border-color: var(--accent-color);
transform: translateY(-2px);
}
.quick-category-btn i {
font-size: 11px;
}
/* Dropdown para subcategorías en panel rápido */
.quick-dropdown {
position: relative;
}
.quick-dropdown-content {
display: none;
position: absolute;
top: 100%;
left: 0;
background: white;
min-width: 250px;
max-height: 300px;
overflow-y: auto;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
z-index: 100;
margin-top: 5px;
}
.quick-dropdown-content.show {
display: block;
animation: slideDown 0.2s ease;
}
@keyframes slideDown {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.quick-dropdown-item {
padding: 10px 15px;
color: var(--dark-color);
font-size: 13px;
cursor: pointer;
border-bottom: 1px solid #eee;
transition: all 0.2s;
}
.quick-dropdown-item:last-child {
border-bottom: none;
}
.quick-dropdown-item:hover {
background: var(--light-color);
}
.quick-dropdown-item .count {
float: right;
font-size: 11px;
color: #999;
background: #eee;
padding: 2px 8px;
border-radius: 10px;
}
.chat-header {
background: white;
padding: 15px 25px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-header h2 {
color: var(--primary-color);
font-size: 18px;
display: flex;
align-items: center;
gap: 10px;
}
.status-indicator {
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--success-color);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
/* Área de mensajes */
.chat-messages {
flex: 1;
padding: 20px 25px;
overflow-y: auto;
background: #f9f9f9;
}
.message {
margin-bottom: 15px;
max-width: 80%;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.user-message {
margin-left: auto;
}
.message-content {
padding: 12px 18px;
border-radius: 18px;
position: relative;
line-height: 1.5;
}
.user-message .message-content {
background: var(--secondary-color);
color: white;
border-bottom-right-radius: 5px;
}
.bot-message .message-content {
background: white;
color: var(--dark-color);
border: 1px solid #eee;
border-bottom-left-radius: 5px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.message-time {
font-size: 10px;
opacity: 0.6;
margin-top: 4px;
text-align: right;
}
/* Fuentes RAG */
.sources-container {
margin-top: 12px;
background: #f8f9fa;
border-radius: 8px;
padding: 12px;
border-left: 3px solid var(--accent-color);
}
.sources-title {
display: flex;
align-items: center;
gap: 6px;
font-weight: bold;
margin-bottom: 8px;
color: var(--primary-color);
font-size: 12px;
}
.source-item {
background: white;
border-radius: 6px;
padding: 8px;
margin-bottom: 6px;
font-size: 12px;
border: 1px solid #eee;
}
.source-item pre {
white-space: pre-wrap;
word-wrap: break-word;
margin: 0;
font-family: inherit;
font-size: 11px;
max-height: 80px;
overflow-y: auto;
}
.source-meta {
display: flex;
gap: 10px;
font-size: 10px;
color: #666;
margin-top: 5px;
}
.confidence-badge {
background: var(--success-color);
color: white;
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
margin-left: 8px;
}
/* Área de entrada */
.input-area {
padding: 15px 25px;
background: white;
border-top: 1px solid #eee;
}
.input-container {
display: flex;
gap: 10px;
align-items: flex-end;
}
textarea {
flex: 1;
padding: 12px 15px;
border: 2px solid #eee;
border-radius: 12px;
resize: none;
font-size: 14px;
transition: border-color 0.3s;
height: 50px;
max-height: 100px;
}
textarea:focus {
outline: none;
border-color: var(--secondary-color);
}
.send-button {
background: var(--secondary-color);
color: white;
border: none;
border-radius: 12px;
width: 50px;
height: 50px;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
.send-button:hover {
background: var(--primary-color);
transform: scale(1.05);
}
.send-button:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
.controls {
display: flex;
justify-content: space-between;
margin-top: 8px;
font-size: 12px;
color: #666;
}
.char-count {
opacity: 0.7;
}
/* Indicador de carga */
.typing-indicator {
display: flex;
align-items: center;
gap: 5px;
padding: 10px 15px;
background: white;
border-radius: 18px;
border: 1px solid #eee;
width: fit-content;
margin-bottom: 15px;
}
.typing-dot {
width: 8px;
height: 8px;
background: var(--secondary-color);
border-radius: 50%;
animation: typing 1.4s infinite ease-in-out;
}
.typing-dot:nth-child(1) { animation-delay: -0.32s; }
.typing-dot:nth-child(2) { animation-delay: -0.16s; }
@keyframes typing {
0%, 80%, 100% { transform: scale(0.7); opacity: 0.5; }
40% { transform: scale(1); opacity: 1; }
}
/* Tema */
.theme-badge {
background: var(--accent-color);
color: white;
padding: 3px 10px;
border-radius: 12px;
font-size: 11px;
margin-left: 8px;
}
/* Responsive */
@media (max-width: 900px) {
.chat-container {
flex-direction: column;
height: 95vh;
}
.sidebar {
width: 100%;
max-height: 180px;
flex-direction: row;
flex-wrap: wrap;
padding: 15px;
}
.logo, .stats {
display: none;
}
.menu-section {
width: 100%;
margin-bottom: 0;
}
.menu-header h3 {
font-size: 13px;
}
.menu-container {
display: flex;
flex-wrap: wrap;
gap: 6px;
overflow-x: auto;
overflow-y: hidden;
}
.menu-category {
margin-bottom: 0;
}
.menu-btn {
width: auto;
white-space: nowrap;
padding: 8px 12px;
font-size: 12px;
}
.menu-subcategory, .menu-question {
position: absolute;
background: white;
border-radius: 8px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
padding: 10px;
z-index: 50;
min-width: 200px;
}
.quick-menu {
padding: 10px 15px;
}
.quick-category-btn {
padding: 6px 10px;
font-size: 11px;
}
.chat-header h2 {
font-size: 16px;
}
.message {
max-width: 90%;
}
}
@media (max-width: 600px) {
body {
padding: 5px;
}
.chat-container {
border-radius: 12px;
}
.chat-messages {
padding: 15px;
}
.input-area {
padding: 12px 15px;
}
.message-content {
padding: 10px 14px;
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="chat-container">
<!-- Panel lateral -->
<div class="sidebar">
<div class="logo">
<h1><i class="fas fa-robot"></i> Chatbot</h1>
<p>Prepa en Línea SEP</p>
</div>
<div class="stats">
<h3><i class="fas fa-chart-bar"></i> Estadísticas</h3>
<div class="stat-item">
<span>Documentos:</span>
<span id="doc-count">0</span>
</div>
<div class="stat-item">
<span>Respuestas RAG:</span>
<span id="rag-count">0</span>
</div>
<div class="stat-item">
<span>Confianza:</span>
<span id="avg-confidence">0%</span>
</div>
</div>
<!-- Menú jerárquico PRINCIPAL -->
<div class="menu-section">
<div class="menu-header">
<h3><i class="fas fa-folder-tree"></i> Temas</h3>
<button class="menu-toggle-btn" onclick="collapseAllMenu()" title="Contraer todo">
<i class="fas fa-compress-alt"></i>
</button>
</div>
<div id="menuBreadcrumb" class="menu-breadcrumb"></div>
<div id="menuContainer" class="menu-container">
<div class="menu-empty">Cargando menú...</div>
</div>
</div>
</div>
<!-- Área principal -->
<div class="main-content">
<!-- Panel de categorías rápidas -->
<div class="quick-menu">
<h4><i class="fas fa-bolt"></i> Acceso rápido</h4>
<div id="quickCategories" class="quick-categories">
<!-- Se llena dinámicamente -->
</div>
</div>
<!-- Encabezado -->
<div class="chat-header">
<h2>
<i class="fas fa-comments"></i> Asistente Virtual
<span class="theme-badge">Prepa en Línea</span>
</h2>
<div class="status-indicator">
<div class="status-dot"></div>
<span>Sistema activo</span>
</div>
</div>
<!-- Mensajes -->
<div class="chat-messages" id="chatMessages">
<!-- Mensaje de bienvenida -->
<div class="message bot-message">
<div class="message-content">
<strong><i class="fas fa-robot"></i> Asistente:</strong><br>
¡Hola! Soy tu asistente de Prepa en Línea SEP.
Puedes explorar los temas en el panel izquierdo o escribir tu pregunta directamente.
¿En qué puedo ayudarte?
</div>
<div class="message-time" id="welcome-time"></div>
</div>
</div>
<!-- Área de entrada -->
<div class="input-area">
<div class="input-container">
<textarea
id="messageInput"
placeholder="Escribe tu pregunta aquí..."
onkeydown="handleKeyDown(event)"
rows="1"
></textarea>
<button class="send-button" id="sendButton" onclick="sendMessage()">
<i class="fas fa-paper-plane"></i>
</button>
</div>
<div class="controls">
<div class="char-count">
<span id="charCount">0</span> caracteres
</div>
<div>
<input type="checkbox" id="showSources" checked>
<label for="showSources">Mostrar fuentes</label>
</div>
</div>
</div>
</div>
</div>
<script>
// Configuración
const API_URL = window.location.origin;
let messageCount = 0;
let ragResponseCount = 0;
let totalConfidence = 0;
// Estado del menú jerárquico
let menuData = {};
let currentCategory = null;
let currentSubcategory = null;
let expandedCategories = new Set();
let expandedSubcategories = new Set();
// Inicialización
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('welcome-time').textContent = getCurrentTime();
document.getElementById('messageInput').addEventListener('input', updateCharCount);
updateCharCount();
loadStats();
loadMenu();
document.getElementById('messageInput').focus();
// Cerrar dropdowns al hacer click fuera
document.addEventListener('click', function(e) {
if (!e.target.closest('.quick-dropdown')) {
document.querySelectorAll('.quick-dropdown-content').forEach(el => el.classList.remove('show'));
}
});
});
function getCurrentTime() {
const now = new Date();
return now.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit', hour12: true });
}
function updateCharCount() {
const input = document.getElementById('messageInput');
const count = input.value.length;
document.getElementById('charCount').textContent = count;
document.getElementById('sendButton').disabled = count === 0;
}
function handleKeyDown(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
if (!document.getElementById('sendButton').disabled) {
sendMessage();
}
}
setTimeout(() => {
const textarea = event.target;
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 100) + 'px';
}, 0);
}
// Enviar mensaje
async function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value.trim();
if (!message) return;
addMessage(message, 'user');
input.value = '';
input.style.height = '50px';
updateCharCount();
showTypingIndicator();
try {
const response = await fetch(`${API_URL}/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: message,
conversation_id: "web_interface",
user_id: "web_user"
})
});
if (!response.ok) throw new Error(`Error HTTP: ${response.status}`);
const data = await response.json();
removeTypingIndicator();
addBotMessage(data);
updateStats(data);
} catch (error) {
console.error('Error:', error);
removeTypingIndicator();
addErrorMessage('Error al conectar con el servidor. Asegúrate de que la API esté corriendo.');
}
}
function addMessage(text, sender) {
const chatMessages = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}-message`;
messageDiv.innerHTML = `
<div class="message-content">
<strong><i class="fas fa-${sender === 'user' ? 'user' : 'robot'}"></i> ${sender === 'user' ? 'Tú' : 'Asistente'}:</strong><br>
${escapeHtml(text)}
</div>
<div class="message-time">${getCurrentTime()}</div>
`;
chatMessages.appendChild(messageDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
messageCount++;
}
function addBotMessage(data) {
const chatMessages = document.getElementById('chatMessages');
const showSources = document.getElementById('showSources').checked;
const icon = data.is_rag_response ? 'fas fa-database' : 'fas fa-comment';
const badge = data.is_rag_response
? `<span class="confidence-badge">RAG ${(data.confidence * 100).toFixed(1)}%</span>`
: '';
let html = `
<div class="message-content">
<strong><i class="${icon}"></i> Asistente:</strong> ${badge}<br>
${escapeHtml(data.response)}
`;
if (data.is_rag_response && data.sources && data.sources.length > 0 && showSources) {
html += `
<div class="sources-container">
<div class="sources-title"><i class="fas fa-file-alt"></i> Fuentes (${data.sources.length})</div>
`;
data.sources.forEach(source => {
html += `
<div class="source-item">
<pre>${escapeHtml(source.content)}</pre>
<div class="source-meta">
<span>${source.metadata.source || 'N/A'}</span>
<span>Fila: ${source.metadata.row_index || 'N/A'}</span>
</div>
</div>
`;
});
html += '</div>';
}
html += `</div><div class="message-time">${getCurrentTime()}</div>`;
const messageDiv = document.createElement('div');
messageDiv.className = 'message bot-message';
messageDiv.innerHTML = html;
chatMessages.appendChild(messageDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function addErrorMessage(text) {
const chatMessages = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = 'message bot-message';
messageDiv.innerHTML = `
<div class="message-content" style="border-left:3px solid var(--danger-color);">
<strong><i class="fas fa-exclamation-triangle"></i> Error:</strong><br>
${escapeHtml(text)}
</div>
<div class="message-time">${getCurrentTime()}</div>
`;
chatMessages.appendChild(messageDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function showTypingIndicator() {
const chatMessages = document.getElementById('chatMessages');
const typingDiv = document.createElement('div');
typingDiv.className = 'typing-indicator';
typingDiv.id = 'typingIndicator';
typingDiv.innerHTML = `
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<span style="margin-left:8px;font-size:12px;">Escribiendo...</span>
`;
chatMessages.appendChild(typingDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function removeTypingIndicator() {
const el = document.getElementById('typingIndicator');
if (el) el.remove();
}
function updateStats(data) {
if (data.is_rag_response) {
ragResponseCount++;
totalConfidence += data.confidence;
document.getElementById('rag-count').textContent = ragResponseCount;
const avg = ragResponseCount > 0 ? (totalConfidence / ragResponseCount * 100).toFixed(1) : 0;
document.getElementById('avg-confidence').textContent = `${avg}%`;
}
}
async function loadStats() {
try {
const response = await fetch(`${API_URL}/stats`);
if (response.ok) {
const data = await response.json();
if (data.vector_store && data.vector_store.total_documents) {
document.getElementById('doc-count').textContent = data.vector_store.total_documents;
}
}
} catch (error) {
console.log('No se pudieron cargar estadísticas');
}
}
// === MENÚ JERÁRQUICO ===
async function loadMenu() {
try {
const response = await fetch(`${API_URL}/menu`);
if (response.ok) {
const data = await response.json();
menuData = data.menu || {};
renderMenu();
renderQuickMenu();
} else {
document.getElementById('menuContainer').innerHTML = '<div class="menu-empty">Menú no disponible</div>';
}
} catch (error) {
console.error('Error cargando menú:', error);
document.getElementById('menuContainer').innerHTML = '<div class="menu-empty">Error al cargar menú</div>';
}
}
function renderMenu() {
const container = document.getElementById('menuContainer');
if (Object.keys(menuData).length === 0) {
container.innerHTML = '<div class="menu-empty">No hay temas disponibles</div>';
return;
}
let html = '';
Object.keys(menuData).forEach(category => {
const isExpanded = expandedCategories.has(category);
html += `
<div class="menu-category">
<button class="menu-btn ${isExpanded ? 'expanded' : ''}" onclick="toggleCategory('${escapeHtml(category)}')">
<i class="fas fa-folder folder-icon"></i>
${escapeHtml(category)}
<i class="fas fa-chevron-right chevron"></i>
</button>
<div class="menu-subcategory ${isExpanded ? 'visible' : ''}" id="subcat-${escapeHtml(category)}">
${renderSubcategories(category)}
</div>
</div>
`;
});
container.innerHTML = html;
updateBreadcrumb();
}
function renderSubcategories(category) {
const subcategories = menuData[category];
let html = '';
Object.keys(subcategories).forEach(subcat => {
const isExpanded = expandedSubcategories.has(`${category}-${subcat}`);
html += `
<button class="subcategory-btn ${isExpanded ? 'expanded' : ''}" onclick="toggleSubcategory('${escapeHtml(category)}', '${escapeHtml(subcat)}')">
<i class="fas fa-folder-open"></i>
${escapeHtml(subcat)}
<span style="margin-left:auto;font-size:10px;opacity:0.6;">${subcategories[subcat].length}</span>
<i class="fas fa-chevron-right chevron"></i>
</button>
<div class="menu-question ${isExpanded ? 'visible' : ''}" id="ques-${escapeHtml(category)}-${escapeHtml(subcat)}">
${renderQuestions(category, subcat)}
</div>
`;
});
return html;
}
function renderQuestions(category, subcategory) {
const questions = menuData[category][subcategory];
let html = '';
questions.forEach((q, idx) => {
const shortQ = q.question.length > 35 ? q.question.substring(0, 35) + '...' : q.question;
html += `
<button class="question-btn" onclick="showAnswer('${escapeHtml(category)}', '${escapeHtml(subcategory)}', ${idx})" title="${escapeHtml(q.question)}">
<i class="fas fa-question-circle"></i>
<span>${escapeHtml(shortQ)}</span>
</button>
`;
});
return html;
}
function toggleCategory(category) {
if (expandedCategories.has(category)) {
expandedCategories.delete(category);
} else {
expandedCategories.add(category);
// Cerrar subcategorías de otras categorías
expandedSubcategories.forEach(key => {
if (key.startsWith(category + '-')) return;
expandedSubcategories.delete(key);
});
}
currentCategory = category;
currentSubcategory = null;
renderMenu();
}
function toggleSubcategory(category, subcategory) {
const key = `${category}-${subcategory}`;
if (expandedSubcategories.has(key)) {
expandedSubcategories.delete(key);
} else {
expandedSubcategories.add(key);
}
currentCategory = category;
currentSubcategory = subcategory;
renderMenu();
}
function updateBreadcrumb() {
const breadcrumb = document.getElementById('menuBreadcrumb');
if (!currentCategory) {
breadcrumb.innerHTML = '';
return;
}
let html = `<span onclick="clearMenuFilter()">Inicio</span>`;
html += ` <i class="fas fa-chevron-right" style="font-size:8px;"></i> `;
html += `<span class="current">${escapeHtml(currentCategory)}</span>`;
if (currentSubcategory) {
html += ` <i class="fas fa-chevron-right" style="font-size:8px;"></i> `;
html += `<span class="current">${escapeHtml(currentSubcategory)}</span>`;
}
breadcrumb.innerHTML = html;
}
function clearMenuFilter() {
currentCategory = null;
currentSubcategory = null;
renderMenu();
}
function collapseAllMenu() {
expandedCategories.clear();
expandedSubcategories.clear();
currentCategory = null;
currentSubcategory = null;
renderMenu();
}
function showAnswer(category, subcategory, questionIdx) {
const question = menuData[category][subcategory][questionIdx];
const chatMessages = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = 'message bot-message';
messageDiv.innerHTML = `
<div class="message-content">
<strong><i class="fas fa-folder-tree"></i> Asistente:</strong><br>
<span style="font-size:11px;color:var(--accent-color);">📁 ${escapeHtml(category)} > ${escapeHtml(subcategory)}</span><br><br>
<strong>Pregunta:</strong> ${escapeHtml(question.question)}<br><br>
<strong>Respuesta:</strong><br>${escapeHtml(question.answer)}
</div>
<div class="message-time">${getCurrentTime()}</div>
`;
chatMessages.appendChild(messageDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// === MENÚ RÁPIDO ===
function renderQuickMenu() {
const container = document.getElementById('quickCategories');
const categories = Object.keys(menuData);
if (categories.length === 0) {
container.innerHTML = '<span style="color:rgba(255,255,255,0.5);font-size:12px;">No hay temas disponibles</span>';
return;
}
let html = '';
categories.slice(0, 6).forEach(category => {
const subcats = Object.keys(menuData[category]);
if (subcats.length === 1) {
// Sin subcategorías, mostrar preguntas directamente
html += `
<button class="quick-category-btn" onclick="showQuickQuestions('${escapeHtml(category)}', '${escapeHtml(subcats[0])}')">
<i class="fas fa-folder"></i> ${escapeHtml(category)}
</button>
`;
} else {
// Con subcategorías, crear dropdown
html += `
<div class="quick-dropdown">
<button class="quick-category-btn" onclick="toggleQuickDropdown('${escapeHtml(category)}', event)">
<i class="fas fa-folder"></i> ${escapeHtml(category)}
<i class="fas fa-caret-down" style="font-size:10px;"></i>
</button>
<div class="quick-dropdown-content" id="quick-drop-${escapeHtml(category)}">
${subcats.map(subcat => `
<div class="quick-dropdown-item" onclick="showQuickQuestions('${escapeHtml(category)}', '${escapeHtml(subcat)}')">
${escapeHtml(subcat)}
<span class="count">${menuData[category][subcat].length}</span>
</div>
`).join('')}
</div>
</div>
`;
}
});
container.innerHTML = html;
}
function toggleQuickDropdown(category, event) {
event.stopPropagation();
const dropdown = document.getElementById(`quick-drop-${category}`);
const allDropdowns = document.querySelectorAll('.quick-dropdown-content');
allDropdowns.forEach(d => {
if (d !== dropdown) d.classList.remove('show');
});
dropdown.classList.toggle('show');
}
function showQuickQuestions(category, subcategory) {
// Cerrar dropdown
document.querySelectorAll('.quick-dropdown-content').forEach(d => d.classList.remove('show'));
// Expadir en el menú lateral
expandedCategories.add(category);
expandedSubcategories.add(`${category}-${subcategory}`);
currentCategory = category;
currentSubcategory = subcategory;
renderMenu();
// Scroll al menú
document.getElementById('menuContainer').scrollTop = 0;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
</body>
</html>