TrainHeartX commited on
Commit
7fb76fd
·
verified ·
1 Parent(s): 6fdc950

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1325 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Nuevo
3
- emoji: 🚀
4
- colorFrom: indigo
5
- colorTo: gray
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: nuevo
3
+ emoji: 🐳
4
+ colorFrom: green
5
+ colorTo: purple
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,1325 @@
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>Sistema de Feedback Avanzado</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
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
11
+
12
+ body {
13
+ font-family: 'Inter', sans-serif;
14
+ }
15
+
16
+ .fade-in {
17
+ animation: fadeIn 0.3s ease-in-out;
18
+ }
19
+
20
+ @keyframes fadeIn {
21
+ from { opacity: 0; transform: translateY(10px); }
22
+ to { opacity: 1; transform: translateY(0); }
23
+ }
24
+
25
+ .card-hover:hover {
26
+ transform: translateY(-3px);
27
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
28
+ }
29
+
30
+ textarea {
31
+ resize: none;
32
+ }
33
+
34
+ .progress-bar {
35
+ height: 6px;
36
+ border-radius: 3px;
37
+ transition: width 0.6s ease;
38
+ }
39
+
40
+ .type-tab {
41
+ transition: all 0.3s ease;
42
+ }
43
+
44
+ .type-tab.active {
45
+ border-bottom: 3px solid;
46
+ font-weight: 600;
47
+ }
48
+
49
+ .priority-high {
50
+ border-left: 4px solid #ef4444;
51
+ }
52
+
53
+ .priority-medium {
54
+ border-left: 4px solid #f59e0b;
55
+ }
56
+
57
+ .priority-low {
58
+ border-left: 4px solid #10b981;
59
+ }
60
+
61
+ .status-pending {
62
+ background-color: #fef3c7;
63
+ color: #d97706;
64
+ }
65
+
66
+ .status-resolved {
67
+ background-color: #d1fae5;
68
+ color: #059669;
69
+ }
70
+
71
+ .status-in-progress {
72
+ background-color: #dbeafe;
73
+ color: #2563eb;
74
+ }
75
+
76
+ .chart-container {
77
+ height: 300px;
78
+ }
79
+
80
+ .counter-card {
81
+ transition: all 0.3s ease;
82
+ }
83
+
84
+ .counter-card:hover {
85
+ transform: translateY(-5px);
86
+ }
87
+
88
+ .glow {
89
+ box-shadow: 0 0 15px rgba(99, 102, 241, 0.2);
90
+ }
91
+ </style>
92
+ </head>
93
+ <body class="bg-gray-50 min-h-screen">
94
+ <div class="container mx-auto px-4 py-8 max-w-7xl">
95
+ <!-- Header con estadísticas -->
96
+ <header class="mb-10">
97
+ <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
98
+ <div>
99
+ <h1 class="text-4xl font-bold text-indigo-700 mb-2">Sistema de Feedback</h1>
100
+ <p class="text-gray-600">Registro y seguimiento de comentarios, bugs y solicitudes</p>
101
+ </div>
102
+ <div class="mt-4 md:mt-0 flex space-x-3">
103
+ <button onclick="openExportModal()" class="px-4 py-2 bg-gray-800 text-white rounded-lg hover:bg-gray-700 transition flex items-center">
104
+ <i class="fas fa-file-export mr-2"></i> Exportar
105
+ </button>
106
+ <button onclick="openAnalyticsModal()" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center glow">
107
+ <i class="fas fa-chart-pie mr-2"></i> Analytics
108
+ </button>
109
+ </div>
110
+ </div>
111
+
112
+ <!-- Estadísticas principales -->
113
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
114
+ <div class="bg-white rounded-xl shadow p-5 counter-card">
115
+ <div class="flex justify-between items-start">
116
+ <div>
117
+ <p class="text-sm text-gray-500 uppercase tracking-wider">Total Registros</p>
118
+ <h3 class="text-2xl font-bold" id="totalCount">0</h3>
119
+ <p class="text-xs text-gray-500 mt-1">Último: <span id="lastRecordDate">N/A</span></p>
120
+ </div>
121
+ <div class="p-3 rounded-full bg-indigo-100 text-indigo-600">
122
+ <i class="fas fa-list"></i>
123
+ </div>
124
+ </div>
125
+ <div class="mt-4">
126
+ <div class="flex justify-between text-xs text-gray-500 mb-1">
127
+ <span>Abiertos</span>
128
+ <span id="openCount">0</span>
129
+ </div>
130
+ <div class="h-1 w-full bg-gray-200 rounded-full">
131
+ <div class="h-1 bg-indigo-500 rounded-full progress-bar" id="openProgress"></div>
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ <div class="bg-white rounded-xl shadow p-5 counter-card">
137
+ <div class="flex justify-between items-start">
138
+ <div>
139
+ <p class="text-sm text-gray-500 uppercase tracking-wider">Bugs Reportados</p>
140
+ <h3 class="text-2xl font-bold" id="bugsCount">0</h3>
141
+ <p class="text-xs text-gray-500 mt-1">Resueltos: <span id="resolvedBugsCount">0</span></p>
142
+ </div>
143
+ <div class="p-3 rounded-full bg-red-100 text-red-600">
144
+ <i class="fas fa-bug"></i>
145
+ </div>
146
+ </div>
147
+ <div class="mt-4">
148
+ <div class="flex justify-between text-xs text-gray-500 mb-1">
149
+ <span>Prioridad Alta</span>
150
+ <span id="highPriorityBugs">0</span>
151
+ </div>
152
+ <div class="h-1 w-full bg-gray-200 rounded-full">
153
+ <div class="h-1 bg-red-500 rounded-full progress-bar" id="bugsProgress"></div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+
158
+ <div class="bg-white rounded-xl shadow p-5 counter-card">
159
+ <div class="flex justify-between items-start">
160
+ <div>
161
+ <p class="text-sm text-gray-500 uppercase tracking-wider">Solicitudes</p>
162
+ <h3 class="text-2xl font-bold" id="requestsCount">0</h3>
163
+ <p class="text-xs text-gray-500 mt-1">Implementadas: <span id="implementedRequests">0</span></p>
164
+ </div>
165
+ <div class="p-3 rounded-full bg-purple-100 text-purple-600">
166
+ <i class="fas fa-lightbulb"></i>
167
+ </div>
168
+ </div>
169
+ <div class="mt-4">
170
+ <div class="flex justify-between text-xs text-gray-500 mb-1">
171
+ <span>En progreso</span>
172
+ <span id="inProgressRequests">0</span>
173
+ </div>
174
+ <div class="h-1 w-full bg-gray-200 rounded-full">
175
+ <div class="h-1 bg-purple-500 rounded-full progress-bar" id="requestsProgress"></div>
176
+ </div>
177
+ </div>
178
+ </div>
179
+
180
+ <div class="bg-white rounded-xl shadow p-5 counter-card">
181
+ <div class="flex justify-between items-start">
182
+ <div>
183
+ <p class="text-sm text-gray-500 uppercase tracking-wider">Resueltos</p>
184
+ <h3 class="text-2xl font-bold" id="resolvedCount">0</h3>
185
+ <p class="text-xs text-gray-500 mt-1">Tiempo promedio: <span id="avgResolutionTime">0d</span></p>
186
+ </div>
187
+ <div class="p-3 rounded-full bg-green-100 text-green-600">
188
+ <i class="fas fa-check-circle"></i>
189
+ </div>
190
+ </div>
191
+ <div class="mt-4">
192
+ <div class="flex justify-between text-xs text-gray-500 mb-1">
193
+ <span>Últimos 7 días</span>
194
+ <span id="lastWeekResolved">0</span>
195
+ </div>
196
+ <div class="h-1 w-full bg-gray-200 rounded-full">
197
+ <div class="h-1 bg-green-500 rounded-full progress-bar" id="resolvedProgress"></div>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ </div>
202
+
203
+ <!-- Estadísticas secundarias -->
204
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
205
+ <div class="bg-white rounded-xl shadow p-4 flex items-center">
206
+ <div class="p-3 rounded-full bg-orange-100 text-orange-600 mr-3">
207
+ <i class="fas fa-exclamation-triangle"></i>
208
+ </div>
209
+ <div>
210
+ <p class="text-sm text-gray-500">Quejas</p>
211
+ <h3 class="text-xl font-bold" id="complaintsCount">0</h3>
212
+ </div>
213
+ </div>
214
+
215
+ <div class="bg-white rounded-xl shadow p-4 flex items-center">
216
+ <div class="p-3 rounded-full bg-yellow-100 text-yellow-600 mr-3">
217
+ <i class="fas fa-star"></i>
218
+ </div>
219
+ <div>
220
+ <p class="text-sm text-gray-500">Elogios</p>
221
+ <h3 class="text-xl font-bold" id="praisesCount">0</h3>
222
+ </div>
223
+ </div>
224
+
225
+ <div class="bg-white rounded-xl shadow p-4 flex items-center">
226
+ <div class="p-3 rounded-full bg-blue-100 text-blue-600 mr-3">
227
+ <i class="fas fa-comment-dots"></i>
228
+ </div>
229
+ <div>
230
+ <p class="text-sm text-gray-500">Sugerencias</p>
231
+ <h3 class="text-xl font-bold" id="suggestionsCount">0</h3>
232
+ </div>
233
+ </div>
234
+
235
+ <div class="bg-white rounded-xl shadow p-4 flex items-center">
236
+ <div class="p-3 rounded-full bg-pink-100 text-pink-600 mr-3">
237
+ <i class="fas fa-user-tie"></i>
238
+ </div>
239
+ <div>
240
+ <p class="text-sm text-gray-500">Usuarios únicos</p>
241
+ <h3 class="text-xl font-bold" id="uniqueUsersCount">0</h3>
242
+ </div>
243
+ </div>
244
+ </div>
245
+ </header>
246
+
247
+ <!-- Pestañas de tipos -->
248
+ <div class="flex overflow-x-auto mb-6 bg-white rounded-lg shadow-sm">
249
+ <button onclick="filterByType('all')" class="type-tab active px-6 py-3 text-sm font-medium text-center border-b-2 border-transparent hover:text-gray-600" data-type="all">
250
+ <i class="fas fa-layer-group mr-2"></i> Todos
251
+ </button>
252
+ <button onclick="filterByType('bug')" class="type-tab px-6 py-3 text-sm font-medium text-center border-b-2 border-transparent hover:text-gray-600" data-type="bug">
253
+ <i class="fas fa-bug mr-2"></i> Bugs
254
+ </button>
255
+ <button onclick="filterByType('request')" class="type-tab px-6 py-3 text-sm font-medium text-center border-b-2 border-transparent hover:text-gray-600" data-type="request">
256
+ <i class="fas fa-lightbulb mr-2"></i> Solicitudes
257
+ </button>
258
+ <button onclick="filterByType('complaint')" class="type-tab px-6 py-3 text-sm font-medium text-center border-b-2 border-transparent hover:text-gray-600" data-type="complaint">
259
+ <i class="fas fa-exclamation-triangle mr-2"></i> Quejas
260
+ </button>
261
+ <button onclick="filterByType('praise')" class="type-tab px-6 py-3 text-sm font-medium text-center border-b-2 border-transparent hover:text-gray-600" data-type="praise">
262
+ <i class="fas fa-star mr-2"></i> Elogios
263
+ </button>
264
+ <button onclick="filterByType('suggestion')" class="type-tab px-6 py-3 text-sm font-medium text-center border-b-2 border-transparent hover:text-gray-600" data-type="suggestion">
265
+ <i class="fas fa-comment-dots mr-2"></i> Sugerencias
266
+ </button>
267
+ </div>
268
+
269
+ <!-- Contenido principal -->
270
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
271
+ <!-- Formulario -->
272
+ <div class="lg:col-span-1">
273
+ <div class="bg-white rounded-xl shadow-md p-6 sticky top-6 transition-all duration-300 hover:shadow-lg">
274
+ <h2 class="text-2xl font-semibold text-gray-800 mb-6 flex items-center">
275
+ <i class="fas fa-plus-circle mr-3 text-indigo-500"></i> Nuevo Registro
276
+ </h2>
277
+
278
+ <form id="feedbackForm" class="space-y-4">
279
+ <div>
280
+ <label for="name" class="block text-sm font-medium text-gray-700 mb-1">Nombre</label>
281
+ <input type="text" id="name" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="Tu nombre" required>
282
+ </div>
283
+
284
+ <div>
285
+ <label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email</label>
286
+ <input type="email" id="email" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="tu@email.com" required>
287
+ </div>
288
+
289
+ <div class="grid grid-cols-2 gap-4">
290
+ <div>
291
+ <label for="type" class="block text-sm font-medium text-gray-700 mb-1">Tipo</label>
292
+ <select id="type" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition">
293
+ <option value="bug">Bug/Error</option>
294
+ <option value="request">Solicitud</option>
295
+ <option value="complaint">Queja</option>
296
+ <option value="praise">Elogio</option>
297
+ <option value="suggestion">Sugerencia</option>
298
+ </select>
299
+ </div>
300
+
301
+ <div>
302
+ <label for="priority" class="block text-sm font-medium text-gray-700 mb-1">Prioridad</label>
303
+ <select id="priority" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition">
304
+ <option value="low">Baja</option>
305
+ <option value="medium" selected>Media</option>
306
+ <option value="high">Alta</option>
307
+ </select>
308
+ </div>
309
+ </div>
310
+
311
+ <div>
312
+ <label for="message" class="block text-sm font-medium text-gray-700 mb-1">Mensaje</label>
313
+ <textarea id="message" rows="5" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="Describe con detalle..." required></textarea>
314
+ </div>
315
+
316
+ <div id="bugDetails" class="hidden space-y-4">
317
+ <div>
318
+ <label for="browser" class="block text-sm font-medium text-gray-700 mb-1">Navegador</label>
319
+ <input type="text" id="browser" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="Ej: Chrome 112">
320
+ </div>
321
+
322
+ <div>
323
+ <label for="os" class="block text-sm font-medium text-gray-700 mb-1">Sistema Operativo</label>
324
+ <input type="text" id="os" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="Ej: Windows 10">
325
+ </div>
326
+
327
+ <div>
328
+ <label for="steps" class="block text-sm font-medium text-gray-700 mb-1">Pasos para reproducir</label>
329
+ <textarea id="steps" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="Describe cómo reproducir el problema..."></textarea>
330
+ </div>
331
+ </div>
332
+
333
+ <div class="flex justify-end space-x-3 pt-2">
334
+ <button type="reset" class="px-6 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition">
335
+ <i class="fas fa-eraser mr-2"></i> Limpiar
336
+ </button>
337
+ <button type="submit" class="px-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center">
338
+ <i class="fas fa-paper-plane mr-2"></i> Enviar
339
+ </button>
340
+ </div>
341
+ </form>
342
+ </div>
343
+ </div>
344
+
345
+ <!-- Lista de Registros -->
346
+ <div class="lg:col-span-2">
347
+ <div class="bg-white rounded-xl shadow-md p-6">
348
+ <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
349
+ <h2 class="text-2xl font-semibold text-gray-800 flex items-center">
350
+ <i class="fas fa-list-ul mr-3 text-indigo-500"></i> Registros Recientes
351
+ </h2>
352
+
353
+ <div class="mt-3 md:mt-0 flex space-x-2">
354
+ <div class="relative">
355
+ <select id="filterStatus" onchange="renderFeedbacks()" class="appearance-none px-4 py-2 pr-8 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition text-sm">
356
+ <option value="all">Todos los estados</option>
357
+ <option value="pending">Pendientes</option>
358
+ <option value="in-progress">En progreso</option>
359
+ <option value="resolved">Resueltos</option>
360
+ </select>
361
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
362
+ <i class="fas fa-chevron-down text-sm"></i>
363
+ </div>
364
+ </div>
365
+
366
+ <div class="relative">
367
+ <select id="filterPriority" onchange="renderFeedbacks()" class="appearance-none px-4 py-2 pr-8 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition text-sm">
368
+ <option value="all">Todas las prioridades</option>
369
+ <option value="high">Alta</option>
370
+ <option value="medium">Media</option>
371
+ <option value="low">Baja</option>
372
+ </select>
373
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
374
+ <i class="fas fa-chevron-down text-sm"></i>
375
+ </div>
376
+ </div>
377
+ </div>
378
+ </div>
379
+
380
+ <div id="feedbackList" class="space-y-4">
381
+ <!-- Los registros aparecerán aquí dinámicamente -->
382
+ <div class="text-center py-10 text-gray-500">
383
+ <i class="fas fa-inbox text-4xl mb-3 opacity-50"></i>
384
+ <p>No hay registros aún. Agrega tu primer comentario o reporte.</p>
385
+ </div>
386
+ </div>
387
+
388
+ <div class="mt-6 flex justify-between items-center">
389
+ <div class="text-sm text-gray-500" id="showingCount">
390
+ Mostrando 0 de 0 registros
391
+ </div>
392
+ <div class="flex space-x-2">
393
+ <button id="prevPage" onclick="changePage(-1)" disabled class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition disabled:opacity-50">
394
+ <i class="fas fa-chevron-left"></i>
395
+ </button>
396
+ <button id="nextPage" onclick="changePage(1)" disabled class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition disabled:opacity-50">
397
+ <i class="fas fa-chevron-right"></i>
398
+ </button>
399
+ </div>
400
+ </div>
401
+ </div>
402
+ </div>
403
+ </div>
404
+ </div>
405
+
406
+ <!-- Modal de Exportación -->
407
+ <div id="exportModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
408
+ <div class="bg-white rounded-xl shadow-xl p-6 w-full max-w-md fade-in">
409
+ <div class="flex justify-between items-center mb-4">
410
+ <h3 class="text-xl font-bold text-gray-800">Exportar Datos</h3>
411
+ <button onclick="closeExportModal()" class="text-gray-500 hover:text-gray-700">
412
+ <i class="fas fa-times"></i>
413
+ </button>
414
+ </div>
415
+
416
+ <div class="space-y-4">
417
+ <div>
418
+ <label class="block text-sm font-medium text-gray-700 mb-1">Formato</label>
419
+ <div class="grid grid-cols-2 gap-3">
420
+ <button onclick="exportData('json')" class="px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition flex items-center justify-center">
421
+ <i class="fas fa-file-code text-blue-500 mr-2"></i> JSON
422
+ </button>
423
+ <button onclick="exportData('csv')" class="px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition flex items-center justify-center">
424
+ <i class="fas fa-file-csv text-green-500 mr-2"></i> CSV
425
+ </button>
426
+ </div>
427
+ </div>
428
+
429
+ <div>
430
+ <label class="block text-sm font-medium text-gray-700 mb-1">Filtros</label>
431
+ <select id="exportFilter" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition">
432
+ <option value="all">Todos los registros</option>
433
+ <option value="bug">Solo Bugs</option>
434
+ <option value="request">Solo Solicitudes</option>
435
+ <option value="complaint">Solo Quejas</option>
436
+ <option value="praise">Solo Elogios</option>
437
+ <option value="suggestion">Solo Sugerencias</option>
438
+ </select>
439
+ </div>
440
+ </div>
441
+ </div>
442
+ </div>
443
+
444
+ <!-- Modal de Analytics -->
445
+ <div id="analyticsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
446
+ <div class="bg-white rounded-xl shadow-xl p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto fade-in">
447
+ <div class="flex justify-between items-center mb-4">
448
+ <h3 class="text-xl font-bold text-gray-800">Analíticas de Feedback</h3>
449
+ <button onclick="closeAnalyticsModal()" class="text-gray-500 hover:text-gray-700">
450
+ <i class="fas fa-times"></i>
451
+ </button>
452
+ </div>
453
+
454
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
455
+ <div class="bg-white p-4 rounded-lg border border-gray-200">
456
+ <h4 class="font-medium text-gray-700 mb-3">Distribución por Tipo</h4>
457
+ <div class="chart-container" id="typeChart"></div>
458
+ </div>
459
+
460
+ <div class="bg-white p-4 rounded-lg border border-gray-200">
461
+ <h4 class="font-medium text-gray-700 mb-3">Estado de Tickets</h4>
462
+ <div class="chart-container" id="statusChart"></div>
463
+ </div>
464
+ </div>
465
+
466
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
467
+ <div class="bg-white p-4 rounded-lg border border-gray-200">
468
+ <h4 class="font-medium text-gray-700 mb-3">Prioridades</h4>
469
+ <div class="chart-container" id="priorityChart"></div>
470
+ </div>
471
+
472
+ <div class="bg-white p-4 rounded-lg border border-gray-200">
473
+ <h4 class="font-medium text-gray-700 mb-3">Actividad Reciente</h4>
474
+ <div class="chart-container" id="activityChart"></div>
475
+ </div>
476
+ </div>
477
+
478
+ <div class="mt-6 pt-4 border-t border-gray-200">
479
+ <h4 class="font-medium text-gray-700 mb-3">Top Usuarios</h4>
480
+ <div id="topUsers" class="space-y-2">
481
+ <!-- Se llenará dinámicamente -->
482
+ </div>
483
+ </div>
484
+ </div>
485
+ </div>
486
+
487
+ <!-- Modal de Detalles -->
488
+ <div id="detailModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
489
+ <div class="bg-white rounded-xl shadow-xl p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto fade-in">
490
+ <div class="flex justify-between items-center mb-4">
491
+ <h3 class="text-xl font-bold text-gray-800">Detalles del Registro</h3>
492
+ <button onclick="closeDetailModal()" class="text-gray-500 hover:text-gray-700">
493
+ <i class="fas fa-times"></i>
494
+ </button>
495
+ </div>
496
+
497
+ <div id="modalContent" class="space-y-4">
498
+ <!-- Contenido dinámico -->
499
+ </div>
500
+
501
+ <div class="mt-6 pt-4 border-t border-gray-200 flex justify-end space-x-3">
502
+ <button onclick="changeStatus('pending')" class="px-4 py-2 bg-yellow-100 text-yellow-800 rounded-lg hover:bg-yellow-200 transition">
503
+ <i class="fas fa-clock mr-2"></i> Pendiente
504
+ </button>
505
+ <button onclick="changeStatus('in-progress')" class="px-4 py-2 bg-blue-100 text-blue-800 rounded-lg hover:bg-blue-200 transition">
506
+ <i class="fas fa-spinner mr-2"></i> En Progreso
507
+ </button>
508
+ <button onclick="changeStatus('resolved')" class="px-4 py-2 bg-green-100 text-green-800 rounded-lg hover:bg-green-200 transition">
509
+ <i class="fas fa-check mr-2"></i> Resuelto
510
+ </button>
511
+ </div>
512
+ </div>
513
+ </div>
514
+
515
+ <script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
516
+ <script>
517
+ // Variables globales
518
+ let feedbacks = JSON.parse(localStorage.getItem('feedbacks')) || [];
519
+ let currentPage = 1;
520
+ const itemsPerPage = 5;
521
+ let currentFilterType = 'all';
522
+ let currentFilterStatus = 'all';
523
+ let currentFilterPriority = 'all';
524
+ let currentDetailId = null;
525
+
526
+ // Inicialización
527
+ document.addEventListener('DOMContentLoaded', function() {
528
+ // Manejar el envío del formulario
529
+ document.getElementById('feedbackForm').addEventListener('submit', handleFormSubmit);
530
+
531
+ // Mostrar campos adicionales para bugs
532
+ document.getElementById('type').addEventListener('change', function() {
533
+ const bugDetails = document.getElementById('bugDetails');
534
+ bugDetails.classList.toggle('hidden', this.value !== 'bug');
535
+ });
536
+
537
+ // Cargar datos iniciales
538
+ updateStats();
539
+ renderFeedbacks();
540
+
541
+ // Agregar algunos datos de ejemplo si está vacío
542
+ if (feedbacks.length === 0) {
543
+ addSampleData();
544
+ }
545
+ });
546
+
547
+ // Agregar datos de ejemplo
548
+ function addSampleData() {
549
+ const sampleFeedbacks = [
550
+ {
551
+ id: 1,
552
+ name: "Juan Pérez",
553
+ email: "juan@example.com",
554
+ type: "bug",
555
+ priority: "high",
556
+ message: "El botón de enviar no funciona en la página de contacto",
557
+ date: "2023-05-15 10:30",
558
+ status: "pending",
559
+ browser: "Chrome 112",
560
+ os: "Windows 10",
561
+ steps: "1. Ir a la página de contacto\n2. Llenar el formulario\n3. Intentar hacer clic en enviar"
562
+ },
563
+ {
564
+ id: 2,
565
+ name: "María Gómez",
566
+ email: "maria@example.com",
567
+ type: "request",
568
+ priority: "medium",
569
+ message: "Sería útil tener un buscador en la sección de productos",
570
+ date: "2023-05-14 15:45",
571
+ status: "in-progress"
572
+ },
573
+ {
574
+ id: 3,
575
+ name: "Carlos Ruiz",
576
+ email: "carlos@example.com",
577
+ type: "complaint",
578
+ priority: "high",
579
+ message: "El tiempo de carga de la página es demasiado lento",
580
+ date: "2023-05-13 09:20",
581
+ status: "pending"
582
+ },
583
+ {
584
+ id: 4,
585
+ name: "Ana López",
586
+ email: "ana@example.com",
587
+ type: "praise",
588
+ priority: "low",
589
+ message: "Excelente servicio al cliente, muy atentos y resolvieron mi problema rápidamente",
590
+ date: "2023-05-12 14:10",
591
+ status: "resolved"
592
+ },
593
+ {
594
+ id: 5,
595
+ name: "Luis Martínez",
596
+ email: "luis@example.com",
597
+ type: "suggestion",
598
+ priority: "low",
599
+ message: "Podrían agregar más métodos de pago como PayPal",
600
+ date: "2023-05-11 11:25",
601
+ status: "pending"
602
+ },
603
+ {
604
+ id: 6,
605
+ name: "Sofía Ramírez",
606
+ email: "sofia@example.com",
607
+ type: "bug",
608
+ priority: "medium",
609
+ message: "El menú desplegable no se muestra correctamente en móviles",
610
+ date: "2023-05-10 16:50",
611
+ status: "resolved",
612
+ browser: "Safari 15",
613
+ os: "iOS 15",
614
+ steps: "1. Abrir el sitio en un iPhone\n2. Intentar abrir el menú principal"
615
+ }
616
+ ];
617
+
618
+ feedbacks = sampleFeedbacks;
619
+ localStorage.setItem('feedbacks', JSON.stringify(feedbacks));
620
+ updateStats();
621
+ renderFeedbacks();
622
+ }
623
+
624
+ // Manejar el envío del formulario
625
+ function handleFormSubmit(e) {
626
+ e.preventDefault();
627
+
628
+ // Obtener valores del formulario
629
+ const name = document.getElementById('name').value;
630
+ const email = document.getElementById('email').value;
631
+ const type = document.getElementById('type').value;
632
+ const priority = document.getElementById('priority').value;
633
+ const message = document.getElementById('message').value;
634
+ const date = new Date().toISOString().slice(0, 19).replace('T', ' ');
635
+
636
+ // Datos adicionales para bugs
637
+ const bugData = type === 'bug' ? {
638
+ browser: document.getElementById('browser').value,
639
+ os: document.getElementById('os').value,
640
+ steps: document.getElementById('steps').value
641
+ } : {};
642
+
643
+ // Crear nuevo registro
644
+ const newFeedback = {
645
+ id: Date.now(),
646
+ name,
647
+ email,
648
+ type,
649
+ priority,
650
+ message,
651
+ date,
652
+ status: 'pending',
653
+ ...bugData
654
+ };
655
+
656
+ // Agregar al array y guardar en localStorage
657
+ feedbacks.unshift(newFeedback);
658
+ localStorage.setItem('feedbacks', JSON.stringify(feedbacks));
659
+
660
+ // Renderizar la lista actualizada
661
+ renderFeedbacks();
662
+ updateStats();
663
+
664
+ // Resetear el formulario
665
+ e.target.reset();
666
+
667
+ // Mostrar notificación
668
+ showNotification('Registro guardado correctamente', 'success');
669
+ }
670
+
671
+ // Renderizar la lista de feedbacks
672
+ function renderFeedbacks() {
673
+ const feedbackList = document.getElementById('feedbackList');
674
+
675
+ // Obtener filtros actuales
676
+ currentFilterStatus = document.getElementById('filterStatus').value;
677
+ currentFilterPriority = document.getElementById('filterPriority').value;
678
+
679
+ // Aplicar filtros
680
+ let filteredFeedbacks = feedbacks.filter(feedback => {
681
+ const typeMatch = currentFilterType === 'all' || feedback.type === currentFilterType;
682
+ const statusMatch = currentFilterStatus === 'all' || feedback.status === currentFilterStatus;
683
+ const priorityMatch = currentFilterPriority === 'all' || feedback.priority === currentFilterPriority;
684
+ return typeMatch && statusMatch && priorityMatch;
685
+ });
686
+
687
+ // Paginación
688
+ const totalPages = Math.ceil(filteredFeedbacks.length / itemsPerPage);
689
+ const paginatedFeedbacks = filteredFeedbacks.slice(
690
+ (currentPage - 1) * itemsPerPage,
691
+ currentPage * itemsPerPage
692
+ );
693
+
694
+ // Actualizar controles de paginación
695
+ document.getElementById('prevPage').disabled = currentPage <= 1;
696
+ document.getElementById('nextPage').disabled = currentPage >= totalPages;
697
+ document.getElementById('showingCount').textContent =
698
+ `Mostrando ${paginatedFeedbacks.length} de ${filteredFeedbacks.length} registros`;
699
+
700
+ // Mostrar mensaje si no hay registros
701
+ if (paginatedFeedbacks.length === 0) {
702
+ feedbackList.innerHTML = `
703
+ <div class="text-center py-10 text-gray-500">
704
+ <i class="fas fa-inbox text-4xl mb-3 opacity-50"></i>
705
+ <p>No hay registros ${currentFilterType !== 'all' ? 'de este tipo' : ''}.</p>
706
+ </div>
707
+ `;
708
+ return;
709
+ }
710
+
711
+ // Generar HTML para cada registro
712
+ feedbackList.innerHTML = paginatedFeedbacks.map(feedback => `
713
+ <div class="bg-white border border-gray-200 rounded-lg p-5 card-hover transition-all duration-300 fade-in ${'priority-' + feedback.priority}">
714
+ <div class="flex justify-between items-start mb-3">
715
+ <div>
716
+ <h3 class="font-semibold text-lg text-gray-800">${feedback.name}</h3>
717
+ <p class="text-sm text-gray-500">${feedback.email}</p>
718
+ </div>
719
+ <div class="flex items-center space-x-2">
720
+ <span class="px-2 py-1 text-xs rounded-full ${getTypeClass(feedback.type)}">
721
+ ${getTypeLabel(feedback.type)}
722
+ </span>
723
+ <span class="px-2 py-1 text-xs rounded-full ${getStatusClass(feedback.status)}">
724
+ ${getStatusLabel(feedback.status)}
725
+ </span>
726
+ <span class="text-xs text-gray-400">${formatDate(feedback.date)}</span>
727
+ </div>
728
+ </div>
729
+ <p class="text-gray-700 mb-4">${feedback.message}</p>
730
+ <div class="flex justify-between items-center">
731
+ <span class="text-xs px-2 py-1 rounded-full ${getPriorityClass(feedback.priority)}">
732
+ <i class="fas ${getPriorityIcon(feedback.priority)} mr-1"></i>
733
+ ${getPriorityLabel(feedback.priority)}
734
+ </span>
735
+ <div class="flex space-x-2">
736
+ <button onclick="viewDetails(${feedback.id})" class="text-indigo-500 hover:text-indigo-700 transition text-sm flex items-center">
737
+ <i class="fas fa-eye mr-1"></i> Ver
738
+ </button>
739
+ <button onclick="deleteFeedback(${feedback.id})" class="text-red-500 hover:text-red-700 transition text-sm flex items-center">
740
+ <i class="fas fa-trash-alt mr-1"></i> Eliminar
741
+ </button>
742
+ </div>
743
+ </div>
744
+ </div>
745
+ `).join('');
746
+ }
747
+
748
+ // Formatear fecha para mostrar
749
+ function formatDate(dateString) {
750
+ const date = new Date(dateString);
751
+ return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
752
+ }
753
+
754
+ // Cambiar página
755
+ function changePage(direction) {
756
+ const newPage = currentPage + direction;
757
+ const filteredFeedbacks = getFilteredFeedbacks();
758
+ const totalPages = Math.ceil(filteredFeedbacks.length / itemsPerPage);
759
+
760
+ if (newPage > 0 && newPage <= totalPages) {
761
+ currentPage = newPage;
762
+ renderFeedbacks();
763
+ }
764
+ }
765
+
766
+ // Filtrar por tipo
767
+ function filterByType(type) {
768
+ currentFilterType = type;
769
+ currentPage = 1;
770
+
771
+ // Actualizar pestañas activas
772
+ document.querySelectorAll('.type-tab').forEach(tab => {
773
+ tab.classList.toggle('active', tab.dataset.type === type);
774
+ });
775
+
776
+ renderFeedbacks();
777
+ updateStats();
778
+ }
779
+
780
+ // Obtener feedbacks filtrados
781
+ function getFilteredFeedbacks() {
782
+ return feedbacks.filter(feedback => {
783
+ const typeMatch = currentFilterType === 'all' || feedback.type === currentFilterType;
784
+ const statusMatch = currentFilterStatus === 'all' || feedback.status === currentFilterStatus;
785
+ const priorityMatch = currentFilterPriority === 'all' || feedback.priority === currentFilterPriority;
786
+ return typeMatch && statusMatch && priorityMatch;
787
+ });
788
+ }
789
+
790
+ // Actualizar estadísticas
791
+ function updateStats() {
792
+ const total = feedbacks.length;
793
+ const bugs = feedbacks.filter(f => f.type === 'bug').length;
794
+ const requests = feedbacks.filter(f => f.type === 'request').length;
795
+ const complaints = feedbacks.filter(f => f.type === 'complaint').length;
796
+ const praises = feedbacks.filter(f => f.type === 'praise').length;
797
+ const suggestions = feedbacks.filter(f => f.type === 'suggestion').length;
798
+ const resolved = feedbacks.filter(f => f.status === 'resolved').length;
799
+ const pending = feedbacks.filter(f => f.status === 'pending').length;
800
+ const inProgress = feedbacks.filter(f => f.status === 'in-progress').length;
801
+
802
+ // Contadores principales
803
+ document.getElementById('totalCount').textContent = total;
804
+ document.getElementById('bugsCount').textContent = bugs;
805
+ document.getElementById('requestsCount').textContent = requests;
806
+ document.getElementById('resolvedCount').textContent = resolved;
807
+ document.getElementById('complaintsCount').textContent = complaints;
808
+ document.getElementById('praisesCount').textContent = praises;
809
+ document.getElementById('suggestionsCount').textContent = suggestions;
810
+
811
+ // Estadísticas secundarias
812
+ document.getElementById('openCount').textContent = pending + inProgress;
813
+ document.getElementById('resolvedBugsCount').textContent = feedbacks.filter(f => f.type === 'bug' && f.status === 'resolved').length;
814
+ document.getElementById('highPriorityBugs').textContent = feedbacks.filter(f => f.type === 'bug' && f.priority === 'high').length;
815
+ document.getElementById('implementedRequests').textContent = feedbacks.filter(f => f.type === 'request' && f.status === 'resolved').length;
816
+ document.getElementById('inProgressRequests').textContent = feedbacks.filter(f => f.type === 'request' && f.status === 'in-progress').length;
817
+ document.getElementById('lastWeekResolved').textContent = feedbacks.filter(f => {
818
+ const resolvedDate = new Date(f.date);
819
+ const oneWeekAgo = new Date();
820
+ oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
821
+ return f.status === 'resolved' && resolvedDate > oneWeekAgo;
822
+ }).length;
823
+
824
+ // Fecha del último registro
825
+ if (feedbacks.length > 0) {
826
+ const lastRecord = feedbacks[0];
827
+ document.getElementById('lastRecordDate').textContent = formatDate(lastRecord.date);
828
+ } else {
829
+ document.getElementById('lastRecordDate').textContent = 'N/A';
830
+ }
831
+
832
+ // Usuarios únicos
833
+ const uniqueEmails = [...new Set(feedbacks.map(f => f.email))];
834
+ document.getElementById('uniqueUsersCount').textContent = uniqueEmails.length;
835
+
836
+ // Tiempo promedio de resolución (simulado para el ejemplo)
837
+ const avgDays = feedbacks.length > 0 ? Math.floor(Math.random() * 5) + 1 : 0;
838
+ document.getElementById('avgResolutionTime').textContent = `${avgDays}d`;
839
+
840
+ // Actualizar barras de progreso
841
+ document.getElementById('bugsProgress').style.width = total > 0 ? `${(bugs / total) * 100}%` : '0%';
842
+ document.getElementById('requestsProgress').style.width = total > 0 ? `${(requests / total) * 100}%` : '0%';
843
+ document.getElementById('resolvedProgress').style.width = total > 0 ? `${(resolved / total) * 100}%` : '0%';
844
+ document.getElementById('openProgress').style.width = total > 0 ? `${((pending + inProgress) / total) * 100}%` : '0%';
845
+ }
846
+
847
+ // Ver detalles
848
+ function viewDetails(id) {
849
+ const feedback = feedbacks.find(f => f.id === id);
850
+ if (!feedback) return;
851
+
852
+ currentDetailId = id;
853
+
854
+ // Construir contenido del modal
855
+ let detailsHTML = `
856
+ <div class="space-y-4">
857
+ <div>
858
+ <h4 class="text-sm font-medium text-gray-500">Información Básica</h4>
859
+ <div class="mt-2 grid grid-cols-2 gap-4">
860
+ <div>
861
+ <p class="text-xs text-gray-500">Nombre</p>
862
+ <p class="font-medium">${feedback.name}</p>
863
+ </div>
864
+ <div>
865
+ <p class="text-xs text-gray-500">Email</p>
866
+ <p class="font-medium">${feedback.email}</p>
867
+ </div>
868
+ <div>
869
+ <p class="text-xs text-gray-500">Tipo</p>
870
+ <p class="font-medium">
871
+ <span class="px-2 py-1 text-xs rounded-full ${getTypeClass(feedback.type)}">
872
+ ${getTypeLabel(feedback.type)}
873
+ </span>
874
+ </p>
875
+ </div>
876
+ <div>
877
+ <p class="text-xs text-gray-500">Prioridad</p>
878
+ <p class="font-medium">
879
+ <span class="px-2 py-1 text-xs rounded-full ${getPriorityClass(feedback.priority)}">
880
+ ${getPriorityLabel(feedback.priority)}
881
+ </span>
882
+ </p>
883
+ </div>
884
+ <div>
885
+ <p class="text-xs text-gray-500">Fecha</p>
886
+ <p class="font-medium">${formatDate(feedback.date)}</p>
887
+ </div>
888
+ <div>
889
+ <p class="text-xs text-gray-500">Estado</p>
890
+ <p class="font-medium">
891
+ <span class="px-2 py-1 text-xs rounded-full ${getStatusClass(feedback.status)}">
892
+ ${getStatusLabel(feedback.status)}
893
+ </span>
894
+ </p>
895
+ </div>
896
+ </div>
897
+ </div>
898
+
899
+ <div>
900
+ <h4 class="text-sm font-medium text-gray-500">Mensaje</h4>
901
+ <div class="mt-2 bg-gray-50 p-4 rounded-lg">
902
+ <p class="whitespace-pre-line">${feedback.message}</p>
903
+ </div>
904
+ </div>
905
+ `;
906
+
907
+ // Mostrar detalles adicionales para bugs
908
+ if (feedback.type === 'bug') {
909
+ detailsHTML += `
910
+ <div>
911
+ <h4 class="text-sm font-medium text-gray-500">Detalles del Bug</h4>
912
+ <div class="mt-2 grid grid-cols-2 gap-4">
913
+ <div>
914
+ <p class="text-xs text-gray-500">Navegador</p>
915
+ <p class="font-medium">${feedback.browser || 'No especificado'}</p>
916
+ </div>
917
+ <div>
918
+ <p class="text-xs text-gray-500">Sistema Operativo</p>
919
+ <p class="font-medium">${feedback.os || 'No especificado'}</p>
920
+ </div>
921
+ </div>
922
+
923
+ <div class="mt-4">
924
+ <p class="text-xs text-gray-500">Pasos para reproducir</p>
925
+ <div class="mt-2 bg-gray-50 p-4 rounded-lg">
926
+ <p class="whitespace-pre-line">${feedback.steps || 'No especificado'}</p>
927
+ </div>
928
+ </div>
929
+ </div>
930
+ `;
931
+ }
932
+
933
+ detailsHTML += `</div>`;
934
+
935
+ document.getElementById('modalContent').innerHTML = detailsHTML;
936
+ document.getElementById('detailModal').classList.remove('hidden');
937
+ }
938
+
939
+ // Cerrar modal de detalles
940
+ function closeDetailModal() {
941
+ document.getElementById('detailModal').classList.add('hidden');
942
+ currentDetailId = null;
943
+ }
944
+
945
+ // Cambiar estado de un feedback
946
+ function changeStatus(newStatus) {
947
+ if (!currentDetailId) return;
948
+
949
+ const index = feedbacks.findIndex(f => f.id === currentDetailId);
950
+ if (index !== -1) {
951
+ feedbacks[index].status = newStatus;
952
+ localStorage.setItem('feedbacks', JSON.stringify(feedbacks));
953
+
954
+ showNotification('Estado actualizado correctamente', 'success');
955
+ renderFeedbacks();
956
+ updateStats();
957
+ closeDetailModal();
958
+ }
959
+ }
960
+
961
+ // Eliminar feedback
962
+ function deleteFeedback(id) {
963
+ if (!confirm('¿Estás seguro de eliminar este registro?')) return;
964
+
965
+ feedbacks = feedbacks.filter(feedback => feedback.id !== id);
966
+ localStorage.setItem('feedbacks', JSON.stringify(feedbacks));
967
+
968
+ // Si estamos en la última página y queda vacía, retroceder una página
969
+ const filteredCount = getFilteredFeedbacks().length;
970
+ const maxPage = Math.ceil(filteredCount / itemsPerPage);
971
+ if (currentPage > maxPage && maxPage > 0) {
972
+ currentPage = maxPage;
973
+ }
974
+
975
+ renderFeedbacks();
976
+ updateStats();
977
+
978
+ showNotification('Registro eliminado', 'error');
979
+ }
980
+
981
+ // Mostrar notificación
982
+ function showNotification(message, type) {
983
+ const notification = document.createElement('div');
984
+ notification.className = `fixed bottom-6 right-6 px-6 py-3 rounded-lg shadow-lg text-white flex items-center ${
985
+ type === 'success' ? 'bg-green-500' : 'bg-red-500'
986
+ } fade-in`;
987
+
988
+ notification.innerHTML = `
989
+ <i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'} mr-2"></i>
990
+ ${message}
991
+ `;
992
+
993
+ document.body.appendChild(notification);
994
+
995
+ // Eliminar después de 3 segundos
996
+ setTimeout(() => {
997
+ notification.classList.remove('fade-in');
998
+ notification.classList.add('opacity-0', 'transition-opacity', 'duration-300');
999
+ setTimeout(() => notification.remove(), 300);
1000
+ }, 3000);
1001
+ }
1002
+
1003
+ // Funciones para manejar tipos
1004
+ function getTypeClass(type) {
1005
+ const classes = {
1006
+ bug: 'bg-red-100 text-red-800',
1007
+ request: 'bg-purple-100 text-purple-800',
1008
+ complaint: 'bg-orange-100 text-orange-800',
1009
+ praise: 'bg-green-100 text-green-800',
1010
+ suggestion: 'bg-blue-100 text-blue-800'
1011
+ };
1012
+ return classes[type] || 'bg-gray-100 text-gray-800';
1013
+ }
1014
+
1015
+ function getTypeLabel(type) {
1016
+ const labels = {
1017
+ bug: 'Bug',
1018
+ request: 'Solicitud',
1019
+ complaint: 'Queja',
1020
+ praise: 'Elogio',
1021
+ suggestion: 'Sugerencia'
1022
+ };
1023
+ return labels[type] || type;
1024
+ }
1025
+
1026
+ // Funciones para manejar prioridades
1027
+ function getPriorityClass(priority) {
1028
+ const classes = {
1029
+ high: 'bg-red-100 text-red-800',
1030
+ medium: 'bg-yellow-100 text-yellow-800',
1031
+ low: 'bg-green-100 text-green-800'
1032
+ };
1033
+ return classes[priority] || 'bg-gray-100 text-gray-800';
1034
+ }
1035
+
1036
+ function getPriorityLabel(priority) {
1037
+ const labels = {
1038
+ high: 'Alta',
1039
+ medium: 'Media',
1040
+ low: 'Baja'
1041
+ };
1042
+ return labels[priority] || priority;
1043
+ }
1044
+
1045
+ function getPriorityIcon(priority) {
1046
+ const icons = {
1047
+ high: 'fa-exclamation-circle',
1048
+ medium: 'fa-exclamation-triangle',
1049
+ low: 'fa-info-circle'
1050
+ };
1051
+ return icons[priority] || 'fa-question-circle';
1052
+ }
1053
+
1054
+ // Funciones para manejar estados
1055
+ function getStatusClass(status) {
1056
+ const classes = {
1057
+ pending: 'bg-yellow-100 text-yellow-800',
1058
+ 'in-progress': 'bg-blue-100 text-blue-800',
1059
+ resolved: 'bg-green-100 text-green-800'
1060
+ };
1061
+ return classes[status] || 'bg-gray-100 text-gray-800';
1062
+ }
1063
+
1064
+ function getStatusLabel(status) {
1065
+ const labels = {
1066
+ pending: 'Pendiente',
1067
+ 'in-progress': 'En Progreso',
1068
+ resolved: 'Resuelto'
1069
+ };
1070
+ return labels[status] || status;
1071
+ }
1072
+
1073
+ // Funciones para exportación
1074
+ function openExportModal() {
1075
+ document.getElementById('exportModal').classList.remove('hidden');
1076
+ }
1077
+
1078
+ function closeExportModal() {
1079
+ document.getElementById('exportModal').classList.add('hidden');
1080
+ }
1081
+
1082
+ function exportData(format) {
1083
+ const filter = document.getElementById('exportFilter').value;
1084
+ let dataToExport = feedbacks;
1085
+
1086
+ if (filter !== 'all') {
1087
+ dataToExport = feedbacks.filter(f => f.type === filter);
1088
+ }
1089
+
1090
+ if (format === 'json') {
1091
+ exportJSON(dataToExport);
1092
+ } else if (format === 'csv') {
1093
+ exportCSV(dataToExport);
1094
+ }
1095
+
1096
+ closeExportModal();
1097
+ showNotification('Exportación completada', 'success');
1098
+ }
1099
+
1100
+ function exportJSON(data) {
1101
+ const json = JSON.stringify(data, null, 2);
1102
+ const blob = new Blob([json], { type: 'application/json' });
1103
+ const url = URL.createObjectURL(blob);
1104
+
1105
+ const a = document.createElement('a');
1106
+ a.href = url;
1107
+ a.download = `feedback_${new Date().toISOString().slice(0, 10)}.json`;
1108
+ document.body.appendChild(a);
1109
+ a.click();
1110
+ document.body.removeChild(a);
1111
+ URL.revokeObjectURL(url);
1112
+ }
1113
+
1114
+ function exportCSV(data) {
1115
+ if (data.length === 0) return;
1116
+
1117
+ // Encabezados
1118
+ const headers = Object.keys(data[0]);
1119
+ let csv = headers.join(',') + '\n';
1120
+
1121
+ // Filas
1122
+ data.forEach(item => {
1123
+ const row = headers.map(header => {
1124
+ let value = item[header];
1125
+ // Escapar comas y comillas
1126
+ if (typeof value === 'string') {
1127
+ value = `"${value.replace(/"/g, '""')}"`;
1128
+ }
1129
+ return value;
1130
+ });
1131
+ csv += row.join(',') + '\n';
1132
+ });
1133
+
1134
+ const blob = new Blob([csv], { type: 'text/csv' });
1135
+ const url = URL.createObjectURL(blob);
1136
+
1137
+ const a = document.createElement('a');
1138
+ a.href = url;
1139
+ a.download = `feedback_${new Date().toISOString().slice(0, 10)}.csv`;
1140
+ document.body.appendChild(a);
1141
+ a.click();
1142
+ document.body.removeChild(a);
1143
+ URL.revokeObjectURL(url);
1144
+ }
1145
+
1146
+ // Funciones para analytics
1147
+ function openAnalyticsModal() {
1148
+ document.getElementById('analyticsModal').classList.remove('hidden');
1149
+ renderCharts();
1150
+ }
1151
+
1152
+ function closeAnalyticsModal() {
1153
+ document.getElementById('analyticsModal').classList.add('hidden');
1154
+ }
1155
+
1156
+ function renderCharts() {
1157
+ // Datos para los gráficos
1158
+ const typeData = [
1159
+ feedbacks.filter(f => f.type === 'bug').length,
1160
+ feedbacks.filter(f => f.type === 'request').length,
1161
+ feedbacks.filter(f => f.type === 'complaint').length,
1162
+ feedbacks.filter(f => f.type === 'praise').length,
1163
+ feedbacks.filter(f => f.type === 'suggestion').length
1164
+ ];
1165
+
1166
+ const statusData = [
1167
+ feedbacks.filter(f => f.status === 'pending').length,
1168
+ feedbacks.filter(f => f.status === 'in-progress').length,
1169
+ feedbacks.filter(f => f.status === 'resolved').length
1170
+ ];
1171
+
1172
+ const priorityData = [
1173
+ feedbacks.filter(f => f.priority === 'high').length,
1174
+ feedbacks.filter(f => f.priority === 'medium').length,
1175
+ feedbacks.filter(f => f.priority === 'low').length
1176
+ ];
1177
+
1178
+ // Gráfico de tipos
1179
+ const typeChart = new ApexCharts(document.querySelector("#typeChart"), {
1180
+ series: typeData,
1181
+ chart: {
1182
+ type: 'donut',
1183
+ height: '100%'
1184
+ },
1185
+ labels: ['Bugs', 'Solicitudes', 'Quejas', 'Elogios', 'Sugerencias'],
1186
+ colors: ['#EF4444', '#8B5CF6', '#F97316', '#10B981', '#3B82F6'],
1187
+ legend: {
1188
+ position: 'bottom'
1189
+ },
1190
+ responsive: [{
1191
+ breakpoint: 480,
1192
+ options: {
1193
+ chart: {
1194
+ width: 200
1195
+ },
1196
+ legend: {
1197
+ position: 'bottom'
1198
+ }
1199
+ }
1200
+ }]
1201
+ });
1202
+ typeChart.render();
1203
+
1204
+ // Gráfico de estados
1205
+ const statusChart = new ApexCharts(document.querySelector("#statusChart"), {
1206
+ series: statusData,
1207
+ chart: {
1208
+ type: 'pie',
1209
+ height: '100%'
1210
+ },
1211
+ labels: ['Pendientes', 'En Progreso', 'Resueltos'],
1212
+ colors: ['#F59E0B', '#3B82F6', '#10B981'],
1213
+ legend: {
1214
+ position: 'bottom'
1215
+ }
1216
+ });
1217
+ statusChart.render();
1218
+
1219
+ // Gráfico de prioridades
1220
+ const priorityChart = new ApexCharts(document.querySelector("#priorityChart"), {
1221
+ series: priorityData,
1222
+ chart: {
1223
+ type: 'radialBar',
1224
+ height: '100%'
1225
+ },
1226
+ plotOptions: {
1227
+ radialBar: {
1228
+ dataLabels: {
1229
+ name: {
1230
+ fontSize: '22px',
1231
+ },
1232
+ value: {
1233
+ fontSize: '16px',
1234
+ },
1235
+ total: {
1236
+ show: true,
1237
+ label: 'Total',
1238
+ formatter: function () {
1239
+ return priorityData.reduce((a, b) => a + b, 0);
1240
+ }
1241
+ }
1242
+ }
1243
+ }
1244
+ },
1245
+ labels: ['Alta', 'Media', 'Baja'],
1246
+ colors: ['#EF4444', '#F59E0B', '#10B981']
1247
+ });
1248
+ priorityChart.render();
1249
+
1250
+ // Gráfico de actividad (simulado)
1251
+ const activityChart = new ApexCharts(document.querySelector("#activityChart"), {
1252
+ series: [{
1253
+ name: 'Registros',
1254
+ data: [5, 8, 12, 7, 9, 15, 10]
1255
+ }],
1256
+ chart: {
1257
+ type: 'area',
1258
+ height: '100%',
1259
+ sparkline: {
1260
+ enabled: false
1261
+ },
1262
+ toolbar: {
1263
+ show: false
1264
+ }
1265
+ },
1266
+ colors: ['#8B5CF6'],
1267
+ fill: {
1268
+ type: 'gradient',
1269
+ gradient: {
1270
+ shadeIntensity: 1,
1271
+ opacityFrom: 0.7,
1272
+ opacityTo: 0.3,
1273
+ }
1274
+ },
1275
+ stroke: {
1276
+ curve: 'smooth',
1277
+ width: 2
1278
+ },
1279
+ xaxis: {
1280
+ categories: ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom']
1281
+ },
1282
+ tooltip: {
1283
+ fixed: {
1284
+ enabled: false
1285
+ },
1286
+ x: {
1287
+ show: false
1288
+ },
1289
+ marker: {
1290
+ show: false
1291
+ }
1292
+ }
1293
+ });
1294
+ activityChart.render();
1295
+
1296
+ // Top usuarios
1297
+ const userCounts = {};
1298
+ feedbacks.forEach(f => {
1299
+ if (userCounts[f.email]) {
1300
+ userCounts[f.email]++;
1301
+ } else {
1302
+ userCounts[f.email] = 1;
1303
+ }
1304
+ });
1305
+
1306
+ const sortedUsers = Object.entries(userCounts)
1307
+ .sort((a, b) => b[1] - a[1])
1308
+ .slice(0, 5);
1309
+
1310
+ const topUsersContainer = document.getElementById('topUsers');
1311
+ topUsersContainer.innerHTML = sortedUsers.map(([email, count]) => `
1312
+ <div class="flex justify-between items-center p-2 bg-gray-50 rounded-lg">
1313
+ <div class="flex items-center">
1314
+ <div class="w-8 h-8 rounded-full bg-indigo-100 text-indigo-600 flex items-center justify-center mr-3">
1315
+ <i class="fas fa-user text-sm"></i>
1316
+ </div>
1317
+ <span class="text-sm font-medium">${email.split('@')[0]}</span>
1318
+ </div>
1319
+ <span class="text-xs bg-gray-200 text-gray-700 px-2 py-1 rounded-full">${count} registros</span>
1320
+ </div>
1321
+ `).join('');
1322
+ }
1323
+ </script>
1324
+ <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=TrainHeartX/nuevo" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1325
+ </html>