fakesisalg commited on
Commit
b0f61ce
verified
1 Parent(s): 706eeb0

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +7 -5
  2. index.html +1176 -19
  3. prompts.txt +1 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Test V2
3
- emoji: 馃憗
4
- colorFrom: indigo
5
- colorTo: green
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-v2
3
+ emoji: 馃惓
4
+ colorFrom: red
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,1176 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Inventory Manager</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
+ /* Custom styles that can't be achieved with Tailwind */
11
+ .sidebar {
12
+ transition: all 0.3s ease;
13
+ }
14
+ .sidebar.collapsed {
15
+ width: 70px;
16
+ }
17
+ .sidebar.collapsed .nav-text {
18
+ display: none;
19
+ }
20
+ .sidebar.collapsed .logo-text {
21
+ display: none;
22
+ }
23
+ .sidebar.collapsed .nav-item {
24
+ justify-content: center;
25
+ }
26
+ .content {
27
+ transition: all 0.3s ease;
28
+ }
29
+ .content.expanded {
30
+ margin-left: 70px;
31
+ }
32
+ @media (max-width: 768px) {
33
+ .sidebar {
34
+ width: 70px;
35
+ }
36
+ .sidebar .nav-text {
37
+ display: none;
38
+ }
39
+ .sidebar .logo-text {
40
+ display: none;
41
+ }
42
+ .sidebar .nav-item {
43
+ justify-content: center;
44
+ }
45
+ .content {
46
+ margin-left: 70px;
47
+ }
48
+ }
49
+ </style>
50
+ </head>
51
+ <body class="bg-gray-100">
52
+ <!-- Login Screen (visible by default) -->
53
+ <div id="login-screen" class="fixed inset-0 flex items-center justify-center bg-gray-900 bg-opacity-90 z-50">
54
+ <div class="bg-white rounded-lg shadow-xl p-8 w-full max-w-md">
55
+ <div class="text-center mb-8">
56
+ <i class="fas fa-boxes text-5xl text-blue-600 mb-4"></i>
57
+ <h1 class="text-3xl font-bold text-gray-800">Inventory Manager</h1>
58
+ <p class="text-gray-600">Inicia sesi贸n para acceder al sistema</p>
59
+ </div>
60
+ <form id="login-form" class="space-y-6">
61
+ <div>
62
+ <label for="username" class="block text-sm font-medium text-gray-700 mb-1">Usuario</label>
63
+ <input type="text" id="username" name="username" required
64
+ class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
65
+ </div>
66
+ <div>
67
+ <label for="password" class="block text-sm font-medium text-gray-700 mb-1">Contrase帽a</label>
68
+ <input type="password" id="password" name="password" required
69
+ class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
70
+ </div>
71
+ <div>
72
+ <button type="submit" class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition duration-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
73
+ Iniciar Sesi贸n
74
+ </button>
75
+ </div>
76
+ <div id="login-error" class="text-red-500 text-sm hidden">
77
+ <i class="fas fa-exclamation-circle mr-1"></i> Usuario o contrase帽a incorrectos
78
+ </div>
79
+ </form>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Dashboard (hidden by default) -->
84
+ <div id="dashboard" class="hidden">
85
+ <!-- Sidebar -->
86
+ <div id="sidebar" class="sidebar fixed inset-y-0 left-0 bg-gray-800 text-white w-64 shadow-lg">
87
+ <div class="flex items-center justify-between p-4 border-b border-gray-700">
88
+ <div class="flex items-center">
89
+ <i class="fas fa-boxes text-2xl text-blue-400"></i>
90
+ <span class="logo-text ml-3 text-xl font-semibold">Inventory</span>
91
+ </div>
92
+ <button id="toggle-sidebar" class="text-gray-400 hover:text-white focus:outline-none">
93
+ <i class="fas fa-bars"></i>
94
+ </button>
95
+ </div>
96
+ <nav class="mt-6">
97
+ <div class="px-4 space-y-2">
98
+ <a href="#" class="nav-item flex items-center px-4 py-3 text-gray-300 hover:bg-gray-700 rounded-md transition duration-300 active-nav" data-section="dashboard-section">
99
+ <i class="fas fa-tachometer-alt"></i>
100
+ <span class="nav-text ml-3">Resumen</span>
101
+ </a>
102
+ <a href="#" class="nav-item flex items-center px-4 py-3 text-gray-300 hover:bg-gray-700 rounded-md transition duration-300" data-section="products-section">
103
+ <i class="fas fa-box-open"></i>
104
+ <span class="nav-text ml-3">Productos</span>
105
+ </a>
106
+ <a href="#" class="nav-item flex items-center px-4 py-3 text-gray-300 hover:bg-gray-700 rounded-md transition duration-300" data-section="add-product-section">
107
+ <i class="fas fa-plus-circle"></i>
108
+ <span class="nav-text ml-3">Agregar Producto</span>
109
+ </a>
110
+ <a href="#" id="logout-btn" class="nav-item flex items-center px-4 py-3 text-gray-300 hover:bg-gray-700 rounded-md transition duration-300">
111
+ <i class="fas fa-sign-out-alt"></i>
112
+ <span class="nav-text ml-3">Cerrar Sesi贸n</span>
113
+ </a>
114
+ </div>
115
+ </nav>
116
+ </div>
117
+
118
+ <!-- Main Content -->
119
+ <div id="content" class="content ml-64 min-h-screen transition-all duration-300">
120
+ <!-- Top Navigation -->
121
+ <header class="bg-white shadow-sm">
122
+ <div class="flex justify-between items-center px-6 py-4">
123
+ <h1 class="text-2xl font-semibold text-gray-800" id="section-title">Resumen</h1>
124
+ <div class="flex items-center space-x-4">
125
+ <div class="relative">
126
+ <button id="user-menu-btn" class="flex items-center focus:outline-none">
127
+ <div class="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center text-white">
128
+ <span id="user-initials">AD</span>
129
+ </div>
130
+ <span class="ml-2 text-gray-700 hidden md:inline" id="username-display">Admin</span>
131
+ <i class="fas fa-chevron-down ml-1 text-gray-500 hidden md:inline"></i>
132
+ </button>
133
+ <div id="user-menu" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-10">
134
+ <div class="px-4 py-2 text-sm text-gray-700 border-b">
135
+ <div>Conectado como</div>
136
+ <div class="font-medium" id="menu-username">admin</div>
137
+ </div>
138
+ <a href="#" id="menu-logout-btn" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
139
+ <i class="fas fa-sign-out-alt mr-2"></i>Cerrar sesi贸n
140
+ </a>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </header>
146
+
147
+ <!-- Dashboard Sections -->
148
+ <main class="p-6">
149
+ <!-- Dashboard Summary Section -->
150
+ <section id="dashboard-section" class="section-content">
151
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
152
+ <div class="bg-white rounded-lg shadow p-6">
153
+ <div class="flex items-center">
154
+ <div class="p-3 rounded-full bg-blue-100 text-blue-600">
155
+ <i class="fas fa-boxes text-xl"></i>
156
+ </div>
157
+ <div class="ml-4">
158
+ <p class="text-sm font-medium text-gray-500">Total Productos</p>
159
+ <p class="text-2xl font-semibold text-gray-800" id="total-products">0</p>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ <div class="bg-white rounded-lg shadow p-6">
164
+ <div class="flex items-center">
165
+ <div class="p-3 rounded-full bg-green-100 text-green-600">
166
+ <i class="fas fa-check-circle text-xl"></i>
167
+ </div>
168
+ <div class="ml-4">
169
+ <p class="text-sm font-medium text-gray-500">Disponibles</p>
170
+ <p class="text-2xl font-semibold text-gray-800" id="available-products">0</p>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ <div class="bg-white rounded-lg shadow p-6">
175
+ <div class="flex items-center">
176
+ <div class="p-3 rounded-full bg-yellow-100 text-yellow-600">
177
+ <i class="fas fa-exclamation-triangle text-xl"></i>
178
+ </div>
179
+ <div class="ml-4">
180
+ <p class="text-sm font-medium text-gray-500">Bajo Stock</p>
181
+ <p class="text-2xl font-semibold text-gray-800" id="low-stock-products">0</p>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ <div class="bg-white rounded-lg shadow p-6">
186
+ <div class="flex items-center">
187
+ <div class="p-3 rounded-full bg-red-100 text-red-600">
188
+ <i class="fas fa-times-circle text-xl"></i>
189
+ </div>
190
+ <div class="ml-4">
191
+ <p class="text-sm font-medium text-gray-500">Agotados</p>
192
+ <p class="text-2xl font-semibold text-gray-800" id="out-of-stock-products">0</p>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </div>
197
+
198
+ <div class="bg-white rounded-lg shadow p-6 mb-6">
199
+ <div class="flex justify-between items-center mb-4">
200
+ <h2 class="text-lg font-semibold text-gray-800">Productos por Categor铆a</h2>
201
+ </div>
202
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4" id="categories-chart">
203
+ <!-- Categories will be populated here -->
204
+ </div>
205
+ </div>
206
+
207
+ <div class="bg-white rounded-lg shadow p-6">
208
+ <div class="flex justify-between items-center mb-4">
209
+ <h2 class="text-lg font-semibold text-gray-800">Productos Recientes</h2>
210
+ <a href="#" class="text-blue-600 hover:text-blue-800 text-sm font-medium" data-section="products-section">Ver todos</a>
211
+ </div>
212
+ <div class="overflow-x-auto">
213
+ <table class="min-w-full divide-y divide-gray-200">
214
+ <thead class="bg-gray-50">
215
+ <tr>
216
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nombre</th>
217
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Categor铆a</th>
218
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Precio</th>
219
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cantidad</th>
220
+ </tr>
221
+ </thead>
222
+ <tbody class="bg-white divide-y divide-gray-200" id="recent-products">
223
+ <!-- Recent products will be populated here -->
224
+ </tbody>
225
+ </table>
226
+ </div>
227
+ </div>
228
+ </section>
229
+
230
+ <!-- Products List Section -->
231
+ <section id="products-section" class="section-content hidden">
232
+ <div class="bg-white rounded-lg shadow p-6">
233
+ <div class="flex justify-between items-center mb-6">
234
+ <h2 class="text-xl font-semibold text-gray-800">Lista de Productos</h2>
235
+ <div class="flex space-x-3">
236
+ <div class="relative">
237
+ <input type="text" id="product-search" placeholder="Buscar productos..." class="pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
238
+ <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
239
+ </div>
240
+ <button id="refresh-products" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition duration-300">
241
+ <i class="fas fa-sync-alt"></i>
242
+ </button>
243
+ </div>
244
+ </div>
245
+ <div class="overflow-x-auto">
246
+ <table class="min-w-full divide-y divide-gray-200">
247
+ <thead class="bg-gray-50">
248
+ <tr>
249
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nombre</th>
250
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Categor铆a</th>
251
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Precio</th>
252
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cantidad</th>
253
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Acciones</th>
254
+ </tr>
255
+ </thead>
256
+ <tbody class="bg-white divide-y divide-gray-200" id="products-table">
257
+ <!-- Products will be populated here -->
258
+ </tbody>
259
+ </table>
260
+ </div>
261
+ <div class="mt-4 flex justify-between items-center">
262
+ <div class="text-sm text-gray-500">
263
+ Mostrando <span id="showing-from">1</span> a <span id="showing-to">10</span> de <span id="total-items">0</span> productos
264
+ </div>
265
+ <div class="flex space-x-2">
266
+ <button id="prev-page" class="px-3 py-1 border border-gray-300 rounded-md bg-white text-gray-700 disabled:opacity-50" disabled>
267
+ <i class="fas fa-chevron-left"></i>
268
+ </button>
269
+ <button id="next-page" class="px-3 py-1 border border-gray-300 rounded-md bg-white text-gray-700 disabled:opacity-50" disabled>
270
+ <i class="fas fa-chevron-right"></i>
271
+ </button>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ </section>
276
+
277
+ <!-- Add Product Section -->
278
+ <section id="add-product-section" class="section-content hidden">
279
+ <div class="bg-white rounded-lg shadow p-6">
280
+ <h2 class="text-xl font-semibold text-gray-800 mb-6">Agregar Nuevo Producto</h2>
281
+ <form id="add-product-form" class="space-y-6">
282
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
283
+ <div>
284
+ <label for="product-name" class="block text-sm font-medium text-gray-700 mb-1">Nombre del Producto *</label>
285
+ <input type="text" id="product-name" name="product-name" required
286
+ class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
287
+ </div>
288
+ <div>
289
+ <label for="product-category" class="block text-sm font-medium text-gray-700 mb-1">Categor铆a *</label>
290
+ <select id="product-category" name="product-category" required
291
+ class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
292
+ <option value="">Seleccione una categor铆a</option>
293
+ <option value="Electr贸nicos">Electr贸nicos</option>
294
+ <option value="Ropa">Ropa</option>
295
+ <option value="Alimentos">Alimentos</option>
296
+ <option value="Oficina">Oficina</option>
297
+ <option value="Hogar">Hogar</option>
298
+ </select>
299
+ </div>
300
+ <div>
301
+ <label for="product-price" class="block text-sm font-medium text-gray-700 mb-1">Precio *</label>
302
+ <div class="relative">
303
+ <span class="absolute left-3 top-3 text-gray-500">$</span>
304
+ <input type="number" id="product-price" name="product-price" min="0" step="0.01" required
305
+ class="w-full pl-8 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
306
+ </div>
307
+ </div>
308
+ <div>
309
+ <label for="product-quantity" class="block text-sm font-medium text-gray-700 mb-1">Cantidad *</label>
310
+ <input type="number" id="product-quantity" name="product-quantity" min="0" required
311
+ class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
312
+ </div>
313
+ </div>
314
+ <div>
315
+ <label for="product-description" class="block text-sm font-medium text-gray-700 mb-1">Descripci贸n</label>
316
+ <textarea id="product-description" name="product-description" rows="3"
317
+ class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
318
+ </div>
319
+ <div class="flex justify-end space-x-4">
320
+ <button type="reset" class="px-4 py-2 border border-gray-300 rounded-md bg-white text-gray-700 hover:bg-gray-50 transition duration-300">
321
+ Limpiar
322
+ </button>
323
+ <button type="submit" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition duration-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
324
+ Guardar Producto
325
+ </button>
326
+ </div>
327
+ </form>
328
+ </div>
329
+ </section>
330
+
331
+ <!-- Edit Product Modal (hidden by default) -->
332
+ <div id="edit-product-modal" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50 hidden">
333
+ <div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-2xl">
334
+ <div class="flex justify-between items-center mb-4">
335
+ <h3 class="text-xl font-semibold text-gray-800">Editar Producto</h3>
336
+ <button id="close-edit-modal" class="text-gray-500 hover:text-gray-700">
337
+ <i class="fas fa-times"></i>
338
+ </button>
339
+ </div>
340
+ <form id="edit-product-form" class="space-y-6">
341
+ <input type="hidden" id="edit-product-id">
342
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
343
+ <div>
344
+ <label for="edit-product-name" class="block text-sm font-medium text-gray-700 mb-1">Nombre del Producto *</label>
345
+ <input type="text" id="edit-product-name" name="edit-product-name" required
346
+ class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
347
+ </div>
348
+ <div>
349
+ <label for="edit-product-category" class="block text-sm font-medium text-gray-700 mb-1">Categor铆a *</label>
350
+ <select id="edit-product-category" name="edit-product-category" required
351
+ class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
352
+ <option value="">Seleccione una categor铆a</option>
353
+ <option value="Electr贸nicos">Electr贸nicos</option>
354
+ <option value="Ropa">Ropa</option>
355
+ <option value="Alimentos">Alimentos</option>
356
+ <option value="Oficina">Oficina</option>
357
+ <option value="Hogar">Hogar</option>
358
+ </select>
359
+ </div>
360
+ <div>
361
+ <label for="edit-product-price" class="block text-sm font-medium text-gray-700 mb-1">Precio *</label>
362
+ <div class="relative">
363
+ <span class="absolute left-3 top-3 text-gray-500">$</span>
364
+ <input type="number" id="edit-product-price" name="edit-product-price" min="0" step="0.01" required
365
+ class="w-full pl-8 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
366
+ </div>
367
+ </div>
368
+ <div>
369
+ <label for="edit-product-quantity" class="block text-sm font-medium text-gray-700 mb-1">Cantidad *</label>
370
+ <input type="number" id="edit-product-quantity" name="edit-product-quantity" min="0" required
371
+ class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
372
+ </div>
373
+ </div>
374
+ <div>
375
+ <label for="edit-product-description" class="block text-sm font-medium text-gray-700 mb-1">Descripci贸n</label>
376
+ <textarea id="edit-product-description" name="edit-product-description" rows="3"
377
+ class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
378
+ </div>
379
+ <div class="flex justify-end space-x-4">
380
+ <button type="button" id="cancel-edit" class="px-4 py-2 border border-gray-300 rounded-md bg-white text-gray-700 hover:bg-gray-50 transition duration-300">
381
+ Cancelar
382
+ </button>
383
+ <button type="submit" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition duration-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
384
+ Guardar Cambios
385
+ </button>
386
+ </div>
387
+ </form>
388
+ </div>
389
+ </div>
390
+ </main>
391
+ </div>
392
+
393
+ <!-- Alert Notification (hidden by default) -->
394
+ <div id="alert-notification" class="fixed bottom-4 right-4 z-50 hidden">
395
+ <div class="bg-white rounded-lg shadow-lg overflow-hidden w-80">
396
+ <div class="flex items-center px-4 py-3 border-l-4 border-green-500">
397
+ <div class="text-green-500 mr-3">
398
+ <i class="fas fa-check-circle text-xl"></i>
399
+ </div>
400
+ <div>
401
+ <h4 class="font-medium text-gray-800" id="alert-title">脡xito</h4>
402
+ <p class="text-sm text-gray-600" id="alert-message">Operaci贸n realizada con 茅xito</p>
403
+ </div>
404
+ <button id="close-alert" class="ml-auto text-gray-400 hover:text-gray-500">
405
+ <i class="fas fa-times"></i>
406
+ </button>
407
+ </div>
408
+ </div>
409
+ </div>
410
+ </div>
411
+
412
+ <script>
413
+ // Configuration variables
414
+ // IMPORTANT: Configure these values according to your backend API
415
+ const config = {
416
+ // Base URL of your API (Spring Boot backend)
417
+ baseUrl: 'http://localhost:8080/api',
418
+
419
+ // Endpoints
420
+ endpoints: {
421
+ login: '/auth/login',
422
+ products: '/productos'
423
+ },
424
+
425
+ // Default pagination settings
426
+ pagination: {
427
+ pageSize: 10,
428
+ currentPage: 1
429
+ }
430
+ };
431
+
432
+ // Application state
433
+ const state = {
434
+ currentUser: null,
435
+ products: [],
436
+ filteredProducts: [],
437
+ currentSection: 'dashboard-section',
438
+ isAdmin: false
439
+ };
440
+
441
+ // DOM Elements
442
+ const elements = {
443
+ loginScreen: document.getElementById('login-screen'),
444
+ dashboard: document.getElementById('dashboard'),
445
+ loginForm: document.getElementById('login-form'),
446
+ loginError: document.getElementById('login-error'),
447
+ sidebar: document.getElementById('sidebar'),
448
+ toggleSidebar: document.getElementById('toggle-sidebar'),
449
+ content: document.getElementById('content'),
450
+ sectionTitle: document.getElementById('section-title'),
451
+ usernameDisplay: document.getElementById('username-display'),
452
+ userInitials: document.getElementById('user-initials'),
453
+ menuUsername: document.getElementById('menu-username'),
454
+ userMenuBtn: document.getElementById('user-menu-btn'),
455
+ userMenu: document.getElementById('user-menu'),
456
+ logoutBtn: document.getElementById('logout-btn'),
457
+ menuLogoutBtn: document.getElementById('menu-logout-btn'),
458
+
459
+ // Dashboard elements
460
+ totalProducts: document.getElementById('total-products'),
461
+ availableProducts: document.getElementById('available-products'),
462
+ lowStockProducts: document.getElementById('low-stock-products'),
463
+ outOfStockProducts: document.getElementById('out-of-stock-products'),
464
+ categoriesChart: document.getElementById('categories-chart'),
465
+ recentProducts: document.getElementById('recent-products'),
466
+
467
+ // Products list elements
468
+ productSearch: document.getElementById('product-search'),
469
+ refreshProducts: document.getElementById('refresh-products'),
470
+ productsTable: document.getElementById('products-table'),
471
+ showingFrom: document.getElementById('showing-from'),
472
+ showingTo: document.getElementById('showing-to'),
473
+ totalItems: document.getElementById('total-items'),
474
+ prevPage: document.getElementById('prev-page'),
475
+ nextPage: document.getElementById('next-page'),
476
+
477
+ // Add product elements
478
+ addProductForm: document.getElementById('add-product-form'),
479
+
480
+ // Edit product modal elements
481
+ editProductModal: document.getElementById('edit-product-modal'),
482
+ closeEditModal: document.getElementById('close-edit-modal'),
483
+ editProductForm: document.getElementById('edit-product-form'),
484
+ editProductId: document.getElementById('edit-product-id'),
485
+ editProductName: document.getElementById('edit-product-name'),
486
+ editProductCategory: document.getElementById('edit-product-category'),
487
+ editProductPrice: document.getElementById('edit-product-price'),
488
+ editProductQuantity: document.getElementById('edit-product-quantity'),
489
+ editProductDescription: document.getElementById('edit-product-description'),
490
+ cancelEdit: document.getElementById('cancel-edit'),
491
+
492
+ // Alert notification
493
+ alertNotification: document.getElementById('alert-notification'),
494
+ alertTitle: document.getElementById('alert-title'),
495
+ alertMessage: document.getElementById('alert-message'),
496
+ closeAlert: document.getElementById('close-alert'),
497
+
498
+ // Section contents
499
+ sectionContents: document.querySelectorAll('.section-content'),
500
+ navLinks: document.querySelectorAll('.nav-item')
501
+ };
502
+
503
+ // Helper functions
504
+ const helpers = {
505
+ // Show alert notification
506
+ showAlert: (title, message, type = 'success') => {
507
+ elements.alertTitle.textContent = title;
508
+ elements.alertMessage.textContent = message;
509
+
510
+ // Update alert style based on type
511
+ const alertContainer = elements.alertNotification.querySelector('div');
512
+ alertContainer.className = `flex items-center px-4 py-3 border-l-4 ${type === 'success' ? 'border-green-500' : 'border-red-500'}`;
513
+
514
+ const icon = elements.alertNotification.querySelector('div > div:first-child');
515
+ icon.className = `${type === 'success' ? 'text-green-500' : 'text-red-500'} mr-3`;
516
+ icon.innerHTML = type === 'success' ? '<i class="fas fa-check-circle text-xl"></i>' : '<i class="fas fa-exclamation-circle text-xl"></i>';
517
+
518
+ elements.alertNotification.classList.remove('hidden');
519
+
520
+ // Auto hide after 5 seconds
521
+ setTimeout(() => {
522
+ elements.alertNotification.classList.add('hidden');
523
+ }, 5000);
524
+ },
525
+
526
+ // Get initials from name
527
+ getInitials: (name) => {
528
+ return name.split(' ').map(part => part[0]).join('').toUpperCase();
529
+ },
530
+
531
+ // Format currency
532
+ formatCurrency: (amount) => {
533
+ return new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'USD' }).format(amount);
534
+ },
535
+
536
+ // Generate basic auth header
537
+ getAuthHeader: () => {
538
+ if (!state.currentUser) return {};
539
+
540
+ // Basic Auth: 'Basic ' + btoa(username + ':' + password)
541
+ const token = btoa(`${state.currentUser.username}:${state.currentUser.password}`);
542
+ return {
543
+ 'Authorization': `Basic ${token}`,
544
+ 'Content-Type': 'application/json'
545
+ };
546
+ },
547
+
548
+ // Handle API errors
549
+ handleApiError: (error) => {
550
+ console.error('API Error:', error);
551
+ helpers.showAlert('Error', error.message || 'Ocurri贸 un error al procesar la solicitud', 'error');
552
+ }
553
+ };
554
+
555
+ // API functions
556
+ const api = {
557
+ // Login user
558
+ login: async (username, password) => {
559
+ try {
560
+ // In a real app, this would call your backend API
561
+ // For demo purposes, we're using mock data
562
+
563
+ // Mock users (in a real app, these would come from your backend)
564
+ const mockUsers = [
565
+ { username: 'admin', password: 'admin123', role: 'admin', name: 'Administrador' },
566
+ { username: 'user', password: 'user123', role: 'usuario', name: 'Usuario Normal' }
567
+ ];
568
+
569
+ // Find user
570
+ const user = mockUsers.find(u => u.username === username && u.password === password);
571
+
572
+ if (!user) {
573
+ throw new Error('Usuario o contrase帽a incorrectos');
574
+ }
575
+
576
+ // Simulate API delay
577
+ await new Promise(resolve => setTimeout(resolve, 500));
578
+
579
+ return user;
580
+ } catch (error) {
581
+ throw error;
582
+ }
583
+ },
584
+
585
+ // Get products from API
586
+ getProducts: async () => {
587
+ try {
588
+ // In a real app, this would call your backend API
589
+ // For demo purposes, we're using mock data
590
+
591
+ // Mock products
592
+ const mockProducts = [
593
+ { id: 1, nombre: 'Laptop HP', categoria: 'Electr贸nicos', precio: 1200.50, cantidad: 15, descripcion: 'Laptop HP con 16GB RAM y 512GB SSD' },
594
+ { id: 2, nombre: 'Smartphone Samsung', categoria: 'Electr贸nicos', precio: 899.99, cantidad: 25, descripcion: 'Smartphone Samsung Galaxy S21' },
595
+ { id: 3, nombre: 'Camiseta Algod贸n', categoria: 'Ropa', precio: 24.99, cantidad: 50, descripcion: 'Camiseta 100% algod贸n talla M' },
596
+ { id: 4, nombre: 'Arroz Integral', categoria: 'Alimentos', precio: 3.50, cantidad: 100, descripcion: 'Arroz integral 1kg' },
597
+ { id: 5, nombre: 'Silla Oficina', categoria: 'Oficina', precio: 149.99, cantidad: 10, descripcion: 'Silla ergon贸mica para oficina' },
598
+ { id: 6, nombre: 'L谩mpara LED', categoria: 'Hogar', precio: 45.75, cantidad: 30, descripcion: 'L谩mpara LED de techo' },
599
+ { id: 7, nombre: 'Tablet Lenovo', categoria: 'Electr贸nicos', precio: 349.99, cantidad: 5, descripcion: 'Tablet Lenovo 10 pulgadas' },
600
+ { id: 8, nombre: 'Pantal贸n Vaquero', categoria: 'Ropa', precio: 39.99, cantidad: 20, descripcion: 'Pantal贸n vaquero talla 32' },
601
+ { id: 9, nombre: 'Aceite Oliva', categoria: 'Alimentos', precio: 8.99, cantidad: 40, descripcion: 'Aceite de oliva virgen extra 1L' },
602
+ { id: 10, nombre: 'Escritorio Madera', categoria: 'Oficina', precio: 199.99, cantidad: 8, descripcion: 'Escritorio de madera 120x60cm' }
603
+ ];
604
+
605
+ // Simulate API delay
606
+ await new Promise(resolve => setTimeout(resolve, 800));
607
+
608
+ return mockProducts;
609
+ } catch (error) {
610
+ throw error;
611
+ }
612
+ },
613
+
614
+ // Add new product
615
+ addProduct: async (productData) => {
616
+ try {
617
+ // In a real app, this would POST to your backend API
618
+ // Example using fetch:
619
+ /*
620
+ const response = await fetch(`${config.baseUrl}${config.endpoints.products}`, {
621
+ method: 'POST',
622
+ headers: helpers.getAuthHeader(),
623
+ body: JSON.stringify(productData)
624
+ });
625
+
626
+ if (!response.ok) {
627
+ throw new Error('Error al agregar el producto');
628
+ }
629
+
630
+ return await response.json();
631
+ */
632
+
633
+ // For demo purposes, we're just returning the product with a new ID
634
+ await new Promise(resolve => setTimeout(resolve, 500));
635
+
636
+ return {
637
+ ...productData,
638
+ id: Math.floor(Math.random() * 1000) + 11 // Generate a random ID
639
+ };
640
+ } catch (error) {
641
+ throw error;
642
+ }
643
+ },
644
+
645
+ // Update product
646
+ updateProduct: async (productId, productData) => {
647
+ try {
648
+ // In a real app, this would PUT to your backend API
649
+ // Example using fetch:
650
+ /*
651
+ const response = await fetch(`${config.baseUrl}${config.endpoints.products}/${productId}`, {
652
+ method: 'PUT',
653
+ headers: helpers.getAuthHeader(),
654
+ body: JSON.stringify(productData)
655
+ });
656
+
657
+ if (!response.ok) {
658
+ throw new Error('Error al actualizar el producto');
659
+ }
660
+
661
+ return await response.json();
662
+ */
663
+
664
+ // For demo purposes, we're just returning the updated product
665
+ await new Promise(resolve => setTimeout(resolve, 500));
666
+
667
+ return {
668
+ ...productData,
669
+ id: productId
670
+ };
671
+ } catch (error) {
672
+ throw error;
673
+ }
674
+ },
675
+
676
+ // Delete product
677
+ deleteProduct: async (productId) => {
678
+ try {
679
+ // In a real app, this would DELETE to your backend API
680
+ // Example using fetch:
681
+ /*
682
+ const response = await fetch(`${config.baseUrl}${config.endpoints.products}/${productId}`, {
683
+ method: 'DELETE',
684
+ headers: helpers.getAuthHeader()
685
+ });
686
+
687
+ if (!response.ok) {
688
+ throw new Error('Error al eliminar el producto');
689
+ }
690
+ */
691
+
692
+ // For demo purposes, we're just simulating a successful deletion
693
+ await new Promise(resolve => setTimeout(resolve, 300));
694
+
695
+ return true;
696
+ } catch (error) {
697
+ throw error;
698
+ }
699
+ }
700
+ };
701
+
702
+ // UI functions
703
+ const ui = {
704
+ // Toggle sidebar
705
+ toggleSidebar: () => {
706
+ elements.sidebar.classList.toggle('collapsed');
707
+ elements.content.classList.toggle('expanded');
708
+
709
+ // Update localStorage
710
+ const isCollapsed = elements.sidebar.classList.contains('collapsed');
711
+ localStorage.setItem('sidebarCollapsed', isCollapsed);
712
+ },
713
+
714
+ // Switch between sections
715
+ switchSection: (sectionId) => {
716
+ // Update active nav link
717
+ elements.navLinks.forEach(link => {
718
+ link.classList.remove('bg-gray-700', 'text-white');
719
+ link.classList.add('text-gray-300');
720
+
721
+ if (link.getAttribute('data-section') === sectionId) {
722
+ link.classList.remove('text-gray-300');
723
+ link.classList.add('bg-gray-700', 'text-white');
724
+ }
725
+ });
726
+
727
+ // Hide all sections
728
+ elements.sectionContents.forEach(section => {
729
+ section.classList.add('hidden');
730
+ });
731
+
732
+ // Show selected section
733
+ document.getElementById(sectionId).classList.remove('hidden');
734
+ state.currentSection = sectionId;
735
+
736
+ // Update section title
737
+ let title = 'Resumen';
738
+ if (sectionId === 'products-section') title = 'Productos';
739
+ if (sectionId === 'add-product-section') title = 'Agregar Producto';
740
+ elements.sectionTitle.textContent = title;
741
+
742
+ // Load section data if needed
743
+ if (sectionId === 'products-section') {
744
+ ui.loadProductsTable();
745
+ }
746
+ },
747
+
748
+ // Load dashboard summary
749
+ loadDashboardSummary: () => {
750
+ const totalProducts = state.products.length;
751
+ const availableProducts = state.products.filter(p => p.cantidad > 10).length;
752
+ const lowStockProducts = state.products.filter(p => p.cantidad > 0 && p.cantidad <= 10).length;
753
+ const outOfStockProducts = state.products.filter(p => p.cantidad === 0).length;
754
+
755
+ elements.totalProducts.textContent = totalProducts;
756
+ elements.availableProducts.textContent = availableProducts;
757
+ elements.lowStockProducts.textContent = lowStockProducts;
758
+ elements.outOfStockProducts.textContent = outOfStockProducts;
759
+
760
+ // Load categories chart
761
+ const categories = {};
762
+ state.products.forEach(product => {
763
+ if (!categories[product.categoria]) {
764
+ categories[product.categoria] = 0;
765
+ }
766
+ categories[product.categoria]++;
767
+ });
768
+
769
+ let categoriesHtml = '';
770
+ for (const [category, count] of Object.entries(categories)) {
771
+ const colors = {
772
+ 'Electr贸nicos': 'bg-blue-100 text-blue-800',
773
+ 'Ropa': 'bg-purple-100 text-purple-800',
774
+ 'Alimentos': 'bg-green-100 text-green-800',
775
+ 'Oficina': 'bg-yellow-100 text-yellow-800',
776
+ 'Hogar': 'bg-red-100 text-red-800'
777
+ };
778
+
779
+ categoriesHtml += `
780
+ <div class="bg-white rounded-lg shadow p-4">
781
+ <div class="flex items-center">
782
+ <div class="p-2 rounded-full ${colors[category] || 'bg-gray-100 text-gray-800'}">
783
+ <i class="fas ${category === 'Electr贸nicos' ? 'fa-laptop' :
784
+ category === 'Ropa' ? 'fa-tshirt' :
785
+ category === 'Alimentos' ? 'fa-utensils' :
786
+ category === 'Oficina' ? 'fa-briefcase' : 'fa-home'}"></i>
787
+ </div>
788
+ <div class="ml-3">
789
+ <p class="text-sm font-medium text-gray-500">${category}</p>
790
+ <p class="text-lg font-semibold text-gray-800">${count} productos</p>
791
+ </div>
792
+ </div>
793
+ </div>
794
+ `;
795
+ }
796
+
797
+ elements.categoriesChart.innerHTML = categoriesHtml;
798
+
799
+ // Load recent products (last 5 added)
800
+ const recentProducts = [...state.products].sort((a, b) => b.id - a.id).slice(0, 5);
801
+ let recentProductsHtml = '';
802
+
803
+ recentProducts.forEach(product => {
804
+ recentProductsHtml += `
805
+ <tr>
806
+ <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${product.nombre}</td>
807
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${product.categoria}</td>
808
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${helpers.formatCurrency(product.precio)}</td>
809
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
810
+ <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${product.cantidad > 10 ? 'bg-green-100 text-green-800' : product.cantidad > 0 ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800'}">
811
+ ${product.cantidad} ${product.cantidad === 1 ? 'unidad' : 'unidades'}
812
+ </span>
813
+ </td>
814
+ </tr>
815
+ `;
816
+ });
817
+
818
+ elements.recentProducts.innerHTML = recentProductsHtml;
819
+ },
820
+
821
+ // Load products table
822
+ loadProductsTable: (products = state.filteredProducts.length ? state.filteredProducts : state.products) => {
823
+ const startIndex = (config.pagination.currentPage - 1) * config.pagination.pageSize;
824
+ const endIndex = startIndex + config.pagination.pageSize;
825
+ const paginatedProducts = products.slice(startIndex, endIndex);
826
+
827
+ let productsHtml = '';
828
+
829
+ paginatedProducts.forEach(product => {
830
+ productsHtml += `
831
+ <tr>
832
+ <td class="px-6 py-4 whitespace-nowrap">
833
+ <div class="flex items-center">
834
+ <div class="flex-shrink-0 h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600">
835
+ <i class="fas ${product.categoria === 'Electr贸nicos' ? 'fa-laptop' :
836
+ product.categoria === 'Ropa' ? 'fa-tshirt' :
837
+ product.categoria === 'Alimentos' ? 'fa-utensils' :
838
+ product.categoria === 'Oficina' ? 'fa-briefcase' : 'fa-home'}"></i>
839
+ </div>
840
+ <div class="ml-4">
841
+ <div class="text-sm font-medium text-gray-900">${product.nombre}</div>
842
+ <div class="text-sm text-gray-500">${product.descripcion.substring(0, 30)}${product.descripcion.length > 30 ? '...' : ''}</div>
843
+ </div>
844
+ </div>
845
+ </td>
846
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${product.categoria}</td>
847
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${helpers.formatCurrency(product.precio)}</td>
848
+ <td class="px-6 py-4 whitespace-nowrap">
849
+ <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${product.cantidad > 10 ? 'bg-green-100 text-green-800' : product.cantidad > 0 ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800'}">
850
+ ${product.cantidad} ${product.cantidad === 1 ? 'unidad' : 'unidades'}
851
+ </span>
852
+ </td>
853
+ <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
854
+ <button class="text-blue-600 hover:text-blue-900 mr-3 edit-product" data-id="${product.id}">
855
+ <i class="fas fa-edit"></i>
856
+ </button>
857
+ ${state.isAdmin ? `
858
+ <button class="text-red-600 hover:text-red-900 delete-product" data-id="${product.id}">
859
+ <i class="fas fa-trash-alt"></i>
860
+ </button>
861
+ ` : ''}
862
+ </td>
863
+ </tr>
864
+ `;
865
+ });
866
+
867
+ elements.productsTable.innerHTML = productsHtml;
868
+
869
+ // Update pagination info
870
+ elements.showingFrom.textContent = startIndex + 1;
871
+ elements.showingTo.textContent = Math.min(endIndex, products.length);
872
+ elements.totalItems.textContent = products.length;
873
+
874
+ // Enable/disable pagination buttons
875
+ elements.prevPage.disabled = config.pagination.currentPage === 1;
876
+ elements.nextPage.disabled = endIndex >= products.length;
877
+
878
+ // Add event listeners to edit and delete buttons
879
+ document.querySelectorAll('.edit-product').forEach(btn => {
880
+ btn.addEventListener('click', () => ui.showEditProductModal(btn.getAttribute('data-id')));
881
+ });
882
+
883
+ if (state.isAdmin) {
884
+ document.querySelectorAll('.delete-product').forEach(btn => {
885
+ btn.addEventListener('click', () => ui.deleteProduct(btn.getAttribute('data-id')));
886
+ });
887
+ }
888
+ },
889
+
890
+ // Show edit product modal
891
+ showEditProductModal: (productId) => {
892
+ const product = state.products.find(p => p.id == productId);
893
+ if (!product) return;
894
+
895
+ elements.editProductId.value = product.id;
896
+ elements.editProductName.value = product.nombre;
897
+ elements.editProductCategory.value = product.categoria;
898
+ elements.editProductPrice.value = product.precio;
899
+ elements.editProductQuantity.value = product.cantidad;
900
+ elements.editProductDescription.value = product.descripcion || '';
901
+
902
+ elements.editProductModal.classList.remove('hidden');
903
+ },
904
+
905
+ // Hide edit product modal
906
+ hideEditProductModal: () => {
907
+ elements.editProductModal.classList.add('hidden');
908
+ },
909
+
910
+ // Delete product
911
+ deleteProduct: async (productId) => {
912
+ if (!confirm('驴Est谩s seguro de que deseas eliminar este producto?')) return;
913
+
914
+ try {
915
+ await api.deleteProduct(productId);
916
+
917
+ // Remove product from state
918
+ state.products = state.products.filter(p => p.id != productId);
919
+ state.filteredProducts = state.filteredProducts.filter(p => p.id != productId);
920
+
921
+ // Reload products table
922
+ ui.loadProductsTable();
923
+
924
+ // Update dashboard summary
925
+ if (state.currentSection === 'dashboard-section') {
926
+ ui.loadDashboardSummary();
927
+ }
928
+
929
+ helpers.showAlert('脡xito', 'Producto eliminado correctamente');
930
+ } catch (error) {
931
+ helpers.handleApiError(error);
932
+ }
933
+ },
934
+
935
+ // Search products
936
+ searchProducts: (query) => {
937
+ if (!query) {
938
+ state.filteredProducts = [];
939
+ ui.loadProductsTable();
940
+ return;
941
+ }
942
+
943
+ const lowerQuery = query.toLowerCase();
944
+ state.filteredProducts = state.products.filter(product =>
945
+ product.nombre.toLowerCase().includes(lowerQuery) ||
946
+ product.categoria.toLowerCase().includes(lowerQuery) ||
947
+ product.descripcion?.toLowerCase().includes(lowerQuery)
948
+ );
949
+
950
+ // Reset to first page
951
+ config.pagination.currentPage = 1;
952
+ ui.loadProductsTable();
953
+ }
954
+ };
955
+
956
+ // Event listeners
957
+ const setupEventListeners = () => {
958
+ // Login form
959
+ elements.loginForm.addEventListener('submit', async (e) => {
960
+ e.preventDefault();
961
+
962
+ const username = elements.username.value;
963
+ const password = elements.password.value;
964
+
965
+ try {
966
+ const user = await api.login(username, password);
967
+
968
+ // Update application state
969
+ state.currentUser = user;
970
+ state.isAdmin = user.role === 'admin';
971
+
972
+ // Hide login screen and show dashboard
973
+ elements.loginScreen.classList.add('hidden');
974
+ elements.dashboard.classList.remove('hidden');
975
+
976
+ // Update UI with user info
977
+ elements.usernameDisplay.textContent = user.name;
978
+ elements.menuUsername.textContent = user.username;
979
+ elements.userInitials.textContent = helpers.getInitials(user.name);
980
+
981
+ // Load initial data
982
+ state.products = await api.getProducts();
983
+
984
+ // Load dashboard
985
+ ui.loadDashboardSummary();
986
+
987
+ // Check sidebar state from localStorage
988
+ if (localStorage.getItem('sidebarCollapsed') === 'true') {
989
+ elements.sidebar.classList.add('collapsed');
990
+ elements.content.classList.add('expanded');
991
+ }
992
+
993
+ // Show welcome message
994
+ helpers.showAlert('Bienvenido', `Has iniciado sesi贸n como ${user.name}`);
995
+ } catch (error) {
996
+ elements.loginError.classList.remove('hidden');
997
+ elements.loginError.textContent = error.message;
998
+ }
999
+ });
1000
+
1001
+ // Logout buttons
1002
+ elements.logoutBtn.addEventListener('click', () => {
1003
+ state.currentUser = null;
1004
+ elements.dashboard.classList.add('hidden');
1005
+ elements.loginScreen.classList.remove('hidden');
1006
+ elements.loginError.classList.add('hidden');
1007
+ elements.loginForm.reset();
1008
+ });
1009
+
1010
+ elements.menuLogoutBtn.addEventListener('click', () => {
1011
+ state.currentUser = null;
1012
+ elements.dashboard.classList.add('hidden');
1013
+ elements.loginScreen.classList.remove('hidden');
1014
+ elements.loginError.classList.add('hidden');
1015
+ elements.loginForm.reset();
1016
+ });
1017
+
1018
+ // Toggle sidebar
1019
+ elements.toggleSidebar.addEventListener('click', ui.toggleSidebar);
1020
+
1021
+ // User menu
1022
+ elements.userMenuBtn.addEventListener('click', () => {
1023
+ elements.userMenu.classList.toggle('hidden');
1024
+ });
1025
+
1026
+ // Close user menu when clicking outside
1027
+ document.addEventListener('click', (e) => {
1028
+ if (!elements.userMenuBtn.contains(e.target) && !elements.userMenu.contains(e.target)) {
1029
+ elements.userMenu.classList.add('hidden');
1030
+ }
1031
+ });
1032
+
1033
+ // Navigation links
1034
+ elements.navLinks.forEach(link => {
1035
+ link.addEventListener('click', (e) => {
1036
+ e.preventDefault();
1037
+ ui.switchSection(link.getAttribute('data-section'));
1038
+ });
1039
+ });
1040
+
1041
+ // Product search
1042
+ elements.productSearch.addEventListener('input', (e) => {
1043
+ ui.searchProducts(e.target.value);
1044
+ });
1045
+
1046
+ // Refresh products
1047
+ elements.refreshProducts.addEventListener('click', async () => {
1048
+ try {
1049
+ state.products = await api.getProducts();
1050
+ state.filteredProducts = [];
1051
+ elements.productSearch.value = '';
1052
+ ui.loadProductsTable();
1053
+ helpers.showAlert('脡xito', 'Productos actualizados correctamente');
1054
+ } catch (error) {
1055
+ helpers.handleApiError(error);
1056
+ }
1057
+ });
1058
+
1059
+ // Pagination buttons
1060
+ elements.prevPage.addEventListener('click', () => {
1061
+ if (config.pagination.currentPage > 1) {
1062
+ config.pagination.currentPage--;
1063
+ ui.loadProductsTable();
1064
+ }
1065
+ });
1066
+
1067
+ elements.nextPage.addEventListener('click', () => {
1068
+ const totalPages = Math.ceil((state.filteredProducts.length || state.products.length) / config.pagination.pageSize);
1069
+ if (config.pagination.currentPage < totalPages) {
1070
+ config.pagination.currentPage++;
1071
+ ui.loadProductsTable();
1072
+ }
1073
+ });
1074
+
1075
+ // Add product form
1076
+ elements.addProductForm.addEventListener('submit', async (e) => {
1077
+ e.preventDefault();
1078
+
1079
+ const productData = {
1080
+ nombre: elements.productName.value,
1081
+ categoria: elements.productCategory.value,
1082
+ precio: parseFloat(elements.productPrice.value),
1083
+ cantidad: parseInt(elements.productQuantity.value),
1084
+ descripcion: elements.productDescription.value
1085
+ };
1086
+
1087
+ try {
1088
+ const newProduct = await api.addProduct(productData);
1089
+
1090
+ // Add product to state
1091
+ state.products.unshift(newProduct);
1092
+
1093
+ // Reset form
1094
+ elements.addProductForm.reset();
1095
+
1096
+ // Reload dashboard or products table
1097
+ if (state.currentSection === 'dashboard-section') {
1098
+ ui.loadDashboardSummary();
1099
+ } else {
1100
+ ui.loadProductsTable();
1101
+ }
1102
+
1103
+ helpers.showAlert('脡xito', 'Producto agregado correctamente');
1104
+ } catch (error) {
1105
+ helpers.handleApiError(error);
1106
+ }
1107
+ });
1108
+
1109
+ // Edit product form
1110
+ elements.editProductForm.addEventListener('submit', async (e) => {
1111
+ e.preventDefault();
1112
+
1113
+ const productId = elements.editProductId.value;
1114
+ const productData = {
1115
+ nombre: elements.editProductName.value,
1116
+ categoria: elements.editProductCategory.value,
1117
+ precio: parseFloat(elements.editProductPrice.value),
1118
+ cantidad: parseInt(elements.editProductQuantity.value),
1119
+ descripcion: elements.editProductDescription.value
1120
+ };
1121
+
1122
+ try {
1123
+ const updatedProduct = await api.updateProduct(productId, productData);
1124
+
1125
+ // Update product in state
1126
+ const index = state.products.findIndex(p => p.id == productId);
1127
+ if (index !== -1) {
1128
+ state.products[index] = updatedProduct;
1129
+ }
1130
+
1131
+ // Update filtered products if needed
1132
+ const filteredIndex = state.filteredProducts.findIndex(p => p.id == productId);
1133
+ if (filteredIndex !== -1) {
1134
+ state.filteredProducts[filteredIndex] = updatedProduct;
1135
+ }
1136
+
1137
+ // Hide modal
1138
+ ui.hideEditProductModal();
1139
+
1140
+ // Reload current view
1141
+ if (state.currentSection === 'dashboard-section') {
1142
+ ui.loadDashboardSummary();
1143
+ } else {
1144
+ ui.loadProductsTable();
1145
+ }
1146
+
1147
+ helpers.showAlert('脡xito', 'Producto actualizado correctamente');
1148
+ } catch (error) {
1149
+ helpers.handleApiError(error);
1150
+ }
1151
+ });
1152
+
1153
+ // Close edit modal buttons
1154
+ elements.closeEditModal.addEventListener('click', ui.hideEditProductModal);
1155
+ elements.cancelEdit.addEventListener('click', ui.hideEditProductModal);
1156
+
1157
+ // Close alert
1158
+ elements.closeAlert.addEventListener('click', () => {
1159
+ elements.alertNotification.classList.add('hidden');
1160
+ });
1161
+ };
1162
+
1163
+ // Initialize the application
1164
+ const init = () => {
1165
+ setupEventListeners();
1166
+
1167
+ // For demo purposes, pre-fill login form
1168
+ elements.username.value = 'admin';
1169
+ elements.password.value = 'admin123';
1170
+ };
1171
+
1172
+ // Start the app when DOM is loaded
1173
+ document.addEventListener('DOMContentLoaded', init);
1174
+ </script>
1175
+ <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-v2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1176
+ </html>
prompts.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Crea una p谩gina web moderna tipo dashboard para un sistema de gesti贸n de inventario, con las siguientes funcionalidades: Login: Pantalla de inicio de sesi贸n con autenticaci贸n b谩sica (usuario y contrase帽a). Validaci贸n de usuario y contrase帽a en el frontend. Roles: "admin" y "usuario". El rol admin puede crear, editar y eliminar productos. El rol usuario solo puede ver y agregar productos. Dashboard: Despu茅s del login, mostrar un dashboard tipo panel administrativo (moderno, elegante y responsive). Incluir una barra lateral con navegaci贸n entre: Resumen del inventario Lista de productos Agregar producto Cerrar sesi贸n Funcionalidades: Mostrar tabla de productos con nombre, categor铆a, precio, cantidad. El bot贸n "Agregar producto" debe mostrar un formulario con campos: nombre categor铆a (select con opciones: Electr贸nicos, Ropa, Alimentos, Oficina, Hogar) precio cantidad descripci贸n Al hacer clic en "Guardar producto", los datos deben enviarse como un POST JSON tipo raw al endpoint /api/productos. Incluir el c贸digo JavaScript que permita enviar la solicitud con fetch() y deje claramente un espacio comentado donde se puede configurar la URL de la API (base_url) y el encabezado Authorization para autenticaci贸n b谩sica. Los productos deben poder visualizarse en la tabla si se desea cargarlos desde la misma API (GET /api/productos), usando fetch(). Estilo: Dise帽o moderno y elegante usando Tailwind CSS o Bootstrap. Debe ser responsive (adaptado a m贸viles y escritorio). Mostrar alertas de 茅xito o error despu茅s de enviar productos. Esta p谩gina debe estar completamente lista para conectarse con una API backend desarrollada en Java Spring Boot que se conecta a una base de datos MySQL gestionada desde HeidiSQL. Incluir comentarios dentro del c贸digo que expliquen d贸nde configurar la URL de la API y c贸mo manejar el env铆o con autenticaci贸n b谩sica.