blackcoffee2 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/CHANGELOG.md +664 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +25 -0
  4. package/README.md +246 -0
  5. package/apps.zip +0 -0
  6. package/bin/adminclient +105 -0
  7. package/bin/blackcoffee +133 -0
  8. package/cli/admin-users.js +282 -0
  9. package/cli/commands/app.js +561 -0
  10. package/cli/commands/config.js +182 -0
  11. package/cli/commands/db.js +257 -0
  12. package/cli/commands/server.js +200 -0
  13. package/config/applications.json +5 -0
  14. package/config/database.json +28 -0
  15. package/config/database.json.example +23 -0
  16. package/config/server.json +32 -0
  17. package/controllers/admin/AdminController.js +529 -0
  18. package/controllers/admin/AdminViewController.js +90 -0
  19. package/controllers/admin/AuthController.js +293 -0
  20. package/controllers/admin/DatabaseAdminController.js +218 -0
  21. package/core/SQLiteAdapter.js +333 -0
  22. package/core/appLoader.js +385 -0
  23. package/core/databasePoolManager.js +431 -0
  24. package/core/hotReload.js +363 -0
  25. package/data/ADMIN-README.md +145 -0
  26. package/data/CHANGELOG.md +48 -0
  27. package/data/GTK3-NODE-PROPOSALS.md +410 -0
  28. package/data/admin-db.js +150 -0
  29. package/data/admin-gui.js +452 -0
  30. package/data/blackcoffee_admin.db-shm +0 -0
  31. package/data/blackcoffee_admin.db-wal +0 -0
  32. package/data/migrations/001_create_admin_users.sql +33 -0
  33. package/docs/APP_HOOKS_HANDLER.md +432 -0
  34. package/docs/APP_HOOKS_REQUIREMENTS.md +588 -0
  35. package/docs/ARCHITECTURE.md +435 -0
  36. package/docs/CREAR_APP_Y_USAR_POOLS.md +1595 -0
  37. package/docs/EVENTS_APP_MANUAL.md +289 -0
  38. package/docs/INSITU_BINARY_UPLOAD_PROPOSAL.md +186 -0
  39. package/docs/INSITU_FIREWALL_EXCEPTION.md +187 -0
  40. package/docs/ROADMAP.md +242 -0
  41. package/docs/ROADMAP.md.backup +243 -0
  42. package/includes/404-hooks.js +423 -0
  43. package/includes/adminAuth.js +214 -0
  44. package/includes/adminExtension.js +53 -0
  45. package/includes/appHooks.js +302 -0
  46. package/includes/initAdminDb.js +115 -0
  47. package/includes/routeLoader.js +67 -0
  48. package/includes/sessions.js +223 -0
  49. package/issues/001-duplicate-module-loading.md +92 -0
  50. package/manuales/ADMIN_EXTENSION_COMMANDS_MANUAL.md +261 -0
  51. package/manuales/ADMIN_EXTENSION_HOOK_EXAMPLE.md +28 -0
  52. package/manuales/ADMIN_EXTENSION_INTEGRATION_MANUAL.md +232 -0
  53. package/manuales/CACHE_REGEX_COMMANDS.md +136 -0
  54. package/manuales/CACHE_SYSTEM_MAP.md +206 -0
  55. package/manuales/CREACION_DE_CONTROLADORES_INSITU.md +383 -0
  56. package/manuales/QUEUE_CLI_MODULE_MANUAL.md +289 -0
  57. package/manuales/QUEUE_SYSTEM_MANUAL.md +320 -0
  58. package/manuales/ROUTE_CACHE_MODULE_MANUAL.md +205 -0
  59. package/manuales/SESSION_MANAGER_GUIDE.md +529 -0
  60. package/manuales/SESSION_SECURITY_FLAGS.md +174 -0
  61. package/manuales/WAF_MODULE_MANUAL.md +229 -0
  62. package/manuales/after_route_handler_filter_example.md +116 -0
  63. package/manuales/after_route_handler_usage.md +130 -0
  64. package/manuales/an/303/241lisis-completo-insitu-framework.md +213 -0
  65. package/manuales/async_hooks_promises_guide.md +325 -0
  66. package/manuales/before_route_handler_filter_example.md +97 -0
  67. package/manuales/before_route_handler_usage.md +122 -0
  68. package/manuales/hooks_chaining_conditions_guide.md +261 -0
  69. package/manuales/hooks_filters_documentation.md +493 -0
  70. package/manuales/hooks_filters_documentation_en.md +493 -0
  71. package/manuales/hooks_vs_middlewares_comparison.md +87 -0
  72. package/manuales/manual-mvc-completo.md +934 -0
  73. package/manuales/modulos_administracion.md +89 -0
  74. package/manuales/router_execution_points.md +74 -0
  75. package/manuales/static_file_hooks_usage.md +222 -0
  76. package/models/AdminUserModel.js +132 -0
  77. package/package.json +45 -0
  78. package/programatically/PRoutes.js +89 -0
  79. package/programatically/initFlow.js +211 -0
  80. package/public/admin/css/db-pools.css +336 -0
  81. package/public/admin/css/styles.css +310 -0
  82. package/public/admin/database.html +312 -0
  83. package/public/admin/index.html +116 -0
  84. package/public/admin/js/app.js +470 -0
  85. package/public/admin/js/db-pools.js +253 -0
  86. package/public/admin/login.html +278 -0
  87. package/public/assets/css/styles.css +477 -0
  88. package/public/assets/js/main.js +89 -0
  89. package/public/index.html +136 -0
  90. package/public/templates/404.html +158 -0
  91. package/routes/admin-views.json +20 -0
  92. package/routes/admin.json +38 -0
  93. package/routes/auth.json +32 -0
  94. package/routes/static.json +18 -0
  95. package/server.js +299 -0
  96. package/test-aplicacion.con-logisession/BlackCoffee.js +226 -0
  97. package/test-aplicacion.con-logisession/SSL_SETUP.md +53 -0
  98. package/test-aplicacion.con-logisession/certs/ca-certificate.pem +32 -0
  99. package/test-aplicacion.con-logisession/certs/ca-private-key.pem +52 -0
  100. package/test-aplicacion.con-logisession/certs/certificate-2048.pem +22 -0
  101. package/test-aplicacion.con-logisession/certs/certificate.pem +32 -0
  102. package/test-aplicacion.con-logisession/certs/private-key-2048.pem +28 -0
  103. package/test-aplicacion.con-logisession/certs/private-key.pem +52 -0
  104. package/test-aplicacion.con-logisession/config/iaQueueSetup.js +84 -0
  105. package/test-aplicacion.con-logisession/config/qwen-rules.json +39 -0
  106. package/test-aplicacion.con-logisession/controllers/analyticsController.js +117 -0
  107. package/test-aplicacion.con-logisession/controllers/auth/AdminAuthController.js +142 -0
  108. package/test-aplicacion.con-logisession/controllers/auth/AuthController.js +439 -0
  109. package/test-aplicacion.con-logisession/controllers/auth/AuthViewController.js +223 -0
  110. package/test-aplicacion.con-logisession/controllers/endpointController.js +66 -0
  111. package/test-aplicacion.con-logisession/controllers/example.js +183 -0
  112. package/test-aplicacion.con-logisession/controllers/iaQueueController.js +367 -0
  113. package/test-aplicacion.con-logisession/controllers/queueController.js +206 -0
  114. package/test-aplicacion.con-logisession/controllers/qwenQueueController.js +197 -0
  115. package/test-aplicacion.con-logisession/controllers/test.js +0 -0
  116. package/test-aplicacion.con-logisession/controllers/tracking/EventsNoFinishController.js +78 -0
  117. package/test-aplicacion.con-logisession/controllers/tracking/TrackingController.js +412 -0
  118. package/test-aplicacion.con-logisession/controllers/tracking/TrackingControllerWithLoadModel.js +437 -0
  119. package/test-aplicacion.con-logisession/hooks/admin-hooks.js +20 -0
  120. package/test-aplicacion.con-logisession/hooks/general-hooks.js +97 -0
  121. package/test-aplicacion.con-logisession/hooks/queue-hooks.js +64 -0
  122. package/test-aplicacion.con-logisession/hooks/route-directory-hooks.js +38 -0
  123. package/test-aplicacion.con-logisession/hooks/security-hooks.js +24 -0
  124. package/test-aplicacion.con-logisession/insitu-admin-client/README.md +69 -0
  125. package/test-aplicacion.con-logisession/insitu-admin-client/package.json +23 -0
  126. package/test-aplicacion.con-logisession/insitu-admin-client.js +257 -0
  127. package/test-aplicacion.con-logisession/models/ExampleModel.js +88 -0
  128. package/test-aplicacion.con-logisession/models/QueueJobModel.js +263 -0
  129. package/test-aplicacion.con-logisession/models/TokenModel.js +207 -0
  130. package/test-aplicacion.con-logisession/models/auth/AuthModel.js +66 -0
  131. package/test-aplicacion.con-logisession/models/auth/UserModel.js +189 -0
  132. package/test-aplicacion.con-logisession/models/tracking/CompletedCartModel.js +213 -0
  133. package/test-aplicacion.con-logisession/models/tracking/EventModel.js +366 -0
  134. package/test-aplicacion.con-logisession/models/tracking/EventsNoFinishModel.js +131 -0
  135. package/test-aplicacion.con-logisession/models/tracking/SessionModel.js +360 -0
  136. package/test-aplicacion.con-logisession/models/tracking/SiteFlowModel.js +286 -0
  137. package/test-aplicacion.con-logisession/models/tracking/TokenModel.js +207 -0
  138. package/test-aplicacion.con-logisession/package-lock.json +3313 -0
  139. package/test-aplicacion.con-logisession/package.json +32 -0
  140. package/test-aplicacion.con-logisession/public/blackcoffee-welcome/index.html +1339 -0
  141. package/test-aplicacion.con-logisession/public/css/style.css +64 -0
  142. package/test-aplicacion.con-logisession/public/ejemplo-estatica/index.html +18 -0
  143. package/test-aplicacion.con-logisession/public/ejemplo-estatica/script.js +16 -0
  144. package/test-aplicacion.con-logisession/public/ejemplo-estatica/styles.css +43 -0
  145. package/test-aplicacion.con-logisession/public/images/logo.svg +7 -0
  146. package/test-aplicacion.con-logisession/public/js/main.js +67 -0
  147. package/test-aplicacion.con-logisession/routes/analytics-routes.json +8 -0
  148. package/test-aplicacion.con-logisession/routes/auth-routes.json +98 -0
  149. package/test-aplicacion.con-logisession/routes/blackcoffee-welcome-routes.json +20 -0
  150. package/test-aplicacion.con-logisession/routes/duplicate-test-routes.json.disabled +16 -0
  151. package/test-aplicacion.con-logisession/routes/ejemplo-estatica-routes.json +11 -0
  152. package/test-aplicacion.con-logisession/routes/endpoints-routes.json +8 -0
  153. package/test-aplicacion.con-logisession/routes/ia-queue-routes.json +26 -0
  154. package/test-aplicacion.con-logisession/routes/product-routes.json.disabled +20 -0
  155. package/test-aplicacion.con-logisession/routes/queue-routes.json +32 -0
  156. package/test-aplicacion.con-logisession/routes/qwen-routes.json +14 -0
  157. package/test-aplicacion.con-logisession/routes/static-routes.json +29 -0
  158. package/test-aplicacion.con-logisession/routes/tracking-routes.json +58 -0
  159. package/test-aplicacion.con-logisession/routes/tracking-with-loadmodel-routes.json +51 -0
  160. package/test-aplicacion.con-logisession/utils/dbAdapter.js +88 -0
  161. package/test-aplicacion.con-logisession/utils/qbWrapper.js +4 -0
  162. package/test-aplicacion.con-logisession/utils/queueProcessor.js +305 -0
  163. package/test-aplicacion.con-logisession/utils/qwenRulesService.js +131 -0
  164. package/test-aplicacion.con-logisession/utils/tokenHelper.js +22 -0
  165. package/test-aplicacion.con-logisession/views/auth/dashboard.html +443 -0
  166. package/test-aplicacion.con-logisession/views/auth/forgot-password.html +200 -0
  167. package/test-aplicacion.con-logisession/views/auth/login.html +213 -0
  168. package/test-aplicacion.con-logisession/views/auth/register.html +294 -0
  169. package/test-aplicacion.con-logisession/views/contact/form.html +47 -0
  170. package/test-aplicacion.con-logisession/views/products/index.html +39 -0
