/* ════════════════════════════════
   INFINITY — ADMIN.JS
   Sistema escalable de permisos por pestañas
   ════════════════════════════════ */

// ════════════════════════════════
// TABS — fuente de verdad de permisos
// Para agregar una pestaña nueva: agrégala aquí y al HTML con data-view
// ════════════════════════════════
const TABS = [
  { id: 'productos',  label: 'Productos',  icon: '📦' },
  { id: 'agotados',   label: 'Agotados',   icon: '🚫' },
  { id: 'descargas',  label: 'Descargas',  icon: '⬇️' },
  { id: 'categorias', label: 'Categorías', icon: '🗂️' },
  { id: 'usuarios',   label: 'Usuarios',   icon: '👥' }
];

const ROLES = {
  admin:    'Administrador',
  editor:   'Editor',
  vendedor: 'Vendedor'
};

// Iconos disponibles para categorías
const CAT_ICONS = [
  '💄','👜','☕','🏠','👓','🧴','🕯️','🖼️',
  '💍','🧣','👒','🪞','🎁','🪴','🍵','📿',
  '🧸','🛍️','👗','📦','🎀','🕊️','✨','🌸'
];

let currentUser       = null;  // { usuario, rol, permisos: [] }
let editingUser       = null;  // nombre del usuario que se está editando
let deletingUser      = null;  // nombre del usuario a eliminar
let editingCategoria  = null;  // id de la categoría que se está editando
let deletingCategoria = null;  // id de la categoría que se va a eliminar
let currentView       = 'dashboard';
let cachedProductos   = [];    // cache de productos para contar items por categoría

// ════════════════════════════════
// INIT
// ════════════════════════════════
document.addEventListener('DOMContentLoaded', async () => {
  setupLoginForm();
  setupLogout();
  setupNavigation();
  setupSubtabs();
  setupModals();
  setupPermisosToggles();
  setupCategoriasModals();
  setupProductosUpload();
  setupProductosSearch();
  setupExcelUpload();

  await checkSession();
});

// ════════════════════════════════
// SESIÓN
// ════════════════════════════════
async function checkSession() {
  try {
    const res = await fetch('/api/auth/me', { credentials: 'include' });
    if (res.ok) {
      const user = await res.json();
      currentUser = user;
      mostrarDashboard(user);
    } else {
      mostrarLogin();
    }
  } catch (err) {
    console.error('Error verificando sesión:', err);
    mostrarLogin();
  }
}

// ════════════════════════════════
// VISTAS
// ════════════════════════════════
function mostrarLogin() {
  currentUser = null;
  document.body.classList.remove('is-admin');
  document.getElementById('loginView').style.display = 'flex';
  document.getElementById('adminApp').style.display  = 'none';
}

function mostrarDashboard(user) {
  currentUser = user;
  document.getElementById('loginView').style.display = 'none';
  document.getElementById('adminApp').style.display  = 'block';

  // Marcar el body según el rol
  document.body.classList.toggle('is-admin', user.rol === 'admin');

  // Saludo con el nombre del usuario
  document.getElementById('adminName').textContent = user.usuario;

  // Aplicar permisos (visibilidad de nav, subsección admin-only, etc.)
  aplicarPermisos();

  // Determinar vista inicial
  const esAdmin = user.rol === 'admin';
  const perms   = getPermisosEfectivos();
  let viewInicial;
  if (esAdmin || perms.includes('dashboard')) {
    viewInicial = 'dashboard';
  } else {
    // Primera vista visible según permisos
    viewInicial = perms[0] || 'descargas';
  }
  cambiarVista(viewInicial);

  cargarEstadisticas();
}

function cambiarVista(view) {
  // Check defensivo: si el usuario no tiene permiso para esta vista, redirige a la primera vista visible
  if (currentUser) {
    const esAdmin = currentUser.rol === 'admin';
    const perms   = getPermisosEfectivos();
    if (!esAdmin && !perms.includes(view)) {
      view = perms[0] || 'descargas';
    }
  }

  currentView = view;

  // Ocultar todas las vistas
  document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));

  // Mostrar la vista solicitada (si existe)
  const target = document.querySelector(`.view[data-view="${view}"]`);
  if (target) {
    target.classList.add('active');
  } else {
    document.querySelector('.view[data-view="dashboard"]').classList.add('active');
    view = 'dashboard';
  }

  // Actualizar nav items (excluir el de logout que no tiene data-view)
  document.querySelectorAll('.nav-item[data-view]').forEach(item => {
    item.classList.toggle('active', item.dataset.view === view);
  });

  // Cargar datos específicos de la vista
  if (view === 'usuarios') {
    const esAdmin = currentUser.rol === 'admin';
    const puedeGestionar = esAdmin || (currentUser.permisos || []).includes('usuarios');
    if (puedeGestionar) {
      cargarUsuarios();
    }
  }

  if (view === 'categorias') {
    cargarCategorias();
  }

  if (view === 'productos') {
    cargarCategoriasEnUpload();
    cargarProductosDisponibles();
  }

  if (view === 'agotados') {
    cargarAgotados();
  }

  if (view === 'descargas') {
    cargarDescargas();
  }
}

async function cargarEstadisticas() {
  try {
    const [resCats, resProds] = await Promise.all([
      fetch('/api/categorias'),
      fetch('/api/productos')
    ]);

    const categorias = await resCats.json();
    const productos  = await resProds.json();
    cachedProductos = productos;

    document.getElementById('statProductos').textContent  = productos.length;
    document.getElementById('statCategorias').textContent = categorias.length;
  } catch (err) {
    console.error('Error cargando estadísticas:', err);
  }
}

// ════════════════════════════════
// PERMISOS — visibilidad según rol + permisos[]
// Vendedor: SIEMPRE ve 'descargas' (implícito) + lo que le hayan dado
// ════════════════════════════════
function getPermisosEfectivos() {
  if (!currentUser) return [];
  const permisos = currentUser.permisos || [];
  if (currentUser.rol === 'vendedor' && !permisos.includes('descargas')) {
    return ['descargas', ...permisos];
  }
  return permisos;
}

function aplicarPermisos() {
  if (!currentUser) return;

  const esAdmin  = currentUser.rol === 'admin';
  const perms    = getPermisosEfectivos();
  const puedeGestionarUsuarios = esAdmin || perms.includes('usuarios');

  // Mostrar/ocultar cada nav-item según permisos
  document.querySelectorAll('.nav-item[data-view]').forEach(item => {
    const view = item.dataset.view;
    const visible = esAdmin || perms.includes(view);
    item.style.display = visible ? '' : 'none';
  });

  // Subsección "Mi contraseña" — SOLO admin
  const miPassSubsection = document.getElementById('miPasswordSubsection');
  if (miPassSubsection) {
    miPassSubsection.style.display = esAdmin ? '' : 'none';
  }

  // Subsección "Gestión de usuarios" — admin O permiso 'usuarios'
  const gestionSubsection = document.getElementById('gestionUsuariosSubsection');
  if (gestionSubsection) {
    gestionSubsection.style.display = puedeGestionarUsuarios ? '' : 'none';
  }
}

// ════════════════════════════════
// NAVEGACIÓN
// ════════════════════════════════
function setupNavigation() {
  document.querySelectorAll('.nav-item[data-view]').forEach(item => {
    item.addEventListener('click', () => {
      cambiarVista(item.dataset.view);
    });
  });
}

// ════════════════════════════════
// SUBTABS (dentro de la view 'descargas')
// ════════════════════════════════
function setupSubtabs() {
  document.querySelectorAll('.subtab').forEach(btn => {
    btn.addEventListener('click', () => {
      const subtab = btn.dataset.subtab;
      // Activar solo este botón
      btn.parentElement.querySelectorAll('.subtab').forEach(b => {
        b.classList.toggle('active', b === btn);
        b.setAttribute('aria-selected', b === btn ? 'true' : 'false');
      });
      // Mostrar solo el panel correspondiente
      document.querySelectorAll('.subtab-panel').forEach(p => {
        p.classList.toggle('active', p.dataset.subtabPanel === subtab);
      });
    });
  });
}

// ════════════════════════════════
// DESCARGAS (vista + subtabs pdf / fotos)
// ════════════════════════════════
function cargarDescargas() {
  initGestores();
  pdfGestor.setup();
  fotosGestor.setup();
}

// ════════════════════════════════
// PERMISOS EN MODALES — render y lectura
// ════════════════════════════════
function renderPermisos(containerId, checkedPerms = []) {
  const container = document.getElementById(containerId);
  if (!container) return;
  container.innerHTML = TABS.map(t => `
    <label class="permission-check">
      <input type="checkbox" value="${t.id}" ${checkedPerms.includes(t.id) ? 'checked' : ''}>
      <span class="permission-icon">${t.icon}</span>
      <span class="permission-label">${t.label}</span>
    </label>
  `).join('');
}

function collectPermisos(containerId) {
  const container = document.getElementById(containerId);
  if (!container) return [];
  return Array.from(container.querySelectorAll('input[type="checkbox"]:checked'))
    .map(cb => cb.value);
}

// Mostrar/ocultar la sección de permisos según el rol elegido en el <select>
function setupPermisosToggles() {
  const pairs = [
    { select: 'nuevoRol',  group: 'permisosGroupCrear',  note: 'permisosNoteCrear',  container: 'permisosCrear' },
    { select: 'editarRol', group: 'permisosGroupEditar', note: 'permisosNoteEditar', container: 'permisosEditar' }
  ];

  pairs.forEach(p => {
    const sel = document.getElementById(p.select);
    if (!sel) return;
    const update = () => {
      const isAdmin = sel.value === 'admin';
      document.getElementById(p.group).style.display = isAdmin ? 'none' : '';
      document.getElementById(p.note).style.display  = isAdmin ? 'flex' : 'none';
    };
    sel.addEventListener('change', update);
  });
}

// ════════════════════════════════
// LOGIN
// ════════════════════════════════
function setupLoginForm() {
  const form    = document.getElementById('loginForm');
  const errorEl = document.getElementById('formError');

  form.addEventListener('submit', async e => {
    e.preventDefault();
    errorEl.classList.remove('show');
    errorEl.textContent = '';

    const usuario    = document.getElementById('usuario').value.trim();
    const contraseña = document.getElementById('contraseña').value;

    if (!usuario || !contraseña) return;

    const btn = form.querySelector('button[type="submit"]');
    btn.disabled    = true;
    btn.textContent = 'Verificando…';

    try {
      const res = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ usuario, contraseña })
      });

      const data = await res.json();

      if (!res.ok) {
        mostrarError(data.error || 'Error al iniciar sesión');
        return;
      }

      // Re-fetch /me para obtener permisos actualizados
      const meRes = await fetch('/api/auth/me', { credentials: 'include' });
      const me    = await meRes.json();
      mostrarDashboard({ usuario: me.usuario, rol: me.rol, permisos: me.permisos || [] });
    } catch (err) {
      console.error(err);
      mostrarError('Error de conexión. Intenta de nuevo.');
    } finally {
      btn.disabled    = false;
      btn.textContent = 'Ingresar';
    }
  });
}

function mostrarError(msg) {
  const el = document.getElementById('formError');
  el.textContent = msg;
  el.classList.add('show');
}

