Solo items y usuarios

This commit is contained in:
2026-06-05 22:06:52 -05:00
commit aa5e85ae94
23 changed files with 6152 additions and 0 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+469
View File
@@ -0,0 +1,469 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Infinity — Panel Admin</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="admin.css?v=4">
</head>
<body>
<!-- FONDO MESH -->
<div class="mesh-bg" aria-hidden="true">
<div class="mesh-blob mesh-blob-1"></div>
<div class="mesh-blob mesh-blob-2"></div>
<div class="mesh-blob mesh-blob-3"></div>
</div>
<!-- LOGIN VIEW -->
<main class="login-view" id="loginView">
<div class="login-card">
<div class="login-header">
<a href="../" class="logo">
<span class="logo-symbol"></span>
<span class="logo-text">Infinity</span>
</a>
<span class="login-badge">Panel administrativo</span>
</div>
<h1 class="login-title">Bienvenido <em>de nuevo</em></h1>
<p class="login-subtitle">Ingresa tus credenciales para continuar</p>
<form class="login-form" id="loginForm" autocomplete="on">
<div class="form-group">
<label for="usuario">Usuario</label>
<input type="text" id="usuario" name="usuario" placeholder="admin" required autocomplete="username">
</div>
<div class="form-group">
<label for="contraseña">Contraseña</label>
<input type="password" id="contraseña" name="contraseña" placeholder="••••••••" required autocomplete="current-password">
</div>
<div class="form-error" id="formError" role="alert"></div>
<button type="submit" class="btn btn-primary btn-full">Ingresar</button>
</form>
<a href="../" class="back-link">← Volver al catálogo</a>
</div>
</main>
<!-- ADMIN APP (post-login) -->
<div class="admin-app" id="adminApp" style="display:none">
<!-- SALUDO SUPERIOR -->
<header class="admin-greeting">
<h1 class="greeting-title">Hola, <em id="adminName">Admin</em> 👋</h1>
<p class="greeting-subtitle">Bienvenido al panel de Infinity.</p>
</header>
<!-- TOPBAR PILL (sticky) -->
<nav class="admin-topbar" aria-label="Navegación del panel">
<div class="topbar-container">
<button type="button" class="nav-item active" data-view="dashboard">
<span class="nav-icon">🏠</span>
<span class="nav-label">Dashboard</span>
</button>
<button type="button" class="nav-item" data-view="productos">
<span class="nav-icon">📦</span>
<span class="nav-label">Productos</span>
</button>
<button type="button" class="nav-item" data-view="agotados">
<span class="nav-icon">🚫</span>
<span class="nav-label">Agotados</span>
</button>
<button type="button" class="nav-item" data-view="categorias">
<span class="nav-icon">🗂️</span>
<span class="nav-label">Categorías</span>
</button>
<button type="button" class="nav-item" data-view="usuarios">
<span class="nav-icon">👥</span>
<span class="nav-label">Usuarios</span>
</button>
<span class="topbar-divider" aria-hidden="true"></span>
<button type="button" class="nav-item nav-item-logout" id="btnLogout">
<span class="nav-icon"></span>
<span class="nav-label">Cerrar sesión</span>
</button>
</div>
</nav>
<!-- CONTENIDO PRINCIPAL -->
<main class="admin-main">
<!-- DASHBOARD VIEW -->
<section class="view active" data-view="dashboard">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">📦</div>
<div class="stat-info">
<div class="stat-value" id="statProductos"></div>
<div class="stat-label">Productos</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">🗂️</div>
<div class="stat-info">
<div class="stat-value" id="statCategorias"></div>
<div class="stat-label">Categorías</div>
</div>
</div>
</div>
</section>
<!-- PRODUCTOS VIEW -->
<section class="view" data-view="productos">
<header class="view-header">
<h1 class="view-title">Productos</h1>
<p class="view-subtitle">Sube fotos masivamente. La descripción y el precio se asignarán después.</p>
</header>
<div class="view-content-card">
<form id="formUploadProductos" class="upload-form" autocomplete="off">
<div class="form-group">
<label for="uploadCategoria">Categoría destino</label>
<select id="uploadCategoria" required>
<option value="">— Selecciona una categoría —</option>
</select>
<span class="form-hint">Todas las fotos subidas se guardarán en esta carpeta.</span>
</div>
<div class="form-group">
<label>Zona de subida</label>
<div class="dropzone" id="dropzone">
<input type="file" id="inputFotos" accept="image/*" multiple hidden>
<div class="dropzone-inner">
<div class="dropzone-icon">📤</div>
<p class="dropzone-title">Arrastra fotos aquí o haz clic para seleccionar</p>
<p class="dropzone-hint">JPG, PNG, WEBP, GIF · Máx 20MB por archivo · 50 archivos por subida</p>
</div>
</div>
</div>
<div class="upload-preview-section" id="previewSection" style="display:none">
<div class="upload-preview-header">
<span class="upload-counter">
<strong id="previewCount">0</strong>/50 fotos
</span>
<button type="button" class="btn btn-ghost btn-sm" id="btnLimpiarPreview">Limpiar todo</button>
</div>
<div class="upload-preview-grid" id="previewGrid"></div>
</div>
<div class="upload-summary" id="uploadSummary" style="display:none"></div>
<div class="form-error" id="uploadError" role="alert"></div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="btnSubirProductos" disabled>
<span class="btn-text">Subir fotos</span>
<span class="btn-spinner" style="display:none"></span>
</button>
</div>
</form>
</div>
<!-- Sub-sección: Lista de productos disponibles -->
<div class="view-subsection">
<div class="subsection-header">
<h2 class="subsection-title">📋 Productos disponibles</h2>
<span class="productos-counter" id="productosDisponiblesCount">0</span>
</div>
<div class="view-content-card">
<div class="productos-search">
<span class="productos-search-icon" aria-hidden="true">🔍</span>
<input type="text" id="productosSearch" placeholder="Buscar por referencia..." autocomplete="off" spellcheck="false">
<button type="button" class="productos-search-clear" id="productosSearchClear" style="display:none" aria-label="Limpiar búsqueda"></button>
</div>
<div class="productos-grid" id="productosListGrid">
<!-- Renderizado por JS -->
</div>
</div>
</div>
</section>
<!-- AGOTADOS VIEW -->
<section class="view" data-view="agotados">
<header class="view-header">
<h1 class="view-title">Agotados</h1>
<p class="view-subtitle">Productos que ya no están disponibles. Puedes reactivarlos o eliminarlos definitivamente.</p>
</header>
<div class="view-content-card">
<div class="agotados-grid" id="agotadosGrid">
<!-- Renderizado por JS -->
</div>
</div>
</section>
<!-- CATEGORÍAS VIEW -->
<section class="view" data-view="categorias">
<header class="view-header view-header-with-action">
<div>
<h1 class="view-title">Categorías</h1>
<p class="view-subtitle">Crea, edita o elimina las categorías del catálogo.</p>
</div>
<button type="button" class="btn btn-primary" id="btnCrearCategoria">
<span></span> Crear categoría
</button>
</header>
<div class="view-content-card">
<div class="categorias-grid" id="categoriasGrid">
<!-- Renderizado por JS -->
</div>
</div>
</section>
<!-- USUARIOS VIEW (visible si tiene permiso 'usuarios') -->
<section class="view" data-view="usuarios">
<header class="view-header">
<h1 class="view-title">Usuarios</h1>
<p class="view-subtitle">Gestiona los usuarios del panel.</p>
</header>
<!-- Sub-sección: Mi contraseña (solo admin) -->
<div class="view-subsection" id="miPasswordSubsection">
<h2 class="subsection-title">🔑 Mi contraseña</h2>
<div class="view-content-card">
<form id="formMiPassword" class="password-form" autocomplete="on">
<div class="form-group">
<label for="miPassActual">Contraseña actual</label>
<input type="password" id="miPassActual" required autocomplete="current-password">
</div>
<div class="form-group">
<label for="miPassNueva">Nueva contraseña</label>
<input type="password" id="miPassNueva" required minlength="6" autocomplete="new-password">
<span class="form-hint">Mínimo 6 caracteres</span>
</div>
<div class="form-group">
<label for="miPassConfirmar">Confirmar nueva contraseña</label>
<input type="password" id="miPassConfirmar" required minlength="6" autocomplete="new-password">
</div>
<div class="form-error" id="miPassError" role="alert"></div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Guardar cambios</button>
</div>
</form>
</div>
</div>
<!-- Sub-sección: Gestión de usuarios (admin o permiso 'usuarios') -->
<div class="view-subsection" id="gestionUsuariosSubsection">
<div class="subsection-header">
<h2 class="subsection-title">👥 Gestión de usuarios</h2>
<button type="button" class="btn btn-primary" id="btnCrearUsuario">
<span></span> Crear usuario
</button>
</div>
<div class="view-content-card">
<div class="users-table-wrap">
<table class="users-table">
<thead>
<tr>
<th>Usuario</th>
<th>Rol</th>
<th class="users-actions-col">Acciones</th>
</tr>
</thead>
<tbody id="usersListBody">
<!-- Renderizado por JS -->
</tbody>
</table>
</div>
</div>
</div>
</section>
</main>
</div>
<!-- MODAL: Crear usuario -->
<div class="modal" id="modalCrearUsuario" style="display:none">
<div class="modal-backdrop" data-close-modal="modalCrearUsuario"></div>
<div class="modal-content">
<div class="modal-header">
<h3>Crear usuario</h3>
<button class="modal-close" data-close-modal="modalCrearUsuario" type="button" aria-label="Cerrar"></button>
</div>
<form id="formCrearUsuario" class="modal-form">
<div class="form-group">
<label for="nuevoUsuario">Usuario</label>
<input type="text" id="nuevoUsuario" required autocomplete="off">
</div>
<div class="form-group">
<label for="nuevoPassword">Contraseña</label>
<input type="password" id="nuevoPassword" required minlength="6" autocomplete="new-password">
<span class="form-hint">Mínimo 6 caracteres</span>
</div>
<div class="form-group">
<label for="nuevoRol">Rol</label>
<select id="nuevoRol" required>
<option value="editor">Editor</option>
<option value="admin">Administrador</option>
</select>
</div>
<div class="form-group" id="permisosGroupCrear">
<label>Permisos de visibilidad</label>
<div class="permissions-grid" id="permisosCrear"></div>
<span class="form-hint">Marca las pestañas que este usuario podrá ver.</span>
</div>
<div class="permission-note" id="permisosNoteCrear" style="display:none">
<span>👑</span>
<span>Los administradores tienen todos los permisos automáticamente.</span>
</div>
<div class="form-error" id="crearUsuarioError" role="alert"></div>
<div class="modal-actions">
<button type="button" class="btn btn-ghost" data-close-modal="modalCrearUsuario">Cancelar</button>
<button type="submit" class="btn btn-primary">Crear</button>
</div>
</form>
</div>
</div>
<!-- MODAL: Editar usuario -->
<div class="modal" id="modalEditarUsuario" style="display:none">
<div class="modal-backdrop" data-close-modal="modalEditarUsuario"></div>
<div class="modal-content">
<div class="modal-header">
<h3>Editar usuario</h3>
<button class="modal-close" data-close-modal="modalEditarUsuario" type="button" aria-label="Cerrar"></button>
</div>
<form id="formEditarUsuario" class="modal-form">
<div class="form-group">
<label>Usuario</label>
<input type="text" id="editarUsuarioNombre" disabled>
</div>
<div class="form-group">
<label for="editarPassword">Nueva contraseña</label>
<input type="password" id="editarPassword" minlength="6" autocomplete="new-password">
<span class="form-hint">Déjala vacía para no cambiar</span>
</div>
<div class="form-group">
<label for="editarRol">Rol</label>
<select id="editarRol" required>
<option value="editor">Editor</option>
<option value="admin">Administrador</option>
</select>
</div>
<div class="form-group" id="permisosGroupEditar">
<label>Permisos de visibilidad</label>
<div class="permissions-grid" id="permisosEditar"></div>
<span class="form-hint">Marca las pestañas que este usuario podrá ver.</span>
</div>
<div class="permission-note" id="permisosNoteEditar" style="display:none">
<span>👑</span>
<span>Los administradores tienen todos los permisos automáticamente.</span>
</div>
<div class="form-error" id="editarUsuarioError" role="alert"></div>
<div class="modal-actions">
<button type="button" class="btn btn-ghost" data-close-modal="modalEditarUsuario">Cancelar</button>
<button type="submit" class="btn btn-primary">Guardar cambios</button>
</div>
</form>
</div>
</div>
<!-- MODAL: Confirmar eliminar -->
<div class="modal modal-sm" id="modalConfirmarEliminar" style="display:none">
<div class="modal-backdrop" data-close-modal="modalConfirmarEliminar"></div>
<div class="modal-content">
<div class="modal-header">
<h3>¿Eliminar usuario?</h3>
<button class="modal-close" data-close-modal="modalConfirmarEliminar" type="button" aria-label="Cerrar"></button>
</div>
<div class="modal-body">
<p>¿Estás seguro de que quieres eliminar al usuario <strong id="eliminarUsuarioNombre"></strong>?</p>
<p class="modal-warning">Esta acción no se puede deshacer.</p>
<div class="form-error" id="eliminarUsuarioError" role="alert"></div>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-ghost" data-close-modal="modalConfirmarEliminar">Cancelar</button>
<button type="button" class="btn btn-danger" id="btnConfirmarEliminar">Sí, eliminar</button>
</div>
</div>
</div>
<!-- MODAL: Crear/Editar categoría -->
<div class="modal" id="modalCategoria" style="display:none">
<div class="modal-backdrop" data-close-modal="modalCategoria"></div>
<div class="modal-content">
<div class="modal-header">
<h3 id="modalCategoriaTitulo">Crear categoría</h3>
<button class="modal-close" data-close-modal="modalCategoria" type="button" aria-label="Cerrar"></button>
</div>
<form id="formCategoria" class="modal-form">
<div class="form-group">
<label for="catNombre">Nombre</label>
<input type="text" id="catNombre" required maxlength="40" autocomplete="off">
<span class="form-hint" id="catIdHint"></span>
</div>
<div class="form-group">
<label>Icono</label>
<div class="icon-picker" id="iconPicker"></div>
<input type="hidden" id="catIcono" value="📦">
</div>
<div class="form-error" id="categoriaError" role="alert"></div>
<div class="modal-actions">
<button type="button" class="btn btn-ghost" data-close-modal="modalCategoria">Cancelar</button>
<button type="submit" class="btn btn-primary" id="btnGuardarCategoria">Crear</button>
</div>
</form>
</div>
</div>
<!-- MODAL: Confirmar eliminar categoría -->
<div class="modal modal-sm" id="modalEliminarCategoria" style="display:none">
<div class="modal-backdrop" data-close-modal="modalEliminarCategoria"></div>
<div class="modal-content">
<div class="modal-header">
<h3>¿Eliminar categoría?</h3>
<button class="modal-close" data-close-modal="modalEliminarCategoria" type="button" aria-label="Cerrar"></button>
</div>
<div class="modal-body">
<p>Vas a eliminar la categoría <strong id="eliminarCatNombre"></strong>.</p>
<div id="eliminarCatItemsMsg" class="alert-warning" style="display:none">
<span>⚠️</span>
<div>
<strong>Esta categoría tiene <span id="eliminarCatCount">0</span> item(s).</strong>
<p>Si continúas, los items se moverán a <strong>"Sin categoría"</strong> (no se borrarán).</p>
</div>
</div>
<p class="modal-warning">Esta acción no se puede deshacer.</p>
<div class="form-error" id="eliminarCatError" role="alert"></div>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-ghost" data-close-modal="modalEliminarCategoria">Cancelar</button>
<button type="button" class="btn btn-danger" id="btnConfirmarEliminarCategoria">Sí, eliminar</button>
</div>
</div>
</div>
<!-- MODAL: Confirmar eliminar producto definitivamente -->
<div class="modal modal-sm" id="modalEliminarProductoDef" style="display:none">
<div class="modal-backdrop" data-close-modal="modalEliminarProductoDef"></div>
<div class="modal-content">
<div class="modal-header">
<h3>¿Eliminar producto definitivamente?</h3>
<button class="modal-close" data-close-modal="modalEliminarProductoDef" type="button" aria-label="Cerrar"></button>
</div>
<div class="modal-body">
<p>Vas a eliminar el producto <strong id="eliminarProdRef"></strong> de forma permanente.</p>
<p>Su imagen y todos sus datos se borrarán. <strong>No se puede deshacer.</strong></p>
<div class="form-error" id="eliminarProdError" role="alert"></div>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-ghost" data-close-modal="modalEliminarProductoDef">Cancelar</button>
<button type="button" class="btn btn-danger" id="btnConfirmarEliminarProdDef">Sí, eliminar</button>
</div>
</div>
</div>
<script src="admin.js?v=4"></script>
</body>
</html>