@@ -0,0 +1,443 @@
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>{{title}} - BlackCoffee</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
13
+ }
14
+
15
+ body {
16
+ background-color: #f5f5f5;
17
+ color: #333;
18
+ line-height: 1.6;
19
+ }
20
+
21
+ .navbar {
22
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
23
+ padding: 1rem 2rem;
24
+ display: flex;
25
+ justify-content: space-between;
26
+ align-items: center;
27
+ color: white;
28
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
29
+ }
30
+
31
+ .navbar-brand {
32
+ font-size: 1.5rem;
33
+ font-weight: bold;
34
+ }
35
+
36
+ .navbar-user {
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 1rem;
40
+ }
41
+
42
+ .user-info {
43
+ text-align: right;
44
+ }
45
+
46
+ .user-name {
47
+ font-weight: bold;
48
+ }
49
+
50
+ .user-email {
51
+ font-size: 0.9rem;
52
+ opacity: 0.8;
53
+ }
54
+
55
+ .container {
56
+ max-width: 1200px;
57
+ margin: 2rem auto;
58
+ padding: 0 2rem;
59
+ }
60
+
61
+ .welcome-section {
62
+ background: white;
63
+ border-radius: 10px;
64
+ padding: 2rem;
65
+ box-shadow: 0 5px 15px rgba(0,0,0,0.05);
66
+ margin-bottom: 2rem;
67
+ }
68
+
69
+ .welcome-section h1 {
70
+ color: #333;
71
+ margin-bottom: 1rem;
72
+ }
73
+
74
+ .user-details {
75
+ display: grid;
76
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
77
+ gap: 1.5rem;
78
+ margin-top: 1.5rem;
79
+ }
80
+
81
+ .detail-card {
82
+ background: #f8f9fa;
83
+ border-radius: 8px;
84
+ padding: 1.5rem;
85
+ border-left: 4px solid #667eea;
86
+ }
87
+
88
+ .detail-card h3 {
89
+ color: #555;
90
+ margin-bottom: 0.5rem;
91
+ }
92
+
93
+ .detail-card ul {
94
+ list-style-type: none;
95
+ }
96
+
97
+ .detail-card li {
98
+ padding: 0.25rem 0;
99
+ color: #666;
100
+ }
101
+
102
+ .capabilities-section {
103
+ background: white;
104
+ border-radius: 10px;
105
+ padding: 2rem;
106
+ box-shadow: 0 5px 15px rgba(0,0,0,0.05);
107
+ }
108
+
109
+ .capabilities-section h2 {
110
+ color: #333;
111
+ margin-bottom: 1rem;
112
+ }
113
+
114
+ .capabilities-grid {
115
+ display: grid;
116
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
117
+ gap: 1rem;
118
+ }
119
+
120
+ .capability-card {
121
+ background: #e8f4ff;
122
+ border-radius: 8px;
123
+ padding: 1rem;
124
+ border-left: 4px solid #2b7bd6;
125
+ }
126
+
127
+ .capability-name {
128
+ font-weight: bold;
129
+ color: #2b7bd6;
130
+ margin-bottom: 0.25rem;
131
+ }
132
+
133
+ .capability-desc {
134
+ font-size: 0.9rem;
135
+ color: #555;
136
+ }
137
+
138
+ .logout-btn {
139
+ background: #dc3545;
140
+ color: white;
141
+ border: none;
142
+ padding: 0.5rem 1rem;
143
+ border-radius: 5px;
144
+ cursor: pointer;
145
+ font-weight: 500;
146
+ }
147
+
148
+ .logout-btn:hover {
149
+ background: #c82333;
150
+ }
151
+ </style>
152
+ </head>
153
+ <body>
154
+ <nav class="navbar">
155
+ <div class="navbar-brand">BlackCoffee Dashboard</div>
156
+ <div class="navbar-user">
157
+ <div class="user-info">
158
+ <div class="user-name" id="user-username">{{user.username}}</div>
159
+ <div class="user-email" id="user-email">{{user.email}}</div>
160
+ </div>
161
+ <button class="logout-btn" onclick="logout()">Cerrar Sesión</button>
162
+ </div>
163
+ </nav>
164
+
165
+ <div class="container">
166
+ <section class="welcome-section">
167
+ <h1 id="welcome-message">¡Bienvenido, {{user.username}}!</h1>
168
+ <p>Has iniciado sesión correctamente en el sistema. Aquí tienes un resumen de tu información y permisos.</p>
169
+
170
+ <div class="user-details">
171
+ <div class="detail-card">
172
+ <h3>Información del Usuario</h3>
173
+ <ul>
174
+ <li><strong>ID:</strong> <span id="user-id">{{user.id}}</span></li>
175
+ <li><strong>Nombre de usuario:</strong> <span id="user-username-detail">{{user.username}}</span></li>
176
+ <li><strong>Email:</strong> <span id="user-email-detail">{{user.email}}</span></li>
177
+ </ul>
178
+ </div>
179
+
180
+ <div class="detail-card user-roles">
181
+ <h3>Roles Asignados</h3>
182
+ {{if user.roles}}
183
+ <ul>
184
+ {{foreach:user.roles}}
185
+ <li>{{name}} - {{description}}</li>
186
+ {{endfor}}
187
+ </ul>
188
+ {{else}}
189
+ <p>No tienes roles asignados.</p>
190
+ {{endif}}
191
+ </div>
192
+
193
+ <div class="detail-card">
194
+ <h3>Capacities Directas</h3>
195
+ {{if user.directCapabilities}}
196
+ <ul>
197
+ {{foreach:user.directCapabilities}}
198
+ <li>{{name}} - {{description}}</li>
199
+ {{endfor}}
200
+ </ul>
201
+ {{else}}
202
+ <p>No tienes capabilities directas asignadas.</p>
203
+ {{endif}}
204
+ </div>
205
+ </div>
206
+ </section>
207
+
208
+ <section class="capabilities-section">
209
+ <h2>Tus Capacities</h2>
210
+ <p>Aquí están todas las capacidades que tienes disponibles, ya sea directamente o a través de tus roles:</p>
211
+
212
+ <div class="capabilities-grid">
213
+ {{if user.allCapabilities}}
214
+ {{foreach:user.allCapabilities}}
215
+ <div class="capability-card">
216
+ <div class="capability-name">{{name}}</div>
217
+ <div class="capability-desc">{{description}}</div>
218
+ {{if resource}}<div><small>Recurso: {{resource}}</small></div>{{endif}}
219
+ {{if action}}<div><small>Acción: {{action}}</small></div>{{endif}}
220
+ </div>
221
+ {{endfor}}
222
+ {{else}}
223
+ <p>No tienes capacidades asignadas.</p>
224
+ {{endif}}
225
+ </div>
226
+ </section>
227
+
228
+ <!-- Sección para datos de carros abandonados -->
229
+ <section class="abandoned-carts-section">
230
+ <h2>Carros Abandonados</h2>
231
+ <p>Datos obtenidos del endpoint protegido de carros completados mediante AJAX:</p>
232
+
233
+ <div id="loadingIndicator" style="display: none; text-align: center; padding: 20px;">
234
+ <p>Cargando datos de carros abandonados...</p>
235
+ </div>
236
+
237
+ <div id="abandonedCartsData">
238
+ <p id="noDataMessage">No se han cargado datos aún.</p>
239
+ <div id="cartList" style="display: none;"></div>
240
+ </div>
241
+ </section>
242
+ </div>
243
+
244
+ <script>
245
+ // Verificar si hay un token válido en la sesión o en localStorage al cargar la página
246
+ document.addEventListener('DOMContentLoaded', async function() {
247
+ // Intentar obtener el token de la sesión o de localStorage
248
+ let token = localStorage.getItem('authToken');
249
+
250
+ // Si no hay token en localStorage, intentar obtenerlo de otro lugar
251
+ // En una implementación real, el token podría haber sido pasado en la vista
252
+ // o estar disponible en una variable JavaScript
253
+
254
+ // Si no hay token, redirigir al login
255
+ if (!token) {
256
+ // Intentar obtener el token de una variable global o de la sesión
257
+ // En este caso, asumiremos que el token se pasa en la vista como variable
258
+ // Si no está disponible, redirigir al login
259
+ window.location.href = '/login';
260
+ return;
261
+ }
262
+
263
+ try {
264
+ const response = await fetch('/api/auth/profile', {
265
+ headers: {
266
+ 'Authorization': `Bearer ${token}`
267
+ }
268
+ });
269
+
270
+ if (response.status === 401) {
271
+ // Token no válido, redirigir al login
272
+ window.location.href = '/login';
273
+ return;
274
+ }
275
+
276
+ const data = await response.json();
277
+ if (data.success && data.user) {
278
+ // Actualizar la interfaz con la información del usuario
279
+ document.querySelector('.user-name').textContent = data.user.username;
280
+ document.querySelector('.user-email').textContent = data.user.email;
281
+
282
+ // Actualizar otros elementos con la información del usuario
283
+ document.querySelector('h1').textContent = `¡Bienvenido, ${data.user.username}!`;
284
+ document.getElementById('user-id').textContent = data.user.id;
285
+ document.getElementById('user-username').textContent = data.user.username;
286
+ document.getElementById('user-email').textContent = data.user.email;
287
+
288
+ // Actualizar roles
289
+ const rolesContainer = document.querySelector('.user-roles ul');
290
+ if (rolesContainer && data.user.roles && data.user.roles.length > 0) {
291
+ rolesContainer.innerHTML = '';
292
+ data.user.roles.forEach(role => {
293
+ const li = document.createElement('li');
294
+ li.textContent = `${role.name} - ${role.description}`;
295
+ rolesContainer.appendChild(li);
296
+ });
297
+ } else if (rolesContainer) {
298
+ rolesContainer.innerHTML = '<li>No tienes roles asignados.</li>';
299
+ }
300
+
301
+ // Actualizar capabilities
302
+ const capsContainer = document.querySelector('.capabilities-grid');
303
+ if (capsContainer && data.user.allCapabilities && data.user.allCapabilities.length > 0) {
304
+ capsContainer.innerHTML = '';
305
+ data.user.allCapabilities.forEach(cap => {
306
+ const capCard = document.createElement('div');
307
+ capCard.className = 'capability-card';
308
+ capCard.innerHTML = `
309
+ <div class="capability-name">${cap.name}</div>
310
+ <div class="capability-desc">${cap.description}</div>
311
+ ${cap.resource ? `<div><small>Recurso: ${cap.resource}</small></div>` : ''}
312
+ ${cap.action ? `<div><small>Acción: ${cap.action}</small></div>` : ''}
313
+ `;
314
+ capsContainer.appendChild(capCard);
315
+ });
316
+ } else if (capsContainer) {
317
+ capsContainer.innerHTML = '<p>No tienes capacidades asignadas.</p>';
318
+ }
319
+
320
+ // Cargar datos de carros abandonados
321
+ loadAbandonedCarts(token);
322
+ } else {
323
+ // Token no válido, redirigir al login
324
+ window.location.href = '/login';
325
+ }
326
+ } catch (error) {
327
+ console.error('Error verificando la sesión:', error);
328
+ // En caso de error de red, también redirigir al login
329
+ window.location.href = '/login';
330
+ }
331
+ });
332
+
333
+ // Función para cargar datos de carros abandonados
334
+ async function loadAbandonedCarts(token) {
335
+ const loadingIndicator = document.getElementById('loadingIndicator');
336
+ const cartList = document.getElementById('cartList');
337
+ const noDataMessage = document.getElementById('noDataMessage');
338
+
339
+ try {
340
+ // Mostrar indicador de carga
341
+ loadingIndicator.style.display = 'block';
342
+ noDataMessage.style.display = 'none';
343
+
344
+ // Hacer la solicitud al endpoint protegido
345
+ const response = await fetch('/api/tracking/completed-carts', {
346
+ headers: {
347
+ 'Authorization': `Bearer ${token}`
348
+ }
349
+ });
350
+
351
+ if (response.status === 401) {
352
+ // Token no válido, redirigir al login
353
+ alert('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.');
354
+ window.location.href = '/login';
355
+ return;
356
+ }
357
+
358
+ const data = await response.json();
359
+
360
+ if (data.rows && data.rows.length > 0) {
361
+ // Crear contenido para mostrar los carros abandonados
362
+ let cartItemsHtml = '<ul class="cart-list">';
363
+
364
+ data.rows.slice(0, 10).forEach(cart => { // Mostrar solo los primeros 10
365
+ cartItemsHtml += `
366
+ <li class="cart-item">
367
+ <strong>ID:</strong> ${cart.id} |
368
+ <strong>Session:</strong> ${cart.session_id} |
369
+ <strong>Fecha:</strong> ${new Date(cart.created_at).toLocaleString()} |
370
+ <strong>URL:</strong> ${cart.site_url}
371
+ </li>
372
+ `;
373
+ });
374
+
375
+ cartItemsHtml += '</ul>';
376
+
377
+ cartList.innerHTML = cartItemsHtml;
378
+ cartList.style.display = 'block';
379
+ } else {
380
+ noDataMessage.textContent = 'No se encontraron carros abandonados.';
381
+ noDataMessage.style.display = 'block';
382
+ }
383
+ } catch (error) {
384
+ console.error('Error cargando carros abandonados:', error);
385
+ noDataMessage.textContent = 'Error al cargar los datos de carros abandonados.';
386
+ noDataMessage.style.display = 'block';
387
+ } finally {
388
+ // Ocultar indicador de carga
389
+ loadingIndicator.style.display = 'none';
390
+ }
391
+ }
392
+
393
+ async function logout() {
394
+ // Obtener el token de localStorage
395
+ const token = localStorage.getItem('authToken');
396
+
397
+ if (token) {
398
+ try {
399
+ // Llamar al endpoint de logout para revocar el token
400
+ await fetch('/api/auth/logout', {
401
+ method: 'POST',
402
+ headers: {
403
+ 'Authorization': `Bearer ${token}`,
404
+ 'Content-Type': 'application/json'
405
+ }
406
+ });
407
+ } catch (error) {
408
+ console.error('Error en logout API:', error);
409
+ // Continuar con el logout incluso si falla la llamada API
410
+ }
411
+ }
412
+
413
+ // Eliminar el token de localStorage
414
+ localStorage.removeItem('authToken');
415
+
416
+ // Redirigir al login
417
+ window.location.href = '/login';
418
+ }
419
+
420
+ // Verificar periodicamente si el token sigue siendo válido
421
+ setInterval(async () => {
422
+ const token = localStorage.getItem('authToken');
423
+ if (token) {
424
+ try {
425
+ const response = await fetch('/api/auth/profile', {
426
+ headers: {
427
+ 'Authorization': `Bearer ${token}`
428
+ }
429
+ });
430
+
431
+ if (response.status === 401) {
432
+ // Token no válido, redirigir al login
433
+ alert('Tu sesión ha expirado. Por favor, inicia sesión nuevamente.');
434
+ logout();
435
+ }
436
+ } catch (error) {
437
+ console.error('Error verificando la sesión:', error);
438
+ }
439
+ }
440
+ }, 300000); // Verificar cada 5 minutos
441
+ </script>
442
+ </body>
443
+ </html>
@@ -0,0 +1,200 @@
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>Recuperar Contraseña - BlackCoffee</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
13
+ }
14
+
15
+ body {
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .forgot-container {
25
+ background: white;
26
+ padding: 40px;
27
+ border-radius: 10px;
28
+ box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
29
+ width: 100%;
30
+ max-width: 450px;
31
+ }
32
+
33
+ .forgot-header {
34
+ text-align: center;
35
+ margin-bottom: 30px;
36
+ }
37
+
38
+ .forgot-header h1 {
39
+ color: #333;
40
+ font-size: 28px;
41
+ margin-bottom: 10px;
42
+ }
43
+
44
+ .forgot-header p {
45
+ color: #666;
46
+ font-size: 14px;
47
+ }
48
+
49
+ .form-group {
50
+ margin-bottom: 20px;
51
+ }
52
+
53
+ .form-group label {
54
+ display: block;
55
+ margin-bottom: 8px;
56
+ color: #333;
57
+ font-weight: 500;
58
+ }
59
+
60
+ .form-group input {
61
+ width: 100%;
62
+ padding: 12px 15px;
63
+ border: 2px solid #e1e1e1;
64
+ border-radius: 5px;
65
+ font-size: 16px;
66
+ transition: border-color 0.3s ease;
67
+ }
68
+
69
+ .form-group input:focus {
70
+ outline: none;
71
+ border-color: #667eea;
72
+ }
73
+
74
+ .btn {
75
+ width: 100%;
76
+ padding: 12px;
77
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
78
+ color: white;
79
+ border: none;
80
+ border-radius: 5px;
81
+ font-size: 16px;
82
+ font-weight: 600;
83
+ cursor: pointer;
84
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
85
+ }
86
+
87
+ .btn:hover {
88
+ transform: translateY(-2px);
89
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
90
+ }
91
+
92
+ .btn:active {
93
+ transform: translateY(0);
94
+ }
95
+
96
+ .forgot-footer {
97
+ text-align: center;
98
+ margin-top: 20px;
99
+ color: #666;
100
+ font-size: 14px;
101
+ }
102
+
103
+ .forgot-footer a {
104
+ color: #667eea;
105
+ text-decoration: none;
106
+ font-weight: 500;
107
+ }
108
+
109
+ .forgot-footer a:hover {
110
+ text-decoration: underline;
111
+ }
112
+
113
+ .error-message {
114
+ display: none;
115
+ background: #fee;
116
+ color: #c33;
117
+ padding: 10px;
118
+ border-radius: 5px;
119
+ margin-bottom: 15px;
120
+ text-align: center;
121
+ font-size: 14px;
122
+ }
123
+
124
+ .success-message {
125
+ display: none;
126
+ background: #efe;
127
+ color: #363;
128
+ padding: 10px;
129
+ border-radius: 5px;
130
+ margin-bottom: 15px;
131
+ text-align: center;
132
+ font-size: 14px;
133
+ }
134
+ </style>
135
+ </head>
136
+ <body>
137
+ <div class="forgot-container">
138
+ <div class="forgot-header">
139
+ <h1>Recuperar Contraseña</h1>
140
+ <p>Ingresa tu email para recibir instrucciones</p>
141
+ </div>
142
+
143
+ <div id="errorMessage" class="error-message"></div>
144
+ <div id="successMessage" class="success-message"></div>
145
+
146
+ <form id="forgotForm">
147
+ <div class="form-group">
148
+ <label for="email">Email</label>
149
+ <input type="email" id="email" name="email" required placeholder="Ingresa tu email registrado">
150
+ </div>
151
+
152
+ <button type="submit" class="btn">Enviar Instrucciones</button>
153
+ </form>
154
+
155
+ <div class="forgot-footer">
156
+ <p><a href="/login">Volver al inicio de sesión</a></p>
157
+ </div>
158
+ </div>
159
+
160
+ <script>
161
+ document.getElementById('forgotForm').addEventListener('submit', async (e) => {
162
+ e.preventDefault();
163
+
164
+ const email = document.getElementById('email').value;
165
+ const errorMessage = document.getElementById('errorMessage');
166
+ const successMessage = document.getElementById('successMessage');
167
+
168
+ // Validar email
169
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
170
+ if (!emailRegex.test(email)) {
171
+ errorMessage.textContent = 'Por favor, ingresa un email válido';
172
+ errorMessage.style.display = 'block';
173
+ return;
174
+ }
175
+
176
+ // Ocultar mensajes anteriores
177
+ errorMessage.style.display = 'none';
178
+ successMessage.style.display = 'none';
179
+
180
+ try {
181
+ // Simular envío de solicitud de recuperación de contraseña
182
+ // En una implementación real, esto haría una llamada a una API
183
+
184
+ // Mostrar mensaje de éxito simulado
185
+ successMessage.textContent = 'Instrucciones de recuperación enviadas a tu email. Revisa tu bandeja de entrada.';
186
+ successMessage.style.display = 'block';
187
+
188
+ // Limpiar el formulario
189
+ document.getElementById('email').value = '';
190
+
191
+ console.log('Solicitud de recuperación de contraseña para:', email);
192
+ } catch (error) {
193
+ console.error('Error en la recuperación de contraseña:', error);
194
+ errorMessage.textContent = 'Error de conexión. Por favor, inténtalo de nuevo.';
195
+ errorMessage.style.display = 'block';
196
+ }
197
+ });
198
+ </script>
199
+ </body>
200
+ </html>