// ════════════════════════════════
// LOGOUT
// ════════════════════════════════
function setupLogout() {
  document.getElementById('btnLogout').addEventListener('click', async () => {
    try {
      await fetch('/api/auth/logout', {
        method: 'POST',
        credentials: 'include'
      });
    } catch (err) {
      console.error('Error en logout:', err);
    }
    document.getElementById('usuario').value    = '';
    document.getElementById('contraseña').value = '';
    mostrarLogin();
  });
}

// ════════════════════════════════
// USUARIOS (admin)
// ════════════════════════════════
async function cargarUsuarios() {
  const tbody = document.getElementById('usersListBody');
  tbody.innerHTML = '<tr><td colspan="3" style="text-align:center;color:var(--ink-mute);">Cargando…</td></tr>';

  try {
    const res = await fetch('/api/usuarios', { credentials: 'include' });
    if (!res.ok) {
      if (res.status === 401 || res.status === 403) {
        mostrarLogin();
        return;
      }
      throw new Error('Error al cargar usuarios');
    }
    const usuarios = await res.json();
    renderUsuarios(usuarios);
  } catch (err) {
    console.error(err);
    tbody.innerHTML = '<tr><td colspan="3" style="text-align:center;color:var(--danger);">Error al cargar usuarios</td></tr>';
  }
}

function renderUsuarios(usuarios) {
  const tbody  = document.getElementById('usersListBody');
  const esAdmin = currentUser.rol === 'admin';
  const puedeGestionar = esAdmin || (currentUser.permisos || []).includes('usuarios');

  // Ocultar/mostrar la columna "Acciones" (header + celdas)
  document.querySelectorAll('.users-table .users-actions-col, .users-table .users-actions')
    .forEach(el => el.style.display = puedeGestionar ? '' : 'none');

  if (usuarios.length === 0) {
    tbody.innerHTML = `<tr><td colspan="${puedeGestionar ? 3 : 2}" style="text-align:center;color:var(--ink-mute);padding:30px;">No hay usuarios aún</td></tr>`;
    return;
  }

  tbody.innerHTML = usuarios.map(u => {
    const isYou = u.usuario === currentUser.usuario;
    return `
      <tr>
        <td>
          <strong>${escapeHtml(u.usuario)}</strong>
          ${isYou ? '<span class="you-badge">tú</span>' : ''}
        </td>
        <td>
          <span class="rol-badge rol-${u.rol}">${ROLES[u.rol] || u.rol}</span>
        </td>
        ${puedeGestionar ? `
          <td class="users-actions">
            <button class="btn-icon" onclick="abrirEditarUsuario('${escapeAttr(u.usuario)}')" title="Editar" type="button">✏️</button>
            ${isYou
              ? '<span class="btn-icon-spacer"></span>'
              : `<button class="btn-icon btn-icon-danger" onclick="abrirEliminarUsuario('${escapeAttr(u.usuario)}')" title="Eliminar" type="button">🗑️</button>`}
          </td>
        ` : ''}
      </tr>
    `;
  }).join('');
}

function escapeHtml(str) {
  return String(str).replace(/[&<>"']/g, m => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
  }[m]));
}

