Spaces:
Running
Running
Add 3 files
Browse files- README.md +7 -5
- index.html +766 -19
- prompts.txt +1 -0
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: test
|
| 3 |
+
emoji: 馃惓
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite
|
| 10 |
---
|
| 11 |
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
index.html
CHANGED
|
@@ -1,19 +1,766 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="es">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Sistema de Inventario</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
+
<style>
|
| 10 |
+
.sidebar {
|
| 11 |
+
transition: all 0.3s;
|
| 12 |
+
}
|
| 13 |
+
@media (max-width: 768px) {
|
| 14 |
+
.sidebar {
|
| 15 |
+
transform: translateX(-100%);
|
| 16 |
+
}
|
| 17 |
+
.sidebar.active {
|
| 18 |
+
transform: translateX(0);
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
.product-card:hover {
|
| 22 |
+
transform: translateY(-2px);
|
| 23 |
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
| 24 |
+
}
|
| 25 |
+
</style>
|
| 26 |
+
</head>
|
| 27 |
+
<body class="bg-gray-100 font-sans">
|
| 28 |
+
<!-- Login Screen -->
|
| 29 |
+
<div id="login-screen" class="fixed inset-0 flex items-center justify-center bg-gray-900 bg-opacity-75 z-50">
|
| 30 |
+
<div class="bg-white rounded-lg shadow-xl p-8 w-full max-w-md">
|
| 31 |
+
<div class="text-center mb-8">
|
| 32 |
+
<i class="fas fa-boxes text-4xl text-blue-600 mb-4"></i>
|
| 33 |
+
<h1 class="text-3xl font-bold text-gray-800">Sistema de Inventario</h1>
|
| 34 |
+
<p class="text-gray-600 mt-2">Ingrese sus credenciales para acceder</p>
|
| 35 |
+
</div>
|
| 36 |
+
<form id="login-form" class="space-y-6">
|
| 37 |
+
<div>
|
| 38 |
+
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">Usuario</label>
|
| 39 |
+
<input type="text" id="username" name="username" required
|
| 40 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
| 41 |
+
</div>
|
| 42 |
+
<div>
|
| 43 |
+
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Contrase帽a</label>
|
| 44 |
+
<input type="password" id="password" name="password" required
|
| 45 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
| 46 |
+
</div>
|
| 47 |
+
<div>
|
| 48 |
+
<button type="submit"
|
| 49 |
+
class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition duration-300 flex items-center justify-center">
|
| 50 |
+
<i class="fas fa-sign-in-alt mr-2"></i> Iniciar Sesi贸n
|
| 51 |
+
</button>
|
| 52 |
+
</div>
|
| 53 |
+
<div class="text-center text-sm text-gray-500">
|
| 54 |
+
<p>Usuario demo: admin / pass123 (rol admin)</p>
|
| 55 |
+
<p>Usuario demo: user / pass123 (rol usuario)</p>
|
| 56 |
+
</div>
|
| 57 |
+
</form>
|
| 58 |
+
</div>
|
| 59 |
+
</div>
|
| 60 |
+
|
| 61 |
+
<!-- Main App (hidden until login) -->
|
| 62 |
+
<div id="app-container" class="hidden">
|
| 63 |
+
<!-- Sidebar -->
|
| 64 |
+
<div class="sidebar fixed inset-y-0 left-0 bg-gray-800 text-white w-64 z-40">
|
| 65 |
+
<div class="p-4 flex items-center border-b border-gray-700">
|
| 66 |
+
<i class="fas fa-boxes text-2xl text-blue-400 mr-3"></i>
|
| 67 |
+
<h1 class="text-xl font-bold">Inventario</h1>
|
| 68 |
+
<button id="close-sidebar" class="ml-auto md:hidden">
|
| 69 |
+
<i class="fas fa-times"></i>
|
| 70 |
+
</button>
|
| 71 |
+
</div>
|
| 72 |
+
<nav class="p-4">
|
| 73 |
+
<div class="mb-8">
|
| 74 |
+
<p class="text-gray-400 uppercase text-xs font-bold mb-4">Men煤 Principal</p>
|
| 75 |
+
<a href="#" class="flex items-center py-2 px-3 bg-gray-700 rounded-md text-white mb-2">
|
| 76 |
+
<i class="fas fa-tachometer-alt mr-3"></i> Dashboard
|
| 77 |
+
</a>
|
| 78 |
+
<a href="#" id="nav-products" class="flex items-center py-2 px-3 hover:bg-gray-700 rounded-md text-gray-300 hover:text-white mb-2">
|
| 79 |
+
<i class="fas fa-boxes mr-3"></i> Productos
|
| 80 |
+
</a>
|
| 81 |
+
<a href="#" id="nav-add-product" class="flex items-center py-2 px-3 hover:bg-gray-700 rounded-md text-gray-300 hover:text-white mb-2">
|
| 82 |
+
<i class="fas fa-plus-circle mr-3"></i> Agregar Producto
|
| 83 |
+
</a>
|
| 84 |
+
</div>
|
| 85 |
+
<div>
|
| 86 |
+
<p class="text-gray-400 uppercase text-xs font-bold mb-4">Usuario</p>
|
| 87 |
+
<a href="#" id="logout-btn" class="flex items-center py-2 px-3 hover:bg-gray-700 rounded-md text-gray-300 hover:text-white">
|
| 88 |
+
<i class="fas fa-sign-out-alt mr-3"></i> Cerrar Sesi贸n
|
| 89 |
+
</a>
|
| 90 |
+
</div>
|
| 91 |
+
</nav>
|
| 92 |
+
</div>
|
| 93 |
+
|
| 94 |
+
<!-- Main Content -->
|
| 95 |
+
<div class="ml-0 md:ml-64 transition-all duration-300">
|
| 96 |
+
<!-- Header -->
|
| 97 |
+
<header class="bg-white shadow-sm py-4 px-6 flex items-center justify-between sticky top-0 z-30">
|
| 98 |
+
<button id="menu-toggle" class="md:hidden text-gray-600">
|
| 99 |
+
<i class="fas fa-bars text-xl"></i>
|
| 100 |
+
</button>
|
| 101 |
+
<div class="flex items-center">
|
| 102 |
+
<div class="mr-4">
|
| 103 |
+
<span id="user-role-badge" class="px-3 py-1 rounded-full text-xs font-semibold"></span>
|
| 104 |
+
</div>
|
| 105 |
+
<div class="relative">
|
| 106 |
+
<button id="user-menu-btn" class="flex items-center focus:outline-none">
|
| 107 |
+
<span id="username-display" class="mr-2 font-medium"></span>
|
| 108 |
+
<i class="fas fa-user-circle text-2xl text-gray-600"></i>
|
| 109 |
+
</button>
|
| 110 |
+
</div>
|
| 111 |
+
</div>
|
| 112 |
+
</header>
|
| 113 |
+
|
| 114 |
+
<!-- Dashboard Content -->
|
| 115 |
+
<main class="p-6">
|
| 116 |
+
<!-- Dashboard Overview -->
|
| 117 |
+
<div id="dashboard-view">
|
| 118 |
+
<div class="mb-8">
|
| 119 |
+
<h2 class="text-2xl font-bold text-gray-800 mb-6">Resumen del Inventario</h2>
|
| 120 |
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
| 121 |
+
<div class="bg-white rounded-lg shadow p-6">
|
| 122 |
+
<div class="flex items-center">
|
| 123 |
+
<div class="p-3 rounded-full bg-blue-100 text-blue-600 mr-4">
|
| 124 |
+
<i class="fas fa-boxes text-xl"></i>
|
| 125 |
+
</div>
|
| 126 |
+
<div>
|
| 127 |
+
<p class="text-gray-500 text-sm">Productos totales</p>
|
| 128 |
+
<h3 class="text-2xl font-bold" id="total-products">0</h3>
|
| 129 |
+
</div>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
<div class="bg-white rounded-lg shadow p-6">
|
| 133 |
+
<div class="flex items-center">
|
| 134 |
+
<div class="p-3 rounded-full bg-green-100 text-green-600 mr-4">
|
| 135 |
+
<i class="fas fa-box-open text-xl"></i>
|
| 136 |
+
</div>
|
| 137 |
+
<div>
|
| 138 |
+
<p class="text-gray-500 text-sm">Stock disponible</p>
|
| 139 |
+
<h3 class="text-2xl font-bold" id="total-stock">0</h3>
|
| 140 |
+
</div>
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
<div class="bg-white rounded-lg shadow p-6">
|
| 144 |
+
<div class="flex items-center">
|
| 145 |
+
<div class="p-3 rounded-full bg-purple-100 text-purple-600 mr-4">
|
| 146 |
+
<i class="fas fa-tags text-xl"></i>
|
| 147 |
+
</div>
|
| 148 |
+
<div>
|
| 149 |
+
<p class="text-gray-500 text-sm">Valor total</p>
|
| 150 |
+
<h3 class="text-2xl font-bold" id="total-value">$0</h3>
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
</div>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
<div>
|
| 157 |
+
<h2 class="text-2xl font-bold text-gray-800 mb-6">Productos con bajo stock</h2>
|
| 158 |
+
<div class="bg-white rounded-lg shadow overflow-hidden">
|
| 159 |
+
<div id="low-stock-products" class="divide-y divide-gray-200">
|
| 160 |
+
<!-- Low stock products will be loaded here -->
|
| 161 |
+
<div class="p-4 text-center text-gray-500">
|
| 162 |
+
<i class="fas fa-spinner fa-spin mr-2"></i> Cargando productos...
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
|
| 169 |
+
<!-- Products List View -->
|
| 170 |
+
<div id="products-view" class="hidden">
|
| 171 |
+
<div class="flex justify-between items-center mb-6">
|
| 172 |
+
<h2 class="text-2xl font-bold text-gray-800">Lista de Productos</h2>
|
| 173 |
+
<button id="add-product-btn" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition duration-300 flex items-center">
|
| 174 |
+
<i class="fas fa-plus mr-2"></i> Agregar Producto
|
| 175 |
+
</button>
|
| 176 |
+
</div>
|
| 177 |
+
<div class="bg-white rounded-lg shadow overflow-hidden">
|
| 178 |
+
<div class="overflow-x-auto">
|
| 179 |
+
<table class="min-w-full divide-y divide-gray-200">
|
| 180 |
+
<thead class="bg-gray-50">
|
| 181 |
+
<tr>
|
| 182 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nombre</th>
|
| 183 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Categor铆a</th>
|
| 184 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Precio</th>
|
| 185 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cantidad</th>
|
| 186 |
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Acciones</th>
|
| 187 |
+
</tr>
|
| 188 |
+
</thead>
|
| 189 |
+
<tbody id="products-table-body" class="bg-white divide-y divide-gray-200">
|
| 190 |
+
<!-- Products will be loaded here -->
|
| 191 |
+
<tr>
|
| 192 |
+
<td colspan="5" class="px-6 py-4 text-center text-gray-500">
|
| 193 |
+
<i class="fas fa-spinner fa-spin mr-2"></i> Cargando productos...
|
| 194 |
+
</td>
|
| 195 |
+
</tr>
|
| 196 |
+
</tbody>
|
| 197 |
+
</table>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
|
| 202 |
+
<!-- Add/Edit Product Form -->
|
| 203 |
+
<div id="product-form-view" class="hidden">
|
| 204 |
+
<div class="mb-6">
|
| 205 |
+
<h2 class="text-2xl font-bold text-gray-800" id="form-title">Agregar Nuevo Producto</h2>
|
| 206 |
+
</div>
|
| 207 |
+
<div class="bg-white rounded-lg shadow p-6">
|
| 208 |
+
<form id="product-form" class="space-y-6">
|
| 209 |
+
<input type="hidden" id="product-id">
|
| 210 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
| 211 |
+
<div>
|
| 212 |
+
<label for="product-name" class="block text-sm font-medium text-gray-700 mb-1">Nombre del Producto *</label>
|
| 213 |
+
<input type="text" id="product-name" name="product-name" required
|
| 214 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
| 215 |
+
</div>
|
| 216 |
+
<div>
|
| 217 |
+
<label for="product-category" class="block text-sm font-medium text-gray-700 mb-1">Categor铆a *</label>
|
| 218 |
+
<select id="product-category" name="product-category" required
|
| 219 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
| 220 |
+
<option value="">Seleccione una categor铆a</option>
|
| 221 |
+
<option value="Electr贸nicos">Electr贸nicos</option>
|
| 222 |
+
<option value="Ropa">Ropa</option>
|
| 223 |
+
<option value="Alimentos">Alimentos</option>
|
| 224 |
+
<option value="Hogar">Hogar</option>
|
| 225 |
+
<option value="Oficina">Oficina</option>
|
| 226 |
+
</select>
|
| 227 |
+
</div>
|
| 228 |
+
<div>
|
| 229 |
+
<label for="product-price" class="block text-sm font-medium text-gray-700 mb-1">Precio *</label>
|
| 230 |
+
<div class="relative">
|
| 231 |
+
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">$</span>
|
| 232 |
+
<input type="number" id="product-price" name="product-price" step="0.01" min="0" required
|
| 233 |
+
class="w-full pl-8 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
| 234 |
+
</div>
|
| 235 |
+
</div>
|
| 236 |
+
<div>
|
| 237 |
+
<label for="product-quantity" class="block text-sm font-medium text-gray-700 mb-1">Cantidad *</label>
|
| 238 |
+
<input type="number" id="product-quantity" name="product-quantity" min="0" required
|
| 239 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
| 240 |
+
</div>
|
| 241 |
+
</div>
|
| 242 |
+
<div>
|
| 243 |
+
<label for="product-description" class="block text-sm font-medium text-gray-700 mb-1">Descripci贸n</label>
|
| 244 |
+
<textarea id="product-description" name="product-description" rows="3"
|
| 245 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
| 246 |
+
</div>
|
| 247 |
+
<div class="flex justify-end space-x-4">
|
| 248 |
+
<button type="button" id="cancel-form-btn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition duration-300">
|
| 249 |
+
Cancelar
|
| 250 |
+
</button>
|
| 251 |
+
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition duration-300">
|
| 252 |
+
Guardar Producto
|
| 253 |
+
</button>
|
| 254 |
+
</div>
|
| 255 |
+
</form>
|
| 256 |
+
</div>
|
| 257 |
+
</div>
|
| 258 |
+
</main>
|
| 259 |
+
</div>
|
| 260 |
+
|
| 261 |
+
<!-- Edit Product Modal -->
|
| 262 |
+
<div id="edit-product-modal" class="fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 hidden">
|
| 263 |
+
<div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-md">
|
| 264 |
+
<div class="flex justify-between items-center mb-4">
|
| 265 |
+
<h3 class="text-xl font-bold text-gray-800">Editar Producto</h3>
|
| 266 |
+
<button id="close-edit-modal" class="text-gray-500 hover:text-gray-700">
|
| 267 |
+
<i class="fas fa-times"></i>
|
| 268 |
+
</button>
|
| 269 |
+
</div>
|
| 270 |
+
<form id="edit-product-form" class="space-y-4">
|
| 271 |
+
<input type="hidden" id="edit-product-id">
|
| 272 |
+
<div>
|
| 273 |
+
<label for="edit-product-name" class="block text-sm font-medium text-gray-700 mb-1">Nombre</label>
|
| 274 |
+
<input type="text" id="edit-product-name" class="w-full px-4 py-2 border border-gray-300 rounded-md">
|
| 275 |
+
</div>
|
| 276 |
+
<div>
|
| 277 |
+
<label for="edit-product-price" class="block text-sm font-medium text-gray-700 mb-1">Precio</label>
|
| 278 |
+
<input type="number" id="edit-product-price" step="0.01" class="w-full px-4 py-2 border border-gray-300 rounded-md">
|
| 279 |
+
</div>
|
| 280 |
+
<div>
|
| 281 |
+
<label for="edit-product-quantity" class="block text-sm font-medium text-gray-700 mb-1">Cantidad</label>
|
| 282 |
+
<input type="number" id="edit-product-quantity" class="w-full px-4 py-2 border border-gray-300 rounded-md">
|
| 283 |
+
</div>
|
| 284 |
+
<div class="flex justify-end space-x-3 pt-4">
|
| 285 |
+
<button type="button" id="cancel-edit-btn" class="px-4 py-2 border border-gray-300 rounded-md">
|
| 286 |
+
Cancelar
|
| 287 |
+
</button>
|
| 288 |
+
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded-md">
|
| 289 |
+
Guardar Cambios
|
| 290 |
+
</button>
|
| 291 |
+
</div>
|
| 292 |
+
</form>
|
| 293 |
+
</div>
|
| 294 |
+
</div>
|
| 295 |
+
|
| 296 |
+
<!-- Delete Confirmation Modal -->
|
| 297 |
+
<div id="delete-modal" class="fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center z-50 hidden">
|
| 298 |
+
<div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-md">
|
| 299 |
+
<div class="flex justify-between items-center mb-4">
|
| 300 |
+
<h3 class="text-xl font-bold text-gray-800">Confirmar Eliminaci贸n</h3>
|
| 301 |
+
<button id="close-delete-modal" class="text-gray-500 hover:text-gray-700">
|
| 302 |
+
<i class="fas fa-times"></i>
|
| 303 |
+
</button>
|
| 304 |
+
</div>
|
| 305 |
+
<p class="text-gray-700 mb-6">驴Est谩s seguro de que deseas eliminar este producto? Esta acci贸n no se puede deshacer.</p>
|
| 306 |
+
<div class="flex justify-end space-x-3">
|
| 307 |
+
<button type="button" id="cancel-delete-btn" class="px-4 py-2 border border-gray-300 rounded-md">
|
| 308 |
+
Cancelar
|
| 309 |
+
</button>
|
| 310 |
+
<button type="button" id="confirm-delete-btn" class="bg-red-600 text-white px-4 py-2 rounded-md">
|
| 311 |
+
Eliminar
|
| 312 |
+
</button>
|
| 313 |
+
</div>
|
| 314 |
+
</div>
|
| 315 |
+
</div>
|
| 316 |
+
</div>
|
| 317 |
+
|
| 318 |
+
<script>
|
| 319 |
+
// Mock database
|
| 320 |
+
const mockDatabase = {
|
| 321 |
+
users: [
|
| 322 |
+
{ id: 1, username: 'admin', password: 'pass123', role: 'admin', name: 'Administrador' },
|
| 323 |
+
{ id: 2, username: 'user', password: 'pass123', role: 'user', name: 'Usuario Regular' }
|
| 324 |
+
],
|
| 325 |
+
products: [
|
| 326 |
+
{ id: 1, name: 'Laptop HP', category: 'Electr贸nicos', price: 1200, quantity: 15, description: 'Laptop HP con 16GB RAM y 512GB SSD' },
|
| 327 |
+
{ id: 2, name: 'Smartphone Samsung', category: 'Electr贸nicos', price: 800, quantity: 25, description: 'Smartphone Samsung Galaxy S21' },
|
| 328 |
+
{ id: 3, name: 'Camisa de algod贸n', category: 'Ropa', price: 25, quantity: 50, description: 'Camisa 100% algod贸n talla M' },
|
| 329 |
+
{ id: 4, name: 'Arroz 5kg', category: 'Alimentos', price: 10, quantity: 40, description: 'Arroz blanco grano largo 5kg' },
|
| 330 |
+
{ id: 5, name: 'Silla de oficina', category: 'Oficina', price: 150, quantity: 10, description: 'Silla ergon贸mica para oficina' },
|
| 331 |
+
{ id: 6, name: 'Monitor 24"', category: 'Electr贸nicos', price: 180, quantity: 2, description: 'Monitor LED 24 pulgadas Full HD' },
|
| 332 |
+
{ id: 7, name: 'Juego de s谩banas', category: 'Hogar', price: 45, quantity: 15, description: 'Juego de s谩banas de algod贸n king size' }
|
| 333 |
+
]
|
| 334 |
+
};
|
| 335 |
+
|
| 336 |
+
// App state
|
| 337 |
+
const state = {
|
| 338 |
+
currentUser: null,
|
| 339 |
+
currentView: 'dashboard',
|
| 340 |
+
products: [],
|
| 341 |
+
productToDelete: null,
|
| 342 |
+
productToEdit: null
|
| 343 |
+
};
|
| 344 |
+
|
| 345 |
+
// DOM Elements
|
| 346 |
+
const loginScreen = document.getElementById('login-screen');
|
| 347 |
+
const appContainer = document.getElementById('app-container');
|
| 348 |
+
const loginForm = document.getElementById('login-form');
|
| 349 |
+
const usernameInput = document.getElementById('username');
|
| 350 |
+
const passwordInput = document.getElementById('password');
|
| 351 |
+
const logoutBtn = document.getElementById('logout-btn');
|
| 352 |
+
const usernameDisplay = document.getElementById('username-display');
|
| 353 |
+
const userRoleBadge = document.getElementById('user-role-badge');
|
| 354 |
+
const menuToggle = document.getElementById('menu-toggle');
|
| 355 |
+
const closeSidebar = document.getElementById('close-sidebar');
|
| 356 |
+
const sidebar = document.querySelector('.sidebar');
|
| 357 |
+
const navProducts = document.getElementById('nav-products');
|
| 358 |
+
const navAddProduct = document.getElementById('nav-add-product');
|
| 359 |
+
const dashboardView = document.getElementById('dashboard-view');
|
| 360 |
+
const productsView = document.getElementById('products-view');
|
| 361 |
+
const productFormView = document.getElementById('product-form-view');
|
| 362 |
+
const productsTableBody = document.getElementById('products-table-body');
|
| 363 |
+
const addProductBtn = document.getElementById('add-product-btn');
|
| 364 |
+
const productForm = document.getElementById('product-form');
|
| 365 |
+
const productIdInput = document.getElementById('product-id');
|
| 366 |
+
const productNameInput = document.getElementById('product-name');
|
| 367 |
+
const productCategoryInput = document.getElementById('product-category');
|
| 368 |
+
const productPriceInput = document.getElementById('product-price');
|
| 369 |
+
const productQuantityInput = document.getElementById('product-quantity');
|
| 370 |
+
const productDescriptionInput = document.getElementById('product-description');
|
| 371 |
+
const cancelFormBtn = document.getElementById('cancel-form-btn');
|
| 372 |
+
const formTitle = document.getElementById('form-title');
|
| 373 |
+
const totalProducts = document.getElementById('total-products');
|
| 374 |
+
const totalStock = document.getElementById('total-stock');
|
| 375 |
+
const totalValue = document.getElementById('total-value');
|
| 376 |
+
const lowStockProducts = document.getElementById('low-stock-products');
|
| 377 |
+
const editProductModal = document.getElementById('edit-product-modal');
|
| 378 |
+
const closeEditModal = document.getElementById('close-edit-modal');
|
| 379 |
+
const cancelEditBtn = document.getElementById('cancel-edit-btn');
|
| 380 |
+
const editProductForm = document.getElementById('edit-product-form');
|
| 381 |
+
const editProductId = document.getElementById('edit-product-id');
|
| 382 |
+
const editProductName = document.getElementById('edit-product-name');
|
| 383 |
+
const editProductPrice = document.getElementById('edit-product-price');
|
| 384 |
+
const editProductQuantity = document.getElementById('edit-product-quantity');
|
| 385 |
+
const deleteModal = document.getElementById('delete-modal');
|
| 386 |
+
const closeDeleteModal = document.getElementById('close-delete-modal');
|
| 387 |
+
const cancelDeleteBtn = document.getElementById('cancel-delete-btn');
|
| 388 |
+
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');
|
| 389 |
+
|
| 390 |
+
// Event Listeners
|
| 391 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 392 |
+
// Initialize mock data
|
| 393 |
+
state.products = [...mockDatabase.products];
|
| 394 |
+
|
| 395 |
+
// Check if user is already logged in (for demo purposes)
|
| 396 |
+
const loggedInUser = localStorage.getItem('inventoryUser');
|
| 397 |
+
if (loggedInUser) {
|
| 398 |
+
state.currentUser = JSON.parse(loggedInUser);
|
| 399 |
+
handleSuccessfulLogin();
|
| 400 |
+
}
|
| 401 |
+
});
|
| 402 |
+
|
| 403 |
+
loginForm.addEventListener('submit', (e) => {
|
| 404 |
+
e.preventDefault();
|
| 405 |
+
const username = usernameInput.value.trim();
|
| 406 |
+
const password = passwordInput.value.trim();
|
| 407 |
+
|
| 408 |
+
// Validate credentials
|
| 409 |
+
const user = mockDatabase.users.find(u => u.username === username && u.password === password);
|
| 410 |
+
|
| 411 |
+
if (user) {
|
| 412 |
+
state.currentUser = user;
|
| 413 |
+
localStorage.setItem('inventoryUser', JSON.stringify(user));
|
| 414 |
+
handleSuccessfulLogin();
|
| 415 |
+
} else {
|
| 416 |
+
alert('Credenciales incorrectas. Intente nuevamente.');
|
| 417 |
+
}
|
| 418 |
+
});
|
| 419 |
+
|
| 420 |
+
logoutBtn.addEventListener('click', () => {
|
| 421 |
+
state.currentUser = null;
|
| 422 |
+
localStorage.removeItem('inventoryUser');
|
| 423 |
+
loginScreen.classList.remove('hidden');
|
| 424 |
+
appContainer.classList.add('hidden');
|
| 425 |
+
usernameInput.value = '';
|
| 426 |
+
passwordInput.value = '';
|
| 427 |
+
});
|
| 428 |
+
|
| 429 |
+
menuToggle.addEventListener('click', () => {
|
| 430 |
+
sidebar.classList.add('active');
|
| 431 |
+
});
|
| 432 |
+
|
| 433 |
+
closeSidebar.addEventListener('click', () => {
|
| 434 |
+
sidebar.classList.remove('active');
|
| 435 |
+
});
|
| 436 |
+
|
| 437 |
+
navProducts.addEventListener('click', (e) => {
|
| 438 |
+
e.preventDefault();
|
| 439 |
+
showProductsView();
|
| 440 |
+
});
|
| 441 |
+
|
| 442 |
+
navAddProduct.addEventListener('click', (e) => {
|
| 443 |
+
e.preventDefault();
|
| 444 |
+
showAddProductForm();
|
| 445 |
+
});
|
| 446 |
+
|
| 447 |
+
addProductBtn.addEventListener('click', () => {
|
| 448 |
+
showAddProductForm();
|
| 449 |
+
});
|
| 450 |
+
|
| 451 |
+
productForm.addEventListener('submit', (e) => {
|
| 452 |
+
e.preventDefault();
|
| 453 |
+
|
| 454 |
+
const productData = {
|
| 455 |
+
name: productNameInput.value.trim(),
|
| 456 |
+
category: productCategoryInput.value,
|
| 457 |
+
price: parseFloat(productPriceInput.value),
|
| 458 |
+
quantity: parseInt(productQuantityInput.value),
|
| 459 |
+
description: productDescriptionInput.value.trim()
|
| 460 |
+
};
|
| 461 |
+
|
| 462 |
+
if (productIdInput.value) {
|
| 463 |
+
// Edit existing product
|
| 464 |
+
const productId = parseInt(productIdInput.value);
|
| 465 |
+
const productIndex = state.products.findIndex(p => p.id === productId);
|
| 466 |
+
|
| 467 |
+
if (productIndex !== -1) {
|
| 468 |
+
state.products[productIndex] = {
|
| 469 |
+
...state.products[productIndex],
|
| 470 |
+
...productData
|
| 471 |
+
};
|
| 472 |
+
|
| 473 |
+
alert('Producto actualizado correctamente');
|
| 474 |
+
}
|
| 475 |
+
} else {
|
| 476 |
+
// Add new product
|
| 477 |
+
const newProduct = {
|
| 478 |
+
id: state.products.length > 0 ? Math.max(...state.products.map(p => p.id)) + 1 : 1,
|
| 479 |
+
...productData
|
| 480 |
+
};
|
| 481 |
+
|
| 482 |
+
state.products.push(newProduct);
|
| 483 |
+
alert('Producto agregado correctamente');
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
// Reset form and show products view
|
| 487 |
+
productForm.reset();
|
| 488 |
+
showProductsView();
|
| 489 |
+
updateDashboardStats();
|
| 490 |
+
});
|
| 491 |
+
|
| 492 |
+
cancelFormBtn.addEventListener('click', () => {
|
| 493 |
+
productForm.reset();
|
| 494 |
+
showProductsView();
|
| 495 |
+
});
|
| 496 |
+
|
| 497 |
+
// Handle edit product modal
|
| 498 |
+
document.addEventListener('click', (e) => {
|
| 499 |
+
if (e.target.classList.contains('edit-product-btn')) {
|
| 500 |
+
const productId = parseInt(e.target.dataset.id);
|
| 501 |
+
const product = state.products.find(p => p.id === productId);
|
| 502 |
+
|
| 503 |
+
if (product) {
|
| 504 |
+
state.productToEdit = product;
|
| 505 |
+
showEditProductModal(product);
|
| 506 |
+
}
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
if (e.target.classList.contains('delete-product-btn')) {
|
| 510 |
+
const productId = parseInt(e.target.dataset.id);
|
| 511 |
+
const product = state.products.find(p => p.id === productId);
|
| 512 |
+
|
| 513 |
+
if (product) {
|
| 514 |
+
state.productToDelete = product;
|
| 515 |
+
showDeleteModal();
|
| 516 |
+
}
|
| 517 |
+
}
|
| 518 |
+
});
|
| 519 |
+
|
| 520 |
+
editProductForm.addEventListener('submit', (e) => {
|
| 521 |
+
e.preventDefault();
|
| 522 |
+
|
| 523 |
+
if (state.productToEdit) {
|
| 524 |
+
const productIndex = state.products.findIndex(p => p.id === state.productToEdit.id);
|
| 525 |
+
|
| 526 |
+
if (productIndex !== -1) {
|
| 527 |
+
state.products[productIndex] = {
|
| 528 |
+
...state.products[productIndex],
|
| 529 |
+
name: editProductName.value.trim(),
|
| 530 |
+
price: parseFloat(editProductPrice.value),
|
| 531 |
+
quantity: parseInt(editProductQuantity.value)
|
| 532 |
+
};
|
| 533 |
+
|
| 534 |
+
alert('Producto actualizado correctamente');
|
| 535 |
+
hideEditProductModal();
|
| 536 |
+
showProductsView();
|
| 537 |
+
updateDashboardStats();
|
| 538 |
+
}
|
| 539 |
+
}
|
| 540 |
+
});
|
| 541 |
+
|
| 542 |
+
closeEditModal.addEventListener('click', hideEditProductModal);
|
| 543 |
+
cancelEditBtn.addEventListener('click', hideEditProductModal);
|
| 544 |
+
|
| 545 |
+
confirmDeleteBtn.addEventListener('click', () => {
|
| 546 |
+
if (state.productToDelete) {
|
| 547 |
+
state.products = state.products.filter(p => p.id !== state.productToDelete.id);
|
| 548 |
+
alert('Producto eliminado correctamente');
|
| 549 |
+
hideDeleteModal();
|
| 550 |
+
showProductsView();
|
| 551 |
+
updateDashboardStats();
|
| 552 |
+
}
|
| 553 |
+
});
|
| 554 |
+
|
| 555 |
+
closeDeleteModal.addEventListener('click', hideDeleteModal);
|
| 556 |
+
cancelDeleteBtn.addEventListener('click', hideDeleteModal);
|
| 557 |
+
|
| 558 |
+
// Functions
|
| 559 |
+
function handleSuccessfulLogin() {
|
| 560 |
+
loginScreen.classList.add('hidden');
|
| 561 |
+
appContainer.classList.remove('hidden');
|
| 562 |
+
|
| 563 |
+
// Update UI with user info
|
| 564 |
+
usernameDisplay.textContent = state.currentUser.name;
|
| 565 |
+
|
| 566 |
+
if (state.currentUser.role === 'admin') {
|
| 567 |
+
userRoleBadge.textContent = 'Administrador';
|
| 568 |
+
userRoleBadge.classList.add('bg-purple-100', 'text-purple-800');
|
| 569 |
+
} else {
|
| 570 |
+
userRoleBadge.textContent = 'Usuario';
|
| 571 |
+
userRoleBadge.classList.add('bg-blue-100', 'text-blue-800');
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
// Load initial view
|
| 575 |
+
showDashboardView();
|
| 576 |
+
updateDashboardStats();
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
function showDashboardView() {
|
| 580 |
+
state.currentView = 'dashboard';
|
| 581 |
+
dashboardView.classList.remove('hidden');
|
| 582 |
+
productsView.classList.add('hidden');
|
| 583 |
+
productFormView.classList.add('hidden');
|
| 584 |
+
|
| 585 |
+
// Update active nav item
|
| 586 |
+
document.querySelectorAll('nav a').forEach(link => {
|
| 587 |
+
link.classList.remove('bg-gray-700', 'text-white');
|
| 588 |
+
link.classList.add('text-gray-300', 'hover:bg-gray-700', 'hover:text-white');
|
| 589 |
+
});
|
| 590 |
+
|
| 591 |
+
document.querySelector('nav a:first-child').classList.add('bg-gray-700', 'text-white');
|
| 592 |
+
document.querySelector('nav a:first-child').classList.remove('text-gray-300', 'hover:bg-gray-700', 'hover:text-white');
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
function showProductsView() {
|
| 596 |
+
state.currentView = 'products';
|
| 597 |
+
dashboardView.classList.add('hidden');
|
| 598 |
+
productsView.classList.remove('hidden');
|
| 599 |
+
productFormView.classList.add('hidden');
|
| 600 |
+
|
| 601 |
+
// Update active nav item
|
| 602 |
+
document.querySelectorAll('nav a').forEach(link => {
|
| 603 |
+
link.classList.remove('bg-gray-700', 'text-white');
|
| 604 |
+
link.classList.add('text-gray-300', 'hover:bg-gray-700', 'hover:text-white');
|
| 605 |
+
});
|
| 606 |
+
|
| 607 |
+
navProducts.classList.add('bg-gray-700', 'text-white');
|
| 608 |
+
navProducts.classList.remove('text-gray-300', 'hover:bg-gray-700', 'hover:text-white');
|
| 609 |
+
|
| 610 |
+
// Render products table
|
| 611 |
+
renderProductsTable();
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
function showAddProductForm() {
|
| 615 |
+
state.currentView = 'add-product';
|
| 616 |
+
dashboardView.classList.add('hidden');
|
| 617 |
+
productsView.classList.add('hidden');
|
| 618 |
+
productFormView.classList.remove('hidden');
|
| 619 |
+
|
| 620 |
+
// Update form title and reset
|
| 621 |
+
formTitle.textContent = 'Agregar Nuevo Producto';
|
| 622 |
+
productForm.reset();
|
| 623 |
+
productIdInput.value = '';
|
| 624 |
+
|
| 625 |
+
// Update active nav item
|
| 626 |
+
document.querySelectorAll('nav a').forEach(link => {
|
| 627 |
+
link.classList.remove('bg-gray-700', 'text-white');
|
| 628 |
+
link.classList.add('text-gray-300', 'hover:bg-gray-700', 'hover:text-white');
|
| 629 |
+
});
|
| 630 |
+
|
| 631 |
+
navAddProduct.classList.add('bg-gray-700', 'text-white');
|
| 632 |
+
navAddProduct.classList.remove('text-gray-300', 'hover:bg-gray-700', 'hover:text-white');
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
function showEditProductForm(product) {
|
| 636 |
+
state.currentView = 'edit-product';
|
| 637 |
+
dashboardView.classList.add('hidden');
|
| 638 |
+
productsView.classList.add('hidden');
|
| 639 |
+
productFormView.classList.remove('hidden');
|
| 640 |
+
|
| 641 |
+
// Update form title and fill with product data
|
| 642 |
+
formTitle.textContent = 'Editar Producto';
|
| 643 |
+
productIdInput.value = product.id;
|
| 644 |
+
productNameInput.value = product.name;
|
| 645 |
+
productCategoryInput.value = product.category;
|
| 646 |
+
productPriceInput.value = product.price;
|
| 647 |
+
productQuantityInput.value = product.quantity;
|
| 648 |
+
productDescriptionInput.value = product.description || '';
|
| 649 |
+
}
|
| 650 |
+
|
| 651 |
+
function renderProductsTable() {
|
| 652 |
+
if (state.products.length === 0) {
|
| 653 |
+
productsTableBody.innerHTML = `
|
| 654 |
+
<tr>
|
| 655 |
+
<td colspan="5" class="px-6 py-4 text-center text-gray-500">
|
| 656 |
+
No hay productos registrados
|
| 657 |
+
</td>
|
| 658 |
+
</tr>
|
| 659 |
+
`;
|
| 660 |
+
return;
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
let html = '';
|
| 664 |
+
|
| 665 |
+
state.products.forEach(product => {
|
| 666 |
+
const lowStockClass = product.quantity < 5 ? 'text-red-600 font-semibold' : '';
|
| 667 |
+
|
| 668 |
+
html += `
|
| 669 |
+
<tr class="hover:bg-gray-50">
|
| 670 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 671 |
+
<div class="flex items-center">
|
| 672 |
+
<div class="text-sm font-medium text-gray-900">${product.name}</div>
|
| 673 |
+
</div>
|
| 674 |
+
</td>
|
| 675 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 676 |
+
<div class="text-sm text-gray-500">${product.category}</div>
|
| 677 |
+
</td>
|
| 678 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 679 |
+
<div class="text-sm text-gray-900">$${product.price.toFixed(2)}</div>
|
| 680 |
+
</td>
|
| 681 |
+
<td class="px-6 py-4 whitespace-nowrap">
|
| 682 |
+
<div class="text-sm ${lowStockClass}">${product.quantity}</div>
|
| 683 |
+
</td>
|
| 684 |
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
| 685 |
+
${state.currentUser.role === 'admin' ? `
|
| 686 |
+
<button class="edit-product-btn text-blue-600 hover:text-blue-900 mr-4" data-id="${product.id}">
|
| 687 |
+
<i class="fas fa-edit"></i>
|
| 688 |
+
</button>
|
| 689 |
+
<button class="delete-product-btn text-red-600 hover:text-red-900" data-id="${product.id}">
|
| 690 |
+
<i class="fas fa-trash-alt"></i>
|
| 691 |
+
</button>
|
| 692 |
+
` : ''}
|
| 693 |
+
</td>
|
| 694 |
+
</tr>
|
| 695 |
+
`;
|
| 696 |
+
});
|
| 697 |
+
|
| 698 |
+
productsTableBody.innerHTML = html;
|
| 699 |
+
}
|
| 700 |
+
|
| 701 |
+
function updateDashboardStats() {
|
| 702 |
+
// Update total products
|
| 703 |
+
totalProducts.textContent = state.products.length;
|
| 704 |
+
|
| 705 |
+
// Update total stock
|
| 706 |
+
const totalStockCount = state.products.reduce((sum, product) => sum + product.quantity, 0);
|
| 707 |
+
totalStock.textContent = totalStockCount;
|
| 708 |
+
|
| 709 |
+
// Update total value
|
| 710 |
+
const totalValueAmount = state.products.reduce((sum, product) => sum + (product.price * product.quantity), 0);
|
| 711 |
+
totalValue.textContent = `$${totalValueAmount.toFixed(2)}`;
|
| 712 |
+
|
| 713 |
+
// Update low stock products
|
| 714 |
+
const lowStockItems = state.products.filter(p => p.quantity < 5);
|
| 715 |
+
|
| 716 |
+
if (lowStockItems.length === 0) {
|
| 717 |
+
lowStockProducts.innerHTML = `
|
| 718 |
+
<div class="p-4 text-center text-gray-500">
|
| 719 |
+
No hay productos con bajo stock
|
| 720 |
+
</div>
|
| 721 |
+
`;
|
| 722 |
+
} else {
|
| 723 |
+
let html = '';
|
| 724 |
+
|
| 725 |
+
lowStockItems.forEach(product => {
|
| 726 |
+
html += `
|
| 727 |
+
<div class="p-4 flex items-center justify-between hover:bg-gray-50">
|
| 728 |
+
<div>
|
| 729 |
+
<h4 class="font-medium text-gray-900">${product.name}</h4>
|
| 730 |
+
<p class="text-sm text-gray-500">${product.category}</p>
|
| 731 |
+
</div>
|
| 732 |
+
<div class="text-red-600 font-semibold">
|
| 733 |
+
${product.quantity} en stock
|
| 734 |
+
</div>
|
| 735 |
+
</div>
|
| 736 |
+
`;
|
| 737 |
+
});
|
| 738 |
+
|
| 739 |
+
lowStockProducts.innerHTML = html;
|
| 740 |
+
}
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
function showEditProductModal(product) {
|
| 744 |
+
editProductId.value = product.id;
|
| 745 |
+
editProductName.value = product.name;
|
| 746 |
+
editProductPrice.value = product.price;
|
| 747 |
+
editProductQuantity.value = product.quantity;
|
| 748 |
+
editProductModal.classList.remove('hidden');
|
| 749 |
+
}
|
| 750 |
+
|
| 751 |
+
function hideEditProductModal() {
|
| 752 |
+
editProductModal.classList.add('hidden');
|
| 753 |
+
state.productToEdit = null;
|
| 754 |
+
}
|
| 755 |
+
|
| 756 |
+
function showDeleteModal() {
|
| 757 |
+
deleteModal.classList.remove('hidden');
|
| 758 |
+
}
|
| 759 |
+
|
| 760 |
+
function hideDeleteModal() {
|
| 761 |
+
deleteModal.classList.add('hidden');
|
| 762 |
+
state.productToDelete = null;
|
| 763 |
+
}
|
| 764 |
+
</script>
|
| 765 |
+
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 馃К <a href="https://enzostvs-deepsite.hf.space?remix=fakesisalg/test" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
| 766 |
+
</html>
|
prompts.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Crea una aplicaci贸n web completa que funcione como un sistema de inventario conectado a una base de datos MySQL. Esta aplicaci贸n debe incluir las siguientes caracter铆sticas: Pantalla de login con autenticaci贸n de usuarios. Roles de acceso: Administrador: puede ver, a帽adir, editar y eliminar productos. Usuario: solo puede ver y a帽adir productos. Una vez iniciado sesi贸n: Mostrar una interfaz con un formulario para a帽adir nuevos productos (nombre, categor铆a, precio, cantidad). Mostrar una lista con los productos almacenados en la base de datos. Desde esta lista, los administradores pueden editar y eliminar productos. La base de datos debe estar conectada a MySQL, permitiendo que todo lo que se haga desde la p谩gina (a帽adir, editar, eliminar, listar) se refleje directamente en la base de datos. Debe poder probarse con herramientas como Postman y visualizarse con HeidiSQL. Usa tecnolog铆as modernas (HTML, CSS, JavaScript, Node.js o cualquier stack que facilite la conexi贸n a MySQL). Importante: El login debe funcionar con validaci贸n real (no solo visual), y los roles deben restringir las acciones en la interfaz seg煤n el tipo de usuario. El dise帽o debe ser sencillo y claro, tipo dashboard.
|