function escapeAttr(str) {
  return String(str).replace(/'/g, "\\'");
}

async function crearUsuario(usuario, contraseña, rol, permisos) {
  const res = await fetch('/api/usuarios', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'include',
    body: JSON.stringify({ usuario, contraseña, rol, permisos })
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || 'Error al crear usuario');
  return true;
}

async function editarUsuario(targetUsuario, payload) {
  const res = await fetch(`/api/usuarios/${encodeURIComponent(targetUsuario)}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'include',
    body: JSON.stringify(payload)
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || 'Error al actualizar usuario');
  return true;
}

async function eliminarUsuario(targetUsuario) {
  const res = await fetch(`/api/usuarios/${encodeURIComponent(targetUsuario)}`, {
    method: 'DELETE',
    credentials: 'include'
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || 'Error al eliminar usuario');
  return true;
}

async function cambiarMiPassword(actual, nueva) {
  const res = await fetch('/api/auth/password', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'include',
    body: JSON.stringify({ actual, nueva })
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || 'Error al cambiar contraseña');
  return true;
}

// ════════════════════════════════
// MODALES
// ════════════════════════════════
function setupModals() {
  // Cerrar modales con click en backdrop o botón
  document.querySelectorAll('[data-close-modal]').forEach(el => {
    el.addEventListener('click', () => {
      const id = el.dataset.closeModal;
      cerrarModal(id);
    });
  });

  // Crear usuario
  document.getElementById('btnCrearUsuario').addEventListener('click', () => {
    document.getElementById('crearUsuarioError').classList.remove('show');
    document.getElementById('crearUsuarioError').textContent = '';

    // Render checkboxes vacíos (nuevo editor sin permisos)
    renderPermisos('permisosCrear', []);

    // Asegurar que la sección de permisos sea visible (rol default = editor)
    document.getElementById('permisosGroupCrear').style.display = '';
    document.getElementById('permisosNoteCrear').style.display  = 'none';

    abrirModal('modalCrearUsuario');
  });

  document.getElementById('formCrearUsuario').addEventListener('submit', async e => {
    e.preventDefault();
    const usuario    = document.getElementById('nuevoUsuario').value.trim();
    const contraseña = document.getElementById('nuevoPassword').value;
    const rol        = document.getElementById('nuevoRol').value;
    const permisos   = rol === 'admin' ? [] : collectPermisos('permisosCrear');
    const errEl      = document.getElementById('crearUsuarioError');

    const btn = e.target.querySelector('button[type="submit"]');
    btn.disabled    = true;
    btn.textContent = 'Creando…';

    try {
      await crearUsuario(usuario, contraseña, rol, permisos);
      cerrarModal('modalCrearUsuario');
      await cargarUsuarios();
      cargarEstadisticas();
    } catch (err) {
      errEl.textContent = err.message;
      errEl.classList.add('show');
    } finally {
      btn.disabled    = false;
      btn.textContent = 'Crear';
    }
  });

  // Editar usuario
  document.getElementById('formEditarUsuario').addEventListener('submit', async e => {
    e.preventDefault();
    if (!editingUser) return;

    const contraseña = document.getElementById('editarPassword').value;
    const rol        = document.getElementById('editarRol').value;
    const permisos   = rol === 'admin' ? [] : collectPermisos('permisosEditar');
    const errEl      = document.getElementById('editarUsuarioError');

    const payload = { rol, permisos };
    if (contraseña) payload.contraseña = contraseña;

    const btn = e.target.querySelector('button[type="submit"]');
    btn.disabled    = true;
    btn.textContent = 'Guardando…';

    try {
      await editarUsuario(editingUser, payload);
      editingUser = null;
      cerrarModal('modalEditarUsuario');
      await cargarUsuarios();
    } catch (err) {
      errEl.textContent = err.message;
      errEl.classList.add('show');
    } finally {
      btn.disabled    = false;
      btn.textContent = 'Guardar cambios';
    }
  });

  // Confirmar eliminar
  document.getElementById('btnConfirmarEliminar').addEventListener('click', async () => {
    if (!deletingUser) return;

    const errEl = document.getElementById('eliminarUsuarioError');
    const btn   = document.getElementById('btnConfirmarEliminar');
    btn.disabled    = true;
    btn.textContent = 'Eliminando…';

    try {
      await eliminarUsuario(deletingUser);
      deletingUser = null;
      cerrarModal('modalConfirmarEliminar');
      await cargarUsuarios();
      cargarEstadisticas();
    } catch (err) {
      errEl.textContent = err.message;
      errEl.classList.add('show');
    } finally {
      btn.disabled    = false;
      btn.textContent = 'Sí, eliminar';
    }
  });

  // Form inline de cambio de contraseña
  document.getElementById('formMiPassword').addEventListener('submit', async e => {
    e.preventDefault();
    const actual     = document.getElementById('miPassActual').value;
    const nueva      = document.getElementById('miPassNueva').value;
    const confirmar  = document.getElementById('miPassConfirmar').value;
    const errEl      = document.getElementById('miPassError');

    if (nueva !== confirmar) {
      errEl.textContent = 'Las contraseñas nuevas no coinciden';
      errEl.classList.add('show');
      return;
    }

    const btn = e.target.querySelector('button[type="submit"]');
    btn.disabled    = true;
    btn.textContent = 'Guardando…';

    try {
      await cambiarMiPassword(actual, nueva);
      errEl.classList.remove('show');
      e.target.reset();

      btn.textContent = '✓ Guardado';
      btn.style.background = 'linear-gradient(135deg, #10b981, #34d399)';
      setTimeout(() => {
        btn.textContent = 'Guardar cambios';
        btn.style.background = '';
        btn.disabled = false;
      }, 2000);
    } catch (err) {
      errEl.textContent = err.message;
      errEl.classList.add('show');
      btn.disabled    = false;
      btn.textContent = 'Guardar cambios';
    }
  });
}

function abrirEditarUsuario(usuario) {
  editingUser = usuario;
  document.getElementById('editarUsuarioNombre').value = usuario;
  document.getElementById('editarPassword').value      = '';

  // obtener datos actuales del usuario
  fetch('/api/usuarios', { credentials: 'include' })
    .then(r => r.json())
    .then(usuarios => {
      const u = usuarios.find(x => x.usuario === usuario);
      if (!u) return;

      document.getElementById('editarRol').value = u.rol;

      // Si es admin, ocultar checkboxes y mostrar nota
      const group = document.getElementById('permisosGroupEditar');
      const note  = document.getElementById('permisosNoteEditar');
      if (u.rol === 'admin') {
        group.style.display = 'none';
        note.style.display  = 'flex';
      } else {
        group.style.display = '';
        note.style.display  = 'none';
        renderPermisos('permisosEditar', u.permisos || []);
      }
    });

  document.getElementById('editarUsuarioError').classList.remove('show');
  document.getElementById('editarUsuarioError').textContent = '';
  abrirModal('modalEditarUsuario');
}

function abrirEliminarUsuario(usuario) {
  deletingUser = usuario;
  document.getElementById('eliminarUsuarioNombre').textContent = usuario;
  document.getElementById('eliminarUsuarioError').classList.remove('show');
  document.getElementById('eliminarUsuarioError').textContent = '';
  abrirModal('modalConfirmarEliminar');
}

function abrirModal(id) {
  const modal = document.getElementById(id);
  modal.style.display = 'flex';
  document.body.style.overflow = 'hidden';
}

function cerrarModal(id) {
  const modal = document.getElementById(id);
  modal.style.display = 'none';
  document.body.style.overflow = '';

  const form = modal.querySelector('form');
  if (form) form.reset();

  modal.querySelectorAll('.form-error').forEach(el => {
    el.classList.remove('show');
    el.textContent = '';
  });
}

// Escape cierra modales
document.addEventListener('keydown', e => {
  if (e.key === 'Escape') {
    document.querySelectorAll('.modal').forEach(m => {
      if (m.style.display === 'flex') m.style.display = 'none';
    });
    document.body.style.overflow = '';
  }
});

// ════════════════════════════════
// CATEGORÍAS (admin o permiso 'categorias')
// ════════════════════════════════
async function cargarCategorias() {
  const grid = document.getElementById('categoriasGrid');
  grid.innerHTML = '<p style="text-align:center;color:var(--ink-mute);padding:30px;">Cargando…</p>';

  try {
    const [resCats, resProds] = await Promise.all([
      fetch('/api/categorias'),
      fetch('/api/productos')
    ]);

    if (!resCats.ok) {
      if (resCats.status === 401 || resCats.status === 403) {
        mostrarLogin();
        return;
      }
      throw new Error('Error al cargar categorías');
    }

    const categorias = await resCats.json();
    const productos  = resProds.ok ? await resProds.json() : [];
    cachedProductos = productos;

    renderCategorias(categorias, productos);
  } catch (err) {
    console.error(err);
    grid.innerHTML = '<p style="text-align:center;color:var(--danger);padding:30px;">Error al cargar categorías</p>';
  }
}

function renderCategorias(categorias, productos) {
  const grid = document.getElementById('categoriasGrid');

  if (categorias.length === 0) {
    grid.innerHTML = '<div class="categorias-empty">No hay categorías. Crea la primera con el botón "+ Crear categoría".</div>';
    return;
  }

  // Conteo por categoría
  const countByCat = {};
  productos.forEach(p => {
    countByCat[p.cat] = (countByCat[p.cat] || 0) + 1;
  });

  grid.innerHTML = categorias.map(c => {
    const count = countByCat[c.id] || 0;
    const isSistema = c.id === 'sin-categoria';
    const subcats = Array.isArray(c.subcategorias) ? c.subcategorias : [];
    const tieneSubcats = subcats.length > 0;

    return `
      <div class="categoria-card ${isSistema ? 'categoria-sistema' : ''}">
        <div class="categoria-header">
          <div class="categoria-icon">${c.icono || '📦'}</div>
          <div class="categoria-meta">
            <div class="categoria-nombre">${escapeHtml(c.nombre)}</div>
            <div class="categoria-id">${escapeHtml(c.id)}</div>
          </div>
          <div class="categoria-count">${count} item${count === 1 ? '' : 's'}</div>
          ${!isSistema ? `
            <div class="categoria-actions">
              <div class="menu-dropdown">
                <button class="btn-icon-tiny menu-toggle" type="button" aria-label="Más acciones" aria-haspopup="true" aria-expanded="false">⋯</button>
                <div class="menu-panel" hidden>
                  <div class="menu-header" title="${escapeAttr(c.nombre)}">${escapeHtml(c.nombre)}</div>
                  <button type="button" class="menu-item" data-action="add-sub-cat" data-id="${escapeAttr(c.id)}">
                    <span class="menu-icon">＋</span> Agregar subcategoría
                  </button>
                  <button type="button" class="menu-item" data-action="edit-cat" data-id="${escapeAttr(c.id)}">
                    <span class="menu-icon">✏️</span> Editar nombre e icono
                  </button>
                  <button type="button" class="menu-item menu-item-danger" data-action="delete-cat" data-id="${escapeAttr(c.id)}" data-name="${escapeAttr(c.nombre)}">
                    <span class="menu-icon">🗑️</span> Eliminar categoría
                  </button>
                </div>
              </div>
            </div>
          ` : ''}
        </div>
        ${tieneSubcats ? `<div class="subcat-tree">${renderSubcatRows(subcats, [c.id])}</div>` : ''}
      </div>
    `;
  }).join('');
}

function renderIconPicker(selectedIcon) {
  const picker = document.getElementById('iconPicker');
  picker.innerHTML = CAT_ICONS.map(icon => `
    <button type="button" class="icon-option ${icon === selectedIcon ? 'selected' : ''}" data-icon="${icon}">${icon}</button>
  `).join('');

  picker.querySelectorAll('.icon-option').forEach(btn => {
    btn.addEventListener('click', () => {
      picker.querySelectorAll('.icon-option').forEach(b => b.classList.remove('selected'));
      btn.classList.add('selected');
      document.getElementById('catIcono').value = btn.dataset.icon;
    });
  });
}

// Abrir modal para CREAR categoría
function abrirCrearCategoria() {
  editingCategoria = null;
  document.getElementById('modalCategoriaTitulo').textContent = 'Crear categoría';
  document.getElementById('btnGuardarCategoria').textContent = 'Crear';
  document.getElementById('catNombre').value = '';
  document.getElementById('catIdHint').textContent = '';
  document.getElementById('categoriaError').classList.remove('show');
  document.getElementById('categoriaError').textContent = '';
  renderIconPicker('📦');
  abrirModal('modalCategoria');

  // Mostrar hint en vivo con el slug generado
  document.getElementById('catNombre').oninput = e => {
    const slug = slugifyLocal(e.target.value);
    document.getElementById('catIdHint').textContent = slug ? `ID: ${slug}` : '';
  };
}

// Abrir modal para EDITAR categoría
function abrirEditarCategoria(id) {
  fetch('/api/categorias', { credentials: 'include' })
    .then(r => r.json())
    .then(categorias => {
      const c = categorias.find(x => x.id === id);
      if (!c) return;

      editingCategoria = id;
      document.getElementById('modalCategoriaTitulo').textContent = 'Editar categoría';
      document.getElementById('btnGuardarCategoria').textContent = 'Guardar cambios';
      document.getElementById('catNombre').value = c.nombre;
      document.getElementById('catIdHint').textContent = `ID: ${c.id} (no se puede cambiar)`;
      document.getElementById('categoriaError').classList.remove('show');
      document.getElementById('categoriaError').textContent = '';
      renderIconPicker(c.icono || '📦');
      abrirModal('modalCategoria');
    });
}

function abrirEliminarCategoria(id, nombre) {
  deletingCategoria = id;
  const count = cachedProductos.filter(p => p.cat === id).length;

  document.getElementById('eliminarCatNombre').textContent = nombre;
  document.getElementById('eliminarCatCount').textContent = count;
  document.getElementById('eliminarCatItemsMsg').style.display = count > 0 ? 'flex' : 'none';
  document.getElementById('eliminarCatError').classList.remove('show');
  document.getElementById('eliminarCatError').textContent = '';
  abrirModal('modalEliminarCategoria');
}

// Slugify en el frontend (mismo criterio que el backend)
function slugifyLocal(text) {
  return String(text)
    .toLowerCase()
    .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
    .replace(/[^a-z0-9\s-]/g, '')
    .trim()
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-')
    .replace(/^-+|-+$/g, '');
}

async function guardarCategoria(e) {
  e.preventDefault();
  const nombre = document.getElementById('catNombre').value.trim();
  const icono  = document.getElementById('catIcono').value;
  const errEl  = document.getElementById('categoriaError');
  const btn    = document.getElementById('btnGuardarCategoria');

  if (!nombre) return;

  btn.disabled    = true;
  btn.textContent = editingCategoria ? 'Guardando…' : 'Creando…';

  try {
    const url    = editingCategoria
      ? `/api/categorias/${encodeURIComponent(editingCategoria)}`
      : '/api/categorias';
    const method = editingCategoria ? 'PUT' : 'POST';

    const res = await fetch(url, {
      method,
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify({ nombre, icono })
    });

    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Error al guardar');

    editingCategoria = null;
    cerrarModal('modalCategoria');
    await cargarCategorias();
    cargarEstadisticas();
  } catch (err) {
    errEl.textContent = err.message;
    errEl.classList.add('show');
  } finally {
    btn.disabled    = false;
    btn.textContent = editingCategoria ? 'Guardar cambios' : 'Crear';
  }
}

async function confirmarEliminarCategoria() {
  if (!deletingCategoria) return;

  const btn   = document.getElementById('btnConfirmarEliminarCategoria');
  const errEl = document.getElementById('eliminarCatError');
  btn.disabled    = true;
  btn.textContent = 'Eliminando…';

  try {
    const res = await fetch(`/api/categorias/${encodeURIComponent(deletingCategoria)}`, {
      method: 'DELETE',
      credentials: 'include'
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Error al eliminar');

    deletingCategoria = null;
    cerrarModal('modalEliminarCategoria');
    await cargarCategorias();
    cargarEstadisticas();
  } catch (err) {
    errEl.textContent = err.message;
    errEl.classList.add('show');
  } finally {
    btn.disabled    = false;
    btn.textContent = 'Sí, eliminar';
  }
}

// Setup de listeners para categorías (se llama desde setupModals)
function setupCategoriasModals() {
  document.getElementById('btnCrearCategoria').addEventListener('click', abrirCrearCategoria);
  document.getElementById('formCategoria').addEventListener('submit', guardarCategoria);
  document.getElementById('btnConfirmarEliminarCategoria').addEventListener('click', confirmarEliminarCategoria);
  document.getElementById('formSubcategoria').addEventListener('submit', guardarSubcategoria);
  document.getElementById('btnConfirmarEliminarSubcat').addEventListener('click', confirmarEliminarSubcategoria);
  setupKebabMenus();
}

// ════════════════════════════════
// MENÚ KEBAB (drop-down ⋯) — para categoría y subcats
// - Click en .menu-toggle → abre/cierra
// - Click en .menu-item → ejecuta acción, cierra
// - Click fuera / Escape → cierra todos
// - Al abrir, marca .menu-active en el contenedor para que no se oculte al perder hover
// ════════════════════════════════
function setupKebabMenus() {
  document.addEventListener('click', e => {
    // Click en el toggle ⋯
    const toggle = e.target.closest('.menu-toggle');
    if (toggle) {
      e.stopPropagation();
      const dropdown = toggle.parentElement;
      const panel    = dropdown.querySelector('.menu-panel');
      if (!panel) return;
      const wasOpen = !panel.hidden;
      closeAllKebabMenus();
      if (!wasOpen) {
        panel.hidden = false;
        toggle.setAttribute('aria-expanded', 'true');
        // Marcar la card como host para que flote sobre los vecinos
        const card = dropdown.closest('.categoria-card');
        if (card) card.classList.add('menu-host');
      }
      return;
    }

    // Click en un item del menú
    const item = e.target.closest('.menu-item');
    if (item) {
      const action = item.dataset.action;
      closeAllKebabMenus();
      if      (action === 'add-sub-cat')     abrirCrearSubcategoria(item.dataset.id);
      else if (action === 'add-sub-sub-cat') abrirCrearSubcatHijo(item.dataset.cat, item.dataset.sub);
      else if (action === 'edit-subcat')     abrirEditarSubcategoria(item.dataset.cat, item.dataset.sub);
      else if (action === 'delete-subcat')   abrirEliminarSubcategoria(item.dataset.cat, item.dataset.sub, item.dataset.name);
      else if (action === 'edit-cat')        abrirEditarCategoria(item.dataset.id);
      else if (action === 'delete-cat')      abrirEliminarCategoria(item.dataset.id, item.dataset.name);
      return;
    }

    // Click fuera de cualquier dropdown
    if (!e.target.closest('.menu-dropdown')) {
      closeAllKebabMenus();
    }
  });

  // Escape cierra todos
  document.addEventListener('keydown', e => {
    if (e.key === 'Escape') closeAllKebabMenus();
  });
}

function closeAllKebabMenus() {
  document.querySelectorAll('.menu-panel').forEach(p => p.hidden = true);
  document.querySelectorAll('.menu-toggle').forEach(b => b.setAttribute('aria-expanded', 'false'));
  document.querySelectorAll('.categoria-card.menu-host')
    .forEach(c => c.classList.remove('menu-host'));
}

// ════════════════════════════════
// SUBCATEGORÍAS (CRUD sobre el árbol anidado)
// Estado: editingSubcat = { parentId, subId } cuando se está editando
//         deletingSubcat = { parentId, subId, nombre } cuando se va a eliminar
// ════════════════════════════════
let editingSubcat   = null;
let deletingSubcat  = null;

// Render recursivo de subcategorías como filas anidadas dentro de la card de la categoría
function renderSubcatRows(subcats, parentPath) {
  if (!Array.isArray(subcats) || subcats.length === 0) return '';
  // parentPath = [c.id]  → render nivel 2 (subs). El ＋ aparece (pueden tener sub-subs).
  // parentPath = [c.id, s.id] → render nivel 3 (sub-subs). El ＋ NO aparece (límite 3 niveles).
  const puedenTenerHijos = parentPath.length === 1;
  return subcats.map(s => {
    const path = [...parentPath, s.id];
    const tieneSubSub = Array.isArray(s.subcategorias) && s.subcategorias.length > 0;
    return `
      <div class="subcat-row" data-subcat-id="${escapeAttr(s.id)}">
        <div class="subcat-info">
          <span class="subcat-name">${escapeHtml(s.nombre)}</span>
          <span class="subcat-id">${escapeHtml(s.id)}</span>
        </div>
        <div class="subcat-actions">
          <div class="menu-dropdown">
            <button class="btn-icon-tiny menu-toggle" type="button" aria-label="Más acciones" aria-haspopup="true" aria-expanded="false">⋯</button>
            <div class="menu-panel" hidden>
              <div class="menu-header" title="${escapeAttr(s.nombre)}">${escapeHtml(s.nombre)}</div>
              ${puedenTenerHijos ? `<button type="button" class="menu-item" data-action="add-sub-sub-cat" data-cat="${escapeAttr(parentPath[0])}" data-sub="${escapeAttr(s.id)}">
                <span class="menu-icon">＋</span> Agregar sub-subcategoría
              </button>` : ''}
              <button type="button" class="menu-item" data-action="edit-subcat" data-cat="${escapeAttr(parentPath[0])}" data-sub="${escapeAttr(s.id)}">
                <span class="menu-icon">✏️</span> Editar nombre
              </button>
              <button type="button" class="menu-item menu-item-danger" data-action="delete-subcat" data-cat="${escapeAttr(parentPath[0])}" data-sub="${escapeAttr(s.id)}" data-name="${escapeAttr(s.nombre)}">
                <span class="menu-icon">🗑️</span> Eliminar subcategoría
              </button>
            </div>
          </div>
        </div>
      </div>
      ${tieneSubSub ? `<div class="subcat-children">${renderSubcatRows(s.subcategorias, path)}</div>` : ''}
    `;
  }).join('');
}

async function abrirCrearSubcategoria(parentId) {
  editingSubcat = null;
  document.getElementById('modalSubcategoriaTitulo').textContent = 'Crear subcategoría';
  document.getElementById('btnGuardarSubcategoria').textContent = 'Crear';
  document.getElementById('subcatNombre').value = '';
  document.getElementById('subcatIdHint').textContent = '';
  document.getElementById('subcategoriaError').classList.remove('show');
  document.getElementById('subcategoriaError').textContent = '';
  // Mostrar breadcrumb del padre (solo la categoría)
  const categorias = await fetch('/api/categorias', { credentials: 'include' }).then(r => r.json());
  const parent = categorias.find(c => c.id === parentId);
  if (parent) {
    document.getElementById('subcatBreadcrumb').innerHTML =
      `<span class="bc-cat">${parent.icono || '📦'} ${escapeHtml(parent.nombre)}</span><span class="bc-arrow">›</span><span class="bc-new">Nueva subcategoría</span>`;
  }
  abrirModal('modalSubcategoria');

  document.getElementById('subcatNombre').oninput = e => {
    const slug = slugifyLocal(e.target.value);
    document.getElementById('subcatIdHint').textContent = slug ? `ID: ${slug}` : '';
  };

  // Guardar el parentId en el dataset del form para usarlo al enviar
  document.getElementById('formSubcategoria').dataset.parentId = parentId;
  document.getElementById('formSubcategoria').dataset.subId = '';
}

async function abrirCrearSubcatHijo(parentCatId, parentSubId) {
  // Para crear una sub-subcategoría, el "padre" es la subcategoría (no la categoría raíz)
  editingSubcat = null;
  document.getElementById('modalSubcategoriaTitulo').textContent = 'Crear sub-subcategoría';
  document.getElementById('btnGuardarSubcategoria').textContent = 'Crear';
  document.getElementById('subcatNombre').value = '';
  document.getElementById('subcatIdHint').textContent = '';
  document.getElementById('subcategoriaError').classList.remove('show');
  document.getElementById('subcategoriaError').textContent = '';

  const categorias = await fetch('/api/categorias', { credentials: 'include' }).then(r => r.json());
  const path = getCategoryPathCliente(categorias, parentSubId);
  if (path) {
    const crumbs = path.map(n => `<span class="bc-cat">${n.icono || '📦'} ${escapeHtml(n.nombre)}</span><span class="bc-arrow">›</span>`).join('');
    document.getElementById('subcatBreadcrumb').innerHTML = crumbs + '<span class="bc-new">Nueva sub-subcategoría</span>';
  }
  abrirModal('modalSubcategoria');

  document.getElementById('subcatNombre').oninput = e => {
    const slug = slugifyLocal(e.target.value);
    document.getElementById('subcatIdHint').textContent = slug ? `ID: ${slug}` : '';
  };

  document.getElementById('formSubcategoria').dataset.parentId = parentCatId;
  document.getElementById('formSubcategoria').dataset.subId = parentSubId;
}

async function abrirEditarSubcategoria(parentCatId, subId) {
  const categorias = await fetch('/api/categorias', { credentials: 'include' }).then(r => r.json());
  const sub = findSubcatCliente(categorias, subId);
  if (!sub) return;

  editingSubcat = { parentCatId, subId };
  document.getElementById('modalSubcategoriaTitulo').textContent = 'Editar subcategoría';
  document.getElementById('btnGuardarSubcategoria').textContent = 'Guardar cambios';
  document.getElementById('subcatNombre').value = sub.nombre;
  document.getElementById('subcatIdHint').textContent = `ID: ${sub.id} (no se puede cambiar)`;
  document.getElementById('subcategoriaError').classList.remove('show');
  document.getElementById('subcategoriaError').textContent = '';

  const path = getCategoryPathCliente(categorias, subId);
  if (path) {
    const crumbs = path.slice(0, -1).map(n => `<span class="bc-cat">${n.icono || '📦'} ${escapeHtml(n.nombre)}</span><span class="bc-arrow">›</span>`).join('');
    document.getElementById('subcatBreadcrumb').innerHTML = crumbs + `<span class="bc-cat">${escapeHtml(sub.nombre)}</span>`;
  }
  abrirModal('modalSubcategoria');

  document.getElementById('formSubcategoria').dataset.parentId = parentCatId;
  document.getElementById('formSubcategoria').dataset.subId = subId;
}

function abrirEliminarSubcategoria(parentCatId, subId, nombre) {
  deletingSubcat = { parentCatId, subId, nombre };
  document.getElementById('eliminarSubcatNombre').textContent = nombre;
  document.getElementById('eliminarSubcatError').classList.remove('show');
  document.getElementById('eliminarSubcatError').textContent = '';
  abrirModal('modalEliminarSubcategoria');
}

// Helpers cliente (espejo de los del backend)
function getCategoryPathCliente(categorias, id, trail = []) {
  for (const c of categorias) {
    const next = [...trail, c];
    if (c.id === id) return next;
    if (Array.isArray(c.subcategorias) && c.subcategorias.length) {
      const found = getCategoryPathCliente(c.subcategorias, id, next);
      if (found) return found;
    }
  }
  return null;
}
function findSubcatCliente(categorias, id) {
  for (const c of categorias) {
    if (Array.isArray(c.subcategorias)) {
      const found = findInSubcatsCliente(c.subcategorias, id);
      if (found) return found;
    }
  }
  return null;
}
function findInSubcatsCliente(subcats, id) {
  for (const s of subcats) {
    if (s.id === id) return s;
    if (Array.isArray(s.subcategorias) && s.subcategorias.length) {
      const found = findInSubcatsCliente(s.subcategorias, id);
      if (found) return found;
    }
  }
  return null;
}

async function guardarSubcategoria(e) {
  e.preventDefault();
  const nombre = document.getElementById('subcatNombre').value.trim();
  const errEl  = document.getElementById('subcategoriaError');
  const btn    = document.getElementById('btnGuardarSubcategoria');
  const form   = document.getElementById('formSubcategoria');
  const parentId = form.dataset.parentId;
  const subId    = form.dataset.subId;

  if (!nombre) return;

  btn.disabled    = true;
  btn.textContent = editingSubcat ? 'Guardando…' : 'Creando…';

  try {
    let url, method, body;

    if (editingSubcat) {
      // EDITAR: PUT /api/categorias/:parentId/subcategorias/:subId
      url    = `/api/categorias/${encodeURIComponent(parentId)}/subcategorias/${encodeURIComponent(subId)}`;
      method = 'PUT';
      body   = { nombre };
    } else if (subId) {
      // CREAR sub-subcat: POST con parentSubId en el body
      url    = `/api/categorias/${encodeURIComponent(parentId)}/subcategorias`;
      method = 'POST';
      body   = { nombre, parentSubId: subId };
    } else {
      // CREAR subcat nivel 1: POST simple
      url    = `/api/categorias/${encodeURIComponent(parentId)}/subcategorias`;
      method = 'POST';
      body   = { nombre };
    }

    const res = await fetch(url, {
      method,
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify(body)
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Error al guardar');

    editingSubcat = null;
    cerrarModal('modalSubcategoria');
    await cargarCategorias();
    cargarEstadisticas();
  } catch (err) {
    errEl.textContent = err.message;
    errEl.classList.add('show');
  } finally {
    btn.disabled    = false;
    btn.textContent = editingSubcat ? 'Guardar cambios' : 'Crear';
  }
}

async function confirmarEliminarSubcategoria() {
  if (!deletingSubcat) return;

  const btn   = document.getElementById('btnConfirmarEliminarSubcat');
  const errEl = document.getElementById('eliminarSubcatError');
  btn.disabled    = true;
  btn.textContent = 'Eliminando…';

  try {
    const res = await fetch(
      `/api/categorias/${encodeURIComponent(deletingSubcat.parentCatId)}/subcategorias/${encodeURIComponent(deletingSubcat.subId)}`,
      { method: 'DELETE', credentials: 'include' }
    );
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Error al eliminar');

    deletingSubcat = null;
    cerrarModal('modalEliminarSubcategoria');
    await cargarCategorias();
    cargarEstadisticas();
  } catch (err) {
    errEl.textContent = err.message;
    errEl.classList.add('show');
  } finally {
    btn.disabled    = false;
    btn.textContent = 'Sí, eliminar';
  }
}

// ════════════════════════════════
// SUBIDA MASIVA DE PRODUCTOS
// ════════════════════════════════

const MAX_FILES_UPLOAD  = 50;
const MAX_SIZE_UPLOAD   = 20 * 1024 * 1024; // 20MB
const ACCEPTED_TYPES     = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'];
// Referencia = nombre completo antes de la extensión, sin importar complejidad.
const REF_VALID_CLIENTE = /^[a-zA-Z0-9]+(?:[-_a-zA-Z0-9]*[a-zA-Z0-9])?$/;
const SIN_CAT_ID         = 'sin-categoria';

let pendingFiles = []; // [{file, ref, num, isValid, motivo}]

function parseFilenameCliente(name) {
  const base = name.replace(/\.[^.]+$/, '').trim();
  if (!base) return null;
  if (!REF_VALID_CLIENTE.test(base)) return null;
  return { ref: base, num: 1 };
}

async function cargarCategoriasEnUpload() {
  const select = document.getElementById('uploadCategoria');
  if (!select) {
    console.warn('[upload] Select #uploadCategoria no encontrado en el DOM');
    return;
  }
  try {
    const res = await fetch('/api/categorias', { credentials: 'same-origin' });
    if (!res.ok) throw new Error('HTTP ' + res.status);
    const categorias = await res.json();
    if (!Array.isArray(categorias)) throw new Error('Respuesta no es array');

    // Cachear el árbol completo (con subcategorías) en el módulo para la cascada
    uploadCategoriasTree = categorias;

    const seleccionPrevia = select.value;
    select.innerHTML = '<option value="">— Selecciona una categoría —</option>';
    categorias
      .filter(c => c.id !== SIN_CAT_ID)
      .forEach(c => {
        const opt = document.createElement('option');
        opt.value = c.id;
        opt.textContent = `${c.icono || '📦'}  ${c.nombre}`;
        select.appendChild(opt);
      });
    if (seleccionPrevia) select.value = seleccionPrevia;
  } catch (err) {
    console.error('[upload] Error cargando categorías:', err);
    const error = document.getElementById('uploadError');
    if (error) error.textContent = 'No se pudieron cargar las categorías. Recarga la página.';
  }
}

// Árbol completo cacheado para la cascada del upload
let uploadCategoriasTree = [];

// Se ejecuta cuando el usuario cambia la categoría
function onCategoriaChange() {
  const catId   = document.getElementById('uploadCategoria').value;
  const subGrp  = document.getElementById('uploadSubcatGroup');
  const subSel  = document.getElementById('uploadSubcategoria');
  const subSubGrp = document.getElementById('uploadSubSubcatGroup');
  const subSubSel = document.getElementById('uploadSubSubcategoria');

  // Reset
  subSel.innerHTML = '<option value="">— Selecciona una subcategoría —</option>';
  subSubSel.innerHTML = '<option value="">— Selecciona una sub-subcategoría —</option>';
  subGrp.style.display    = 'none';
  subSubGrp.style.display = 'none';

  if (!catId) {
    actualizarBreadcrumbDestino();
    updateSubmitButton();
    return;
  }

  const cat = uploadCategoriasTree.find(c => c.id === catId);
  if (!cat) return;

  if (Array.isArray(cat.subcategorias) && cat.subcategorias.length > 0) {
    subGrp.style.display = '';
    cat.subcategorias.forEach(s => {
      const opt = document.createElement('option');
      opt.value = s.id;
      opt.textContent = `${s.icono || '📂'}  ${s.nombre}`;
      subSel.appendChild(opt);
    });
  }
  actualizarBreadcrumbDestino();
  updateSubmitButton();
}

// Se ejecuta cuando el usuario cambia la subcategoría
function onSubcategoriaChange() {
  const catId  = document.getElementById('uploadCategoria').value;
  const subId  = document.getElementById('uploadSubcategoria').value;
  const subSubGrp = document.getElementById('uploadSubSubcatGroup');
  const subSubSel = document.getElementById('uploadSubSubcategoria');

  subSubSel.innerHTML = '<option value="">— Selecciona una sub-subcategoría —</option>';
  subSubGrp.style.display = 'none';

  if (!catId || !subId) {
    actualizarBreadcrumbDestino();
    updateSubmitButton();
    return;
  }

  const cat = uploadCategoriasTree.find(c => c.id === catId);
  if (!cat || !Array.isArray(cat.subcategorias)) return;

  const sub = cat.subcategorias.find(s => s.id === subId);
  if (!sub) return;

  if (Array.isArray(sub.subcategorias) && sub.subcategorias.length > 0) {
    subSubGrp.style.display = '';
    sub.subcategorias.forEach(ss => {
      const opt = document.createElement('option');
      opt.value = ss.id;
      opt.textContent = `${ss.icono || '📂'}  ${ss.nombre}`;
      subSubSel.appendChild(opt);
    });
  }
  actualizarBreadcrumbDestino();
  updateSubmitButton();
}

// Muestra el breadcrumb del destino actual (cat > sub1 > sub2)
function actualizarBreadcrumbDestino() {
  const cont = document.getElementById('uploadDestino');
  const path = document.getElementById('uploadDestinoPath');
  if (!cont || !path) return;

  const catId   = document.getElementById('uploadCategoria').value;
  const subId   = document.getElementById('uploadSubcategoria').value;
  const subSubId= document.getElementById('uploadSubSubcategoria').value;

  if (!catId) { cont.style.display = 'none'; return; }

  const cat = uploadCategoriasTree.find(c => c.id === catId);
  if (!cat) { cont.style.display = 'none'; return; }

  const parts = [`${cat.icono || '📦'} ${cat.nombre}`];
  if (subId) {
    const sub = (cat.subcategorias || []).find(s => s.id === subId);
    if (sub) {
      parts.push(`${sub.icono || '📂'} ${sub.nombre}`);
      if (subSubId) {
        const subSub = (sub.subcategorias || []).find(s => s.id === subSubId);
        if (subSub) parts.push(`${subSub.icono || '📂'} ${subSub.nombre}`);
      }
    }
  }
  path.textContent = parts.join(' › ');
  cont.style.display = '';
}

// Devuelve el array `sub` que se enviará al backend (la cadena hasta la hoja)
function getSubPathActual() {
  const catId  = document.getElementById('uploadCategoria').value;
  const subId  = document.getElementById('uploadSubcategoria').value;
  const subSubId = document.getElementById('uploadSubSubcategoria').value;

  if (!catId) return [];
  const path = [];
  if (subId)     path.push(subId);
  if (subSubId)  path.push(subSubId);
  return path;
}

function setupProductosUpload() {
  const dropzone    = document.getElementById('dropzone');
  const inputFotos  = document.getElementById('inputFotos');
  const btnLimpiar  = document.getElementById('btnLimpiarPreview');
  const form        = document.getElementById('formUploadProductos');
  const select      = document.getElementById('uploadCategoria');
  const selectSub   = document.getElementById('uploadSubcategoria');
  const selectSubSub= document.getElementById('uploadSubSubcategoria');

  if (!dropzone || !inputFotos) return;

  // Click en dropzone abre el selector
  dropzone.addEventListener('click', () => inputFotos.click());

  // Drag & drop
  ['dragenter', 'dragover'].forEach(ev => {
    dropzone.addEventListener(ev, e => {
      e.preventDefault();
      e.stopPropagation();
      dropzone.classList.add('is-dragover');
    });
  });
  ['dragleave', 'drop'].forEach(ev => {
    dropzone.addEventListener(ev, e => {
      e.preventDefault();
      e.stopPropagation();
      dropzone.classList.remove('is-dragover');
    });
  });
  dropzone.addEventListener('drop', e => {
    const files = Array.from(e.dataTransfer.files || []);
    addFiles(files);
  });

  inputFotos.addEventListener('change', e => {
    addFiles(Array.from(e.target.files || []));
    inputFotos.value = ''; // permite re-seleccionar el mismo archivo
  });

  btnLimpiar.addEventListener('click', () => {
    pendingFiles = [];
    renderPreview();
  });

  // Cascada: categoría → subcategoría → sub-subcategoría
  select.addEventListener('change', onCategoriaChange);
  if (selectSub)    selectSub.addEventListener('change', onSubcategoriaChange);
  if (selectSubSub) selectSubSub.addEventListener('change', updateSubmitButton);

  form.addEventListener('submit', submitUpload);
}

function addFiles(files) {
  const total = pendingFiles.length + files.length;
  if (total > MAX_FILES_UPLOAD) {
    mostrarError(`Máximo ${MAX_FILES_UPLOAD} archivos. Has seleccionado ${files.length} y ya tenías ${pendingFiles.length}.`);
    return;
  }

  for (const f of files) {
    if (!ACCEPTED_TYPES.includes(f.type)) {
      pendingFiles.push({ file: f, ref: null, num: null, isValid: false, motivo: 'Tipo no permitido' });
      continue;
    }
    if (f.size > MAX_SIZE_UPLOAD) {
      pendingFiles.push({ file: f, ref: null, num: null, isValid: false, motivo: 'Supera 20MB' });
      continue;
    }
    const parsed = parseFilenameCliente(f.name);
    if (!parsed) {
      pendingFiles.push({ file: f, ref: null, num: null, isValid: false, motivo: 'Nombre inválido' });
      continue;
    }
    pendingFiles.push({ file: f, ref: parsed.ref, num: parsed.num, isValid: true, motivo: null });
  }
  renderPreview();
}

function renderPreview() {
  const section = document.getElementById('previewSection');
  const grid    = document.getElementById('previewGrid');
  const counter = document.getElementById('previewCount');

  counter.textContent = pendingFiles.length;

  if (pendingFiles.length === 0) {
    section.style.display = 'none';
    updateSubmitButton();
    return;
  }
  section.style.display = 'block';

  grid.innerHTML = '';
  pendingFiles.forEach((item, idx) => {
    const div = document.createElement('div');
    div.className = 'preview-item' + (item.isValid ? '' : ' is-invalid');

    const url = URL.createObjectURL(item.file);
    const img = document.createElement('img');
    img.src = url;
    img.alt = item.file.name;
    img.onload = () => URL.revokeObjectURL(url);
    div.appendChild(img);

    const info = document.createElement('div');
    info.className = 'preview-info';
    if (item.isValid) {
      info.innerHTML = `<strong>${escapeHtml(item.ref)}</strong><span>1 producto</span>`;
    } else {
      info.innerHTML = `<strong>${escapeHtml(item.file.name)}</strong><span>${escapeHtml(item.motivo)}</span>`;
    }
    div.appendChild(info);

    if (!item.isValid) {
      const tag = document.createElement('div');
      tag.className = 'preview-invalid-tag';
      tag.textContent = 'Inválido';
      div.appendChild(tag);
    }

    const btnRemove = document.createElement('button');
    btnRemove.type = 'button';
    btnRemove.className = 'preview-remove';
    btnRemove.innerHTML = '✕';
    btnRemove.setAttribute('aria-label', 'Quitar');
    btnRemove.addEventListener('click', () => {
      pendingFiles.splice(idx, 1);
      renderPreview();
    });
    div.appendChild(btnRemove);

    grid.appendChild(div);
  });

  updateSubmitButton();
}

function updateSubmitButton() {
  const btn    = document.getElementById('btnSubirProductos');
  const catId  = document.getElementById('uploadCategoria').value;
  const validos = pendingFiles.filter(p => p.isValid).length;
  if (!catId || validos === 0) {
    btn.disabled = true;
    return;
  }
  // Validar que la cadena llegue a una hoja
  const cat = uploadCategoriasTree.find(c => c.id === catId);
  if (!cat) { btn.disabled = true; return; }
  const catTieneSubcats = Array.isArray(cat.subcategorias) && cat.subcategorias.length > 0;
  if (catTieneSubcats) {
    const subId = document.getElementById('uploadSubcategoria').value;
    if (!subId) { btn.disabled = true; return; }
    const sub = cat.subcategorias.find(s => s.id === subId);
    if (!sub) { btn.disabled = true; return; }
    if (Array.isArray(sub.subcategorias) && sub.subcategorias.length > 0) {
      const subSubId = document.getElementById('uploadSubSubcategoria').value;
      if (!subSubId) { btn.disabled = true; return; }
    }
  }
  btn.disabled = false;
}

async function submitUpload(e) {
  e.preventDefault();
  const select = document.getElementById('uploadCategoria');
  const btn    = document.getElementById('btnSubirProductos');
  const error  = document.getElementById('uploadError');
  const summary= document.getElementById('uploadSummary');

  error.textContent = '';
  summary.style.display = 'none';
  summary.innerHTML = '';

  const validos = pendingFiles.filter(p => p.isValid);
  if (!select.value) {
    error.textContent = 'Selecciona una categoría';
    return;
  }
  if (validos.length === 0) {
    error.textContent = 'No hay fotos válidas para subir';
    return;
  }

  // Lock UI
  btn.disabled = true;
  btn.querySelector('.btn-text').textContent = 'Subiendo…';
  btn.querySelector('.btn-spinner').style.display = 'inline-block';

  const subPath = getSubPathActual();

  const fd = new FormData();
  fd.append('categoria', select.value);
  fd.append('sub', JSON.stringify(subPath));
  validos.forEach(item => fd.append('fotos', item.file, item.file.name));

  try {
    const res  = await fetch('/api/productos/upload', { method: 'POST', body: fd });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Error en la subida');

    renderSummary(data.resumen, validos);

    // Limpiar lo que se subió bien
    const refsSubidos = new Set((data.resumen.creadosList || []).map(c => c.ref));
    if (refsSubidos.size > 0) {
      pendingFiles = pendingFiles.filter(p => !p.isValid || !refsSubidos.has(p.ref));
      renderPreview();
      // refrescar stats
      if (typeof cargarEstadisticas === 'function') cargarEstadisticas();
    }
  } catch (err) {
    error.textContent = err.message;
  } finally {
    btn.disabled = false;
    btn.querySelector('.btn-text').textContent = 'Subir fotos';
    btn.querySelector('.btn-spinner').style.display = 'none';
    updateSubmitButton();
  }
}

function renderSummary(r, validos) {
  const cont = document.getElementById('uploadSummary');
  cont.innerHTML = '';

  if (r.creados > 0) {
    const row = document.createElement('div');
    row.className = 'upload-summary-row is-success';
    row.innerHTML = `
      <span class="icon">✅</span>
      <div class="text">
        <strong>${r.creados} producto(s) creado(s)</strong>
        <ul>${r.creadosList.map(c => `<li><code>${escapeHtml(c.ref)}</code></li>`).join('')}</ul>
      </div>
    `;
    cont.appendChild(row);
  }

  if (r.saltados > 0) {
    const row = document.createElement('div');
    row.className = 'upload-summary-row is-warning';
    row.innerHTML = `
      <span class="icon">⏭️</span>
      <div class="text">
        <strong>${r.saltados} referencia(s) ya existían — no se sobrescribieron</strong>
        <ul>${r.saltadosList.map(s => `<li><code>${escapeHtml(s.ref)}</code> — ${escapeHtml(s.motivo)}</li>`).join('')}</ul>
      </div>
    `;
    cont.appendChild(row);
  }

  if (r.ignorados > 0) {
    const row = document.createElement('div');
    row.className = 'upload-summary-row is-warning';
    row.innerHTML = `
      <span class="icon">⚠️</span>
      <div class="text">
        <strong>${r.ignorados} archivo(s) con nombre inválido — no se procesaron</strong>
        <ul>${r.ignoradosList.map(i => `<li><code>${escapeHtml(i.archivo)}</code> — ${escapeHtml(i.motivo)}</li>`).join('')}</ul>
      </div>
    `;
    cont.appendChild(row);
  }

  if (r.errores > 0) {
    const row = document.createElement('div');
    row.className = 'upload-summary-row is-error';
    row.innerHTML = `
      <span class="icon">❌</span>
      <div class="text">
        <strong>${r.errores} error(es) al procesar</strong>
        <ul>${r.erroresList.map(e => `<li><code>${escapeHtml(e.ref)}</code> — ${escapeHtml(e.motivo)}</li>`).join('')}</ul>
      </div>
    `;
    cont.appendChild(row);
  }

  if (cont.children.length === 0) {
    cont.innerHTML = `<div class="upload-summary-row is-warning"><span class="icon">ℹ️</span><div class="text"><strong>Nada se subió</strong></div></div>`;
  }

  cont.style.display = 'flex';
}

// ════════════════════════════════
// PRODUCTOS DISPONIBLES (lista en vista Productos)
// Solo se muestran los productos que NO están agotados
// (img no empieza con "agotados/").
// ════════════════════════════════
let deletingProductoRef = null;
let busquedaProductos   = '';   // texto de la barra de búsqueda
let productosCache      = [];   // últimos productos cargados (para filtrar en cliente)
let categoriasCache     = [];   // últimas categorías cargadas (para iconos)

async function cargarProductosDisponibles() {
  const grid = document.getElementById('productosListGrid');
  if (!grid) return;
  grid.innerHTML = '<p style="text-align:center;color:var(--ink-mute);padding:24px;grid-column:1/-1;">Cargando…</p>';

  try {
    const [resProds, resCats] = await Promise.all([
      fetch('/api/productos', { credentials: 'include' }),
      fetch('/api/categorias',  { credentials: 'include' })
    ]);
    if (!resProds.ok) {
      if (resProds.status === 401 || resProds.status === 403) { mostrarLogin(); return; }
      throw new Error('Error al cargar productos');
    }
    const productos  = await resProds.json();
    const categorias = resCats.ok ? await resCats.json() : [];
    cachedProductos  = productos;
    productosCache   = productos;
    categoriasCache  = categorias;

    renderProductosDisponibles();
  } catch (err) {
    console.error(err);
    grid.innerHTML = '<p style="text-align:center;color:var(--danger);padding:24px;grid-column:1/-1;">Error al cargar productos</p>';
  }
}

function renderProductosDisponibles() {
  const grid  = document.getElementById('productosListGrid');
  const count = document.getElementById('productosDisponiblesCount');
  if (!grid) return;

  // Solo productos disponibles: img NO empieza con "agotados/"
  // (y descartamos sin-categoria para mantener consistencia con el catálogo público)
  let disponibles = productosCache.filter(p =>
    p.cat && p.cat !== 'sin-categoria' && (!p.img || !p.img.startsWith('agotados/'))
  );
  const total = disponibles.length;

  // Filtro por referencia (case-insensitive, match parcial)
  const q = busquedaProductos.trim().toLowerCase();
  if (q) {
    disponibles = disponibles.filter(p => String(p.ref || '').toLowerCase().includes(q));
  }

  // Contador: "X items" o "X / Y items" cuando hay filtro
  if (count) {
    if (q) {
      count.textContent = `${disponibles.length} / ${total} item${total === 1 ? '' : 's'}`;
    } else {
      count.textContent = `${total} item${total === 1 ? '' : 's'}`;
    }
  }

  if (total === 0) {
    grid.innerHTML = '<div class="productos-empty">No hay productos disponibles aún. Sube fotos en el formulario de arriba.</div>';
    return;
  }

  if (disponibles.length === 0) {
    grid.innerHTML = `<div class="productos-empty">No se encontraron productos con la referencia "<strong>${escapeHtml(busquedaProductos)}</strong>".</div>`;
    return;
  }

  // Ordenar por ref para tener un orden estable
  disponibles.sort((a, b) => String(a.ref).localeCompare(String(b.ref)));

  const categorias = categoriasCache;
  grid.innerHTML = disponibles.map(p => {
    const cat = categorias.find(c => c.id === p.cat);
    const icon = cat ? cat.icono : '📦';
    const imgSrc = p.img
      ? (p.img.startsWith('/') ? p.img : `/Imagenes/${p.img}`)
      : '';
    return `
      <div class="producto-item">
        <div class="producto-thumb-wrap">
          <img class="producto-thumb" src="${escapeHtml(imgSrc)}" alt="${escapeHtml(p.ref)}" loading="lazy"
               onerror="this.classList.add('is-broken')">
          <span class="producto-thumb-fallback">${icon}</span>
        </div>
        <div class="producto-info">
          <div class="producto-ref">${escapeHtml(p.ref)}</div>
          <div class="producto-cat-tag">${icon} ${escapeHtml(cat ? cat.nombre : p.cat)}</div>
        </div>
        <div class="producto-actions">
          <button class="btn-icon btn-icon-danger" onclick="abrirAgotarProducto('${escapeAttr(p.ref)}')" title="Marcar como agotado" type="button">🚫</button>
        </div>
      </div>
    `;
  }).join('');
}

// Hook de la barra de búsqueda — se ejecuta una sola vez al cargar el DOM.
function setupProductosSearch() {
  const input = document.getElementById('productosSearch');
  const clear = document.getElementById('productosSearchClear');
  if (!input || !clear) return;

  input.addEventListener('input', e => {
    busquedaProductos = e.target.value;
    clear.style.display = busquedaProductos ? 'flex' : 'none';
    renderProductosDisponibles();
  });

  clear.addEventListener('click', () => {
    input.value = '';
    busquedaProductos = '';
    clear.style.display = 'none';
    renderProductosDisponibles();
    input.focus();
  });
}

// ════════════════════════════════
// EXCEL/CSV — asignar nombre + precio por referencia
// ════════════════════════════════
function setupExcelUpload() {
  const input = document.getElementById('excelArchivo');
  const hint  = document.getElementById('excelArchivoHint');
  const form  = document.getElementById('formExcelUpload');
  const btn   = document.getElementById('btnProcesarExcel');
  if (!input || !form) return;

  input.addEventListener('change', () => {
    const f = input.files && input.files[0];
    if (f) {
      const sizeKB = (f.size / 1024).toFixed(1);
      hint.textContent = `📄 ${f.name} · ${sizeKB} KB`;
    } else {
      hint.textContent = 'Ningún archivo seleccionado';
    }
    btn.disabled = !f;
  });

  form.addEventListener('submit', submitExcelUpload);
}

async function submitExcelUpload(e) {
  e.preventDefault();
  const input = document.getElementById('excelArchivo');
  const btn   = document.getElementById('btnProcesarExcel');
  const errEl = document.getElementById('excelError');
  const summ  = document.getElementById('excelSummary');
  const f     = input.files && input.files[0];

  errEl.textContent = '';
  errEl.classList.remove('show');
  summ.style.display = 'none';
  summ.innerHTML = '';

  if (!f) {
    errEl.textContent = 'Selecciona un archivo';
    errEl.classList.add('show');
    return;
  }

  // Lock UI
  btn.disabled = true;
  btn.querySelector('.btn-text').textContent = 'Procesando…';
  btn.querySelector('.btn-spinner').style.display = 'inline-block';

  const fd = new FormData();
  fd.append('archivo', f, f.name);

  try {
    const res  = await fetch('/api/productos/excel', { method: 'POST', body: fd, credentials: 'include' });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Error al procesar el archivo');

    renderExcelSummary(data.resumen);

    // Refrescar lista de productos disponibles (para que se vean los nuevos nombres)
    if (currentView === 'productos' && typeof cargarProductosDisponibles === 'function') {
      cargarProductosDisponibles();
    }
  } catch (err) {
    errEl.textContent = err.message;
    errEl.classList.add('show');
  } finally {
    btn.disabled = false;
    btn.querySelector('.btn-text').textContent = 'Procesar archivo';
    btn.querySelector('.btn-spinner').style.display = 'none';
  }
}

function renderExcelSummary(r) {
  const cont = document.getElementById('excelSummary');
  cont.innerHTML = '';

  if (r.actualizados > 0) {
    const row = document.createElement('div');
    row.className = 'upload-summary-row is-success';
    const items = (r.actualizadosList || []).slice(0, 10).map(a =>
      `<li><code>${escapeHtml(a.ref)}</code>${a.fila ? ` <span style="opacity:0.7">(fila ${a.fila})</span>` : ''}</li>`
    ).join('');
    const more = r.actualizadosList && r.actualizadosList.length > 10
      ? `<li style="opacity:0.7">…y ${r.actualizadosList.length - 10} más</li>` : '';
    row.innerHTML = `
      <span class="icon">✅</span>
      <div class="text">
        <strong>${r.actualizados} producto(s) actualizado(s)</strong>
        <ul>${items}${more}</ul>
      </div>
    `;
    cont.appendChild(row);
  }

  if (r.noEncontrados > 0) {
    const row = document.createElement('div');
    row.className = 'upload-summary-row is-warning';
    const items = (r.noEncontradosList || []).slice(0, 10).map(ref =>
      `<li><code>${escapeHtml(ref)}</code> <span style="opacity:0.7">(no existe en el catálogo)</span></li>`
    ).join('');
    const more = r.noEncontradosList && r.noEncontradosList.length > 10
      ? `<li style="opacity:0.7">…y ${r.noEncontradosList.length - 10} más</li>` : '';
    row.innerHTML = `
      <span class="icon">⚠️</span>
      <div class="text">
        <strong>${r.noEncontrados} referencia(s) no encontrada(s) — no se crearon productos nuevos</strong>
        <ul>${items}${more}</ul>
      </div>
    `;
    cont.appendChild(row);
  }

  if (r.errores > 0) {
    const row = document.createElement('div');
    row.className = 'upload-summary-row is-error';
    const items = (r.erroresList || []).slice(0, 10).map(e =>
      `<li><code>${escapeHtml(e.ref || ('Fila ' + e.fila))}</code> — ${escapeHtml(e.motivo)}</li>`
    ).join('');
    const more = r.erroresList && r.erroresList.length > 10
      ? `<li style="opacity:0.7">…y ${r.erroresList.length - 10} más</li>` : '';
    row.innerHTML = `
      <span class="icon">❌</span>
      <div class="text">
        <strong>${r.errores} error(es) al procesar</strong>
        <ul>${items}${more}</ul>
      </div>
    `;
    cont.appendChild(row);
  }

  if (r.filasVacias > 0) {
    const row = document.createElement('div');
    row.className = 'upload-summary-row is-warning';
    row.innerHTML = `
      <span class="icon">ℹ️</span>
      <div class="text">
        <strong>${r.filasVacias} fila(s) vacía(s) ignorada(s)</strong>
      </div>
    `;
    cont.appendChild(row);
  }

  if (cont.children.length === 0) {
    cont.innerHTML = `<div class="upload-summary-row is-warning"><span class="icon">ℹ️</span><div class="text"><strong>Nada se actualizó</strong></div></div>`;
  }

  cont.style.display = 'flex';
}

function abrirAgotarProducto(ref) {
  if (!confirm(`¿Marcar el producto "${ref}" como agotado?\n\nSu imagen se moverá a la carpeta "agotados" y dejará de mostrarse en el catálogo público. Podrás reactivarlo después desde la pestaña "Agotados".`)) return;
  agotarProducto(ref);
}

async function agotarProducto(ref) {
  try {
    const res = await fetch(`/api/productos/${encodeURIComponent(ref)}/agotar`, {
      method: 'POST',
      credentials: 'include'
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Error al agotar el producto');

    // Refrescar vistas relacionadas
    await Promise.all([
      cargarProductosDisponibles(),
      cargarEstadisticas()
    ]);
  } catch (err) {
    alert('Error: ' + err.message);
  }
}

// ════════════════════════════════
// AGOTADOS (vista dedicada)
// ════════════════════════════════
async function cargarAgotados() {
  const grid = document.getElementById('agotadosGrid');
  if (!grid) return;
  grid.innerHTML = '<p style="text-align:center;color:var(--ink-mute);padding:24px;grid-column:1/-1;">Cargando…</p>';

  try {
    const [resProds, resCats] = await Promise.all([
      fetch('/api/productos', { credentials: 'include' }),
      fetch('/api/categorias',  { credentials: 'include' })
    ]);
    if (!resProds.ok) {
      if (resProds.status === 401 || resProds.status === 403) { mostrarLogin(); return; }
      throw new Error('Error al cargar productos');
    }
    const productos  = await resProds.json();
    const categorias = resCats.ok ? await resCats.json() : [];
    cachedProductos = productos;

    renderAgotados(productos, categorias);
  } catch (err) {
    console.error(err);
    grid.innerHTML = '<p style="text-align:center;color:var(--danger);padding:24px;grid-column:1/-1;">Error al cargar agotados</p>';
  }
}

function renderAgotados(productos, categorias) {
  const grid = document.getElementById('agotadosGrid');
  if (!grid) return;

  // Solo agotados: img empieza con "agotados/"
  const agotados = productos.filter(p => p.img && p.img.startsWith('agotados/'));

  if (agotados.length === 0) {
    grid.innerHTML = '<div class="agotados-empty">No hay productos agotados. Cuando un producto se agote, aparecerá aquí.</div>';
    return;
  }

  // Ordenar por ref
  agotados.sort((a, b) => String(a.ref).localeCompare(String(b.ref)));

  grid.innerHTML = agotados.map(p => {
    const cat = categorias.find(c => c.id === p.cat);
    const imgSrc = `/Imagenes/${p.img}`;
    return `
      <div class="agotado-card">
        <div class="agotado-img-wrap">
          <span class="agotado-badge">Agotado</span>
          <img class="agotado-img" src="${escapeHtml(imgSrc)}" alt="${escapeHtml(p.ref)}" loading="lazy"
               onerror="this.remove(); this.parentElement.classList.add('img-fallback');">
          <span class="agotado-fallback">${cat ? cat.icono : '🚫'}</span>
        </div>
        <div class="agotado-info">
          <div class="agotado-ref">${escapeHtml(p.ref)}</div>
          <div class="agotado-cat">${cat ? cat.icono : '📦'} ${escapeHtml(cat ? cat.nombre : p.cat)}</div>
        </div>
        <div class="agotado-actions">
          <button class="btn btn-ghost btn-sm" onclick="reactivarProducto('${escapeAttr(p.ref)}')" type="button" title="Volver a disponible">↩ Reactivar</button>
          <button class="btn btn-danger btn-sm" onclick="abrirEliminarProductoDef('${escapeAttr(p.ref)}')" type="button" title="Eliminar definitivamente">🗑 Eliminar</button>
        </div>
      </div>
    `;
  }).join('');
}

async function reactivarProducto(ref) {
  if (!confirm(`¿Reactivar el producto "${ref}"?\n\nSu imagen volverá a su categoría original y volverá a mostrarse en el catálogo público.`)) return;
  try {
    const res = await fetch(`/api/productos/${encodeURIComponent(ref)}/reactivar`, {
      method: 'POST',
      credentials: 'include'
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Error al reactivar');

    // Refrescar vistas
    await Promise.all([
      cargarAgotados(),
      cargarEstadisticas()
    ]);
    // Si la lista de productos está visible (cargada), refrescarla también
    const grid = document.getElementById('productosListGrid');
    if (grid && currentView === 'productos') {
      cargarProductosDisponibles();
    }
  } catch (err) {
    alert('Error: ' + err.message);
  }
}

function abrirEliminarProductoDef(ref) {
  deletingProductoRef = ref;
  document.getElementById('eliminarProdRef').textContent = ref;
  document.getElementById('eliminarProdError').classList.remove('show');
  document.getElementById('eliminarProdError').textContent = '';
  abrirModal('modalEliminarProductoDef');
}

async function confirmarEliminarProductoDef() {
  if (!deletingProductoRef) return;

  const btn   = document.getElementById('btnConfirmarEliminarProdDef');
  const errEl = document.getElementById('eliminarProdError');
  btn.disabled    = true;
  btn.textContent = 'Eliminando…';

  try {
    const res = await fetch(`/api/productos/${encodeURIComponent(deletingProductoRef)}`, {
      method: 'DELETE',
      credentials: 'include'
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error || 'Error al eliminar');

    deletingProductoRef = null;
    cerrarModal('modalEliminarProductoDef');
    await cargarAgotados();
    cargarEstadisticas();
    // Si la lista de productos está visible, refrescarla
    const grid = document.getElementById('productosListGrid');
    if (grid && currentView === 'productos') {
      cargarProductosDisponibles();
    }
  } catch (err) {
    errEl.textContent = err.message;
    errEl.classList.add('show');
  } finally {
    btn.disabled    = false;
    btn.textContent = 'Sí, eliminar';
  }
}

// Hook al botón de confirmación del modal de eliminación definitiva
// (se ejecuta al cargar el DOM junto con el resto de setupModals)
document.addEventListener('DOMContentLoaded', () => {
  const btn = document.getElementById('btnConfirmarEliminarProdDef');
  if (btn) btn.addEventListener('click', confirmarEliminarProductoDef);
});

// ════════════════════════════════
// DESCARGAS — gestor genérico de árbol de selección
// Usado por los subtabs PDF y Fotos.
// ════════════════════════════════
function isSafari() {
  return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
}

function createDescargaGestor(config) {
  const {
    selectorId, summaryId, summaryCountId, summaryLeavesId,
    btnGenerateId, btnAllId, btnNoneId, errorId,
    generateUrl, generateBtnText, fallbackFilename,
    extractZipOnNonSafari = false
  } = config;

  const state = {
    arbol: [],
    productos: [],
    seleccionHojas: new Set()
  };

  function contarProductosEnRama(catId, subArr) {
    return state.productos.filter(p => {
      if (!p.cat || p.cat === 'sin-categoria') return false;
      if (!p.img) return false;
      if (p.img.startsWith('agotados/')) return false;
      if (p.cat !== catId) return false;
      const pSub = Array.isArray(p.sub) ? p.sub : [];
      if (subArr.length === 0) return true;
      if (pSub.length < subArr.length) return false;
      for (let i = 0; i < subArr.length; i++) {
        if (pSub[i] !== subArr[i]) return false;
      }
      return true;
    }).length;
  }

  function renderNodoHtml(nodo, pathArr) {
    const key         = pathArr.join('|');
    const catId       = pathArr[0];
    const subArr      = pathArr.slice(1);
    const tieneHijos  = Array.isArray(nodo.subcategorias) && nodo.subcategorias.length > 0;

    const count = tieneHijos
      ? nodo.subcategorias.reduce(
          (sum, s) => sum + contarProductosEnRama(catId, [...subArr, s.id]),
          0
        )
      : contarProductosEnRama(catId, subArr);

    const isLeaf    = !tieneHijos;
    const disabled  = count === 0 ? 'disabled' : '';
    const countCls  = count === 0 ? 'is-zero' : '';

    let html = `
      <div class="pdf-node">
        <label class="pdf-check">
          <input type="checkbox"
                 data-key="${escapeAttr(key)}"
                 data-has-children="${tieneHijos ? '1' : '0'}"
                 data-leaf="${isLeaf ? '1' : '0'}"
                 ${disabled}>
        </label>
        <span class="pdf-node-label">
          <span class="pdf-node-icon">${nodo.icono || '📦'}</span>
          <span class="pdf-node-name">${escapeHtml(nodo.nombre)}</span>
          <span class="pdf-node-count ${countCls}">${count}</span>
        </span>
      </div>
    `;

    if (tieneHijos) {
      const childrenHtml = nodo.subcategorias
        .map(s => renderNodoHtml(s, [...pathArr, s.id]))
        .join('');
      html += `<div class="pdf-node-children">${childrenHtml}</div>`;
    }

    return html;
  }

  async function cargarArbol() {
    const cont = document.getElementById(selectorId);
    cont.innerHTML = '<p class="pdf-selector-loading">Cargando categorías…</p>';

    try {
      const [resCats, resProds] = await Promise.all([
        fetch('/api/categorias', { credentials: 'include' }),
        fetch('/api/productos',   { credentials: 'include' })
      ]);
      if (!resCats.ok)  throw new Error('Error al cargar categorías');
      if (!resProds.ok) throw new Error('Error al cargar productos');

      state.arbol     = await resCats.json();
      state.productos = await resProds.json();
      state.seleccionHojas = new Set();

      renderArbol();
    } catch (err) {
      console.error(`[${selectorId}] Error cargando datos:`, err);
      cont.innerHTML = '<p class="pdf-selector-empty">Error al cargar. Recarga la pestaña.</p>';
    }
  }

  function renderArbol() {
    const cont = document.getElementById(selectorId);
    const catsVisibles = state.arbol.filter(c => c.id !== 'sin-categoria');

    if (catsVisibles.length === 0) {
      cont.innerHTML = '<p class="pdf-selector-empty">No hay categorías para mostrar.</p>';
      return;
    }

    cont.innerHTML = catsVisibles.map(cat => renderNodoHtml(cat, [cat.id])).join('');

    cont.querySelectorAll('.pdf-check input').forEach(input => {
      input.addEventListener('change', onCheckChange);
    });

    actualizarResumen();
  }

  function onCheckChange(e) {
    const input       = e.target;
    const key         = input.dataset.key;
    const hasChildren = input.dataset.hasChildren === '1';
    const isChecked   = input.checked;

    if (input.disabled) {
      input.checked = false;
      return;
    }

    if (hasChildren) {
      setDescendantsChecked(key, isChecked);
    } else {
      if (isChecked) {
        state.seleccionHojas.add(key);
      } else {
        state.seleccionHojas.delete(key);
      }
    }

    input.indeterminate = false;
    updateAncestorsState();
    actualizarResumen();
  }

  function setDescendantsChecked(parentKey, isChecked) {
    const cont   = document.getElementById(selectorId);
    const inputs = cont.querySelectorAll(`.pdf-check input[data-key^="${CSS.escape(parentKey)}|"]`);

    inputs.forEach(child => {
      if (child.disabled) return;
      child.checked       = isChecked;
      child.indeterminate = false;
      if (child.dataset.leaf === '1') {
        if (isChecked) {
          state.seleccionHojas.add(child.dataset.key);
        } else {
          state.seleccionHojas.delete(child.dataset.key);
        }
      }
    });
  }

  function updateAncestorsState() {
    const cont   = document.getElementById(selectorId);
    const inputs = cont.querySelectorAll('.pdf-check input[data-has-children="1"]');

    inputs.forEach(input => {
      const key        = input.dataset.key;
      const leafInputs = cont.querySelectorAll(
        `.pdf-check input[data-leaf="1"][data-key^="${CSS.escape(key)}|"]`
      );

      let checked = 0, total = 0;
      leafInputs.forEach(leaf => {
        if (leaf.disabled) return;
        total++;
        if (leaf.checked) checked++;
      });

      input.checked       = total > 0 && checked === total;
      input.indeterminate = checked > 0 && checked < total;
    });
  }

  function marcarTodo() {
    const cont = document.getElementById(selectorId);
    cont.querySelectorAll('.pdf-check input').forEach(input => {
      if (input.disabled) return;
      input.checked       = true;
      input.indeterminate = false;
      if (input.dataset.leaf === '1') {
        state.seleccionHojas.add(input.dataset.key);
      }
    });
    updateAncestorsState();
    actualizarResumen();
  }

  function desmarcarTodo() {
    state.seleccionHojas.clear();
    const cont = document.getElementById(selectorId);
    cont.querySelectorAll('.pdf-check input').forEach(input => {
      input.checked       = false;
      input.indeterminate = false;
    });
    actualizarResumen();
  }

  function actualizarResumen() {
    const summary  = document.getElementById(summaryId);
    const countEl  = document.getElementById(summaryCountId);
    const leavesEl = document.getElementById(summaryLeavesId);

    let totalProductos = 0;
    for (const key of state.seleccionHojas) {
      const parts  = key.split('|');
      const catId  = parts[0];
      const subArr = parts.slice(1);
      totalProductos += contarProductosEnRama(catId, subArr);
    }

    if (state.seleccionHojas.size === 0) {
      summary.style.display = 'none';
    } else {
      summary.style.display = '';
      countEl.textContent  = totalProductos;
      leavesEl.textContent = state.seleccionHojas.size;
    }

    const btn = document.getElementById(btnGenerateId);
    if (btn) btn.disabled = state.seleccionHojas.size === 0;
  }

  async function generar() {
    const btn    = document.getElementById(btnGenerateId);
    const errEl  = document.getElementById(errorId);
    errEl.textContent = '';
    errEl.classList.remove('show');

    if (state.seleccionHojas.size === 0) {
      errEl.textContent = 'Selecciona al menos una hoja';
      errEl.classList.add('show');
      return;
    }

    const seleccion = Array.from(state.seleccionHojas).map(key => {
      const parts = key.split('|');
      return { cat: parts[0], sub: parts.slice(1) };
    });

    btn.disabled = true;
    const spinner = btn.querySelector('.btn-spinner');
    const btnText = btn.querySelector('.btn-text');
    btnText.textContent = 'Generando…';
    if (spinner) spinner.style.display = 'inline-block';

    try {
      const res = await fetch(generateUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ seleccion })
      });

      if (!res.ok) {
        const data = await res.json().catch(() => ({}));
        throw new Error(data.error || 'Error al generar la descarga');
      }

      const blob = await res.blob();

      if (extractZipOnNonSafari && !isSafari()) {
        const zip      = await JSZip.loadAsync(blob);
        const entries  = [];
        zip.forEach((relPath, zipEntry) => entries.push({ relPath, zipEntry }));
        btnText.textContent = `Extrayendo ${entries.length} foto(s)…`;
        for (const { relPath, zipEntry } of entries) {
          const imgBlob = await zipEntry.async('blob');
          const imgUrl  = URL.createObjectURL(imgBlob);
          const a       = document.createElement('a');
          a.href        = imgUrl;
          a.download    = relPath;
          document.body.appendChild(a);
          a.click();
          a.remove();
          URL.revokeObjectURL(imgUrl);
          await new Promise(r => setTimeout(r, 300));
        }
      } else {
        const dispo = res.headers.get('Content-Disposition') || '';
        const match = dispo.match(/filename="(.+)"/);
        const url   = URL.createObjectURL(blob);
        const a     = document.createElement('a');
        a.href      = url;
        a.download  = match ? match[1] : fallbackFilename;
        document.body.appendChild(a);
        a.click();
        a.remove();
        URL.revokeObjectURL(url);
      }
    } catch (err) {
      errEl.textContent = err.message;
      errEl.classList.add('show');
    } finally {
      btn.disabled = false;
      btnText.textContent = generateBtnText;
      if (spinner) spinner.style.display = 'none';
      actualizarResumen();
    }
  }

  function setup() {
    const btnGen  = document.getElementById(btnGenerateId);
    const btnAll  = document.getElementById(btnAllId);
    const btnNone = document.getElementById(btnNoneId);

    if (btnGen && !btnGen.dataset.wired) {
      btnGen.addEventListener('click', generar);
      btnGen.dataset.wired = '1';
    }
    if (btnAll && !btnAll.dataset.wired) {
      btnAll.addEventListener('click', marcarTodo);
      btnAll.dataset.wired = '1';
    }
    if (btnNone && !btnNone.dataset.wired) {
      btnNone.addEventListener('click', desmarcarTodo);
      btnNone.dataset.wired = '1';
    }

    cargarArbol();
  }

  return { setup, state, marcarTodo, desmarcarTodo, generar };
}

// Instancias del gestor para cada subtab
let pdfGestor;
let fotosGestor;

// Inicialización diferida en cargarDescargas()
function initGestores() {
  if (pdfGestor) return;
  pdfGestor = createDescargaGestor({
    selectorId:       'pdfSelector',
    summaryId:        'pdfSummary',
    summaryCountId:   'pdfSummaryCount',
    summaryLeavesId:  'pdfSummaryLeaves',
    btnGenerateId:    'btnPdfGenerar',
    btnAllId:         'btnPdfMarcarTodo',
    btnNoneId:        'btnPdfDesmarcarTodo',
    errorId:          'pdfError',
    generateUrl:      '/api/descargas/pdf',
    generateBtnText:  '📄 Generar PDF',
    fallbackFilename: 'catalogo-infinity.pdf'
  });

  fotosGestor = createDescargaGestor({
    selectorId:            'fotosSelector',
    summaryId:             'fotosSummary',
    summaryCountId:        'fotosSummaryCount',
    summaryLeavesId:       'fotosSummaryLeaves',
    btnGenerateId:         'btnFotosGenerar',
    btnAllId:              'btnFotosMarcarTodo',
    btnNoneId:             'btnFotosDesmarcarTodo',
    errorId:               'fotosError',
    generateUrl:           '/api/descargas/fotos',
    generateBtnText:       '🖼️ Descargar fotos',
    fallbackFilename:      'fotos-infinity.zip',
    extractZipOnNonSafari: true
  });
}

// cargarDescargas() ahora usa los gestores
// (definido arriba en la sección DESCARGAS)
