Files changed (1) hide show
  1. README.md +1429 -0
README.md ADDED
@@ -0,0 +1,1429 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 Pendientes de Pago</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/localforage/1.10.0/localforage.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
12
+ <style>
13
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
14
+
15
+ body {
16
+ font-family: 'Inter', sans-serif;
17
+ background-color: #f8fafc;
18
+ color: #1e293b;
19
+ }
20
+
21
+ .sidebar {
22
+ transition: all 0.3s ease;
23
+ }
24
+
25
+ .card {
26
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
27
+ transition: all 0.3s ease;
28
+ }
29
+
30
+ .card:hover {
31
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
32
+ transform: translateY(-2px);
33
+ }
34
+
35
+ .tab-content {
36
+ animation: fadeIn 0.3s ease;
37
+ }
38
+
39
+ @keyframes fadeIn {
40
+ from { opacity: 0; }
41
+ to { opacity: 1; }
42
+ }
43
+
44
+ .table-container {
45
+ max-height: 500px;
46
+ scrollbar-width: thin;
47
+ scrollbar-color: #cbd5e1 #f1f5f9;
48
+ }
49
+
50
+ .table-container::-webkit-scrollbar {
51
+ width: 8px;
52
+ height: 8px;
53
+ }
54
+
55
+ .table-container::-webkit-scrollbar-track {
56
+ background: #f1f5f9;
57
+ }
58
+
59
+ .table-container::-webkit-scrollbar-thumb {
60
+ background-color: #cbd5e1;
61
+ border-radius: 4px;
62
+ }
63
+
64
+ .badge {
65
+ font-size: 0.75rem;
66
+ padding: 0.25rem 0.5rem;
67
+ }
68
+
69
+ .action-btn {
70
+ transition: all 0.2s ease;
71
+ }
72
+
73
+ .action-btn:hover {
74
+ transform: scale(1.05);
75
+ }
76
+
77
+ .floating-btn {
78
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
79
+ transition: all 0.3s ease;
80
+ }
81
+
82
+ .floating-btn:hover {
83
+ box-shadow: 0 6px 8px rgba(0,0,0,0.15);
84
+ transform: translateY(-2px);
85
+ }
86
+
87
+ .no-records {
88
+ background-image: linear-gradient(to right, #f8fafc, #f1f5f9, #f8fafc);
89
+ background-size: 200% auto;
90
+ animation: shimmer 1.5s infinite linear;
91
+ }
92
+
93
+ @keyframes shimmer {
94
+ 0% { background-position: -200% 0; }
95
+ 100% { background-position: 200% 0; }
96
+ }
97
+ </style>
98
+ </head>
99
+ <body class="min-h-screen bg-gray-50">
100
+ <!-- Contenedor principal -->
101
+ <div class="flex h-screen bg-gray-50" id="app">
102
+ <!-- Sidebar -->
103
+ <div class="sidebar bg-white w-64 border-r border-gray-200 flex flex-col">
104
+ <div class="p-4 border-b border-gray-200 flex items-center">
105
+ <i class="fas fa-wallet text-2xl text-blue-600 mr-3"></i>
106
+ <h1 class="text-xl font-bold text-gray-800">Pendientes de Pago</h1>
107
+ </div>
108
+ <div class="flex-1 overflow-y-auto">
109
+ <nav class="p-4 space-y-1">
110
+ <a href="#" class="tab-link flex items-center px-4 py-2 text-sm font-medium rounded-lg bg-blue-50 text-blue-700" data-tab="dashboard">
111
+ <i class="fas fa-tachometer-alt mr-3 text-blue-600"></i> Dashboard
112
+ </a>
113
+ <a href="#" class="tab-link flex items-center px-4 py-2 text-sm font-medium rounded-lg text-gray-700 hover:bg-gray-100" data-tab="pendientes">
114
+ <i class="fas fa-list-ul mr-3 text-gray-500"></i> Pendientes
115
+ </a>
116
+ <a href="#" class="tab-link flex items-center px-4 py-2 text-sm font-medium rounded-lg text-gray-700 hover:bg-gray-100" data-tab="historial">
117
+ <i class="fas fa-history mr-3 text-gray-500"></i> Historial
118
+ </a>
119
+ <a href="#" class="tab-link flex items-center px-4 py-2 text-sm font-medium rounded-lg text-gray-700 hover:bg-gray-100" data-tab="reportes">
120
+ <i class="fas fa-chart-bar mr-3 text-gray-500"></i> Reportes
121
+ </a>
122
+ <a href="#" class="tab-link flex items-center px-4 py-2 text-sm font-medium rounded-lg text-gray-700 hover:bg-gray-100" data-tab="configuracion">
123
+ <i class="fas fa-cog mr-3 text-gray-500"></i> Configuración
124
+ </a>
125
+ </nav>
126
+ </div>
127
+ <div class="p-4 border-t border-gray-200">
128
+ <button onclick="logout()" class="w-full flex items-center justify-center px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-50 rounded-lg">
129
+ <i class="fas fa-sign-out-alt mr-2"></i> Cerrar Sesión
130
+ </button>
131
+ </div>
132
+ </div>
133
+
134
+ <!-- Contenido principal -->
135
+ <div class="flex-1 overflow-y-auto">
136
+ <div class="p-6">
137
+ <!-- Header -->
138
+ <div class="flex justify-between items-center mb-6">
139
+ <h2 class="text-2xl font-bold text-gray-800" id="current-tab-title">Dashboard</h2>
140
+ <div class="flex items-center space-x-2">
141
+ <span id="current-date" class="text-sm text-gray-500"></span>
142
+ <div class="relative">
143
+ <button id="notifications-btn" class="p-2 rounded-full hover:bg-gray-100">
144
+ <i class="fas fa-bell text-gray-500"></i>
145
+ <span class="absolute top-0 right-0 h-2 w-2 rounded-full bg-red-500"></span>
146
+ </button>
147
+ </div>
148
+ </div>
149
+ </div>
150
+
151
+ <!-- Contenido de las pestañas -->
152
+ <div class="tab-content" id="dashboard-content">
153
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
154
+ <div class="card bg-white rounded-xl p-6">
155
+ <div class="flex items-center justify-between">
156
+ <div>
157
+ <p class="text-sm font-medium text-gray-500">Total Pendiente</p>
158
+ <h3 class="text-2xl font-bold text-gray-800 mt-1" id="total-pendiente">$0.00</h3>
159
+ </div>
160
+ <div class="p-3 rounded-full bg-blue-100 text-blue-600">
161
+ <i class="fas fa-clock text-xl"></i>
162
+ </div>
163
+ </div>
164
+ <div class="mt-4 pt-4 border-t border-gray-100">
165
+ <p class="text-xs text-gray-500">Última actualización: <span id="last-update">hoy</span></p>
166
+ </div>
167
+ </div>
168
+
169
+ <div class="card bg-white rounded-xl p-6">
170
+ <div class="flex items-center justify-between">
171
+ <div>
172
+ <p class="text-sm font-medium text-gray-500">Total Pagado</p>
173
+ <h3 class="text-2xl font-bold text-gray-800 mt-1" id="total-pagado">$0.00</h3>
174
+ </div>
175
+ <div class="p-3 rounded-full bg-green-100 text-green-600">
176
+ <i class="fas fa-check-circle text-xl"></i>
177
+ </div>
178
+ </div>
179
+ <div class="mt-4 pt-4 border-t border-gray-100">
180
+ <p class="text-xs text-gray-500">Este mes: <span id="month-pagado">$0.00</span></p>
181
+ </div>
182
+ </div>
183
+
184
+ <div class="card bg-white rounded-xl p-6">
185
+ <div class="flex items-center justify-between">
186
+ <div>
187
+ <p class="text-sm font-medium text-gray-500">Registros Pendientes</p>
188
+ <h3 class="text-2xl font-bold text-gray-800 mt-1" id="count-pendientes">0</h3>
189
+ </div>
190
+ <div class="p-3 rounded-full bg-yellow-100 text-yellow-600">
191
+ <i class="fas fa-file-invoice text-xl"></i>
192
+ </div>
193
+ </div>
194
+ <div class="mt-4 pt-4 border-t border-gray-100">
195
+ <p class="text-xs text-gray-500">Vencidos: <span id="count-vencidos">0</span></p>
196
+ </div>
197
+ </div>
198
+
199
+ <div class="card bg-white rounded-xl p-6">
200
+ <div class="flex items-center justify-between">
201
+ <div>
202
+ <p class="text-sm font-medium text-gray-500">Registros Historial</p>
203
+ <h3 class="text-2xl font-bold text-gray-800 mt-1" id="count-historial">0</h3>
204
+ </div>
205
+ <div class="p-3 rounded-full bg-purple-100 text-purple-600">
206
+ <i class="fas fa-archive text-xl"></i>
207
+ </div>
208
+ </div>
209
+ <div class="mt-4 pt-4 border-t border-gray-100">
210
+ <p class="text-xs text-gray-500">Este mes: <span id="month-historial">0</span></p>
211
+ </div>
212
+ </div>
213
+ </div>
214
+
215
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
216
+ <div class="card bg-white rounded-xl p-6">
217
+ <div class="flex items-center justify-between mb-4">
218
+ <h3 class="text-lg font-semibold text-gray-800">Evolución de Pagos</h3>
219
+ <div class="flex space-x-2">
220
+ <button class="text-xs px-2 py-1 bg-gray-100 rounded hover:bg-gray-200" data-period="week">Semana</button>
221
+ <button class="text-xs px-2 py-1 bg-blue-100 rounded hover:bg-blue-200" data-period="month">Mes</button>
222
+ <button class="text-xs px-2 py-1 bg-gray-100 rounded hover:bg-gray-200" data-period="year">Año</button>
223
+ </div>
224
+ </div>
225
+ <div class="h-64">
226
+ <canvas id="payments-chart"></canvas>
227
+ </div>
228
+ </div>
229
+
230
+ <div class="card bg-white rounded-xl p-6">
231
+ <div class="flex items-center justify-between mb-4">
232
+ <h3 class="text-lg font-semibold text-gray-800">Distribución por Categoría</h3>
233
+ <div class="flex space-x-2">
234
+ <button class="text-xs px-2 py-1 bg-gray-100 rounded hover:bg-gray-200" data-type="pendientes">Pendientes</button>
235
+ <button class="text-xs px-2 py-1 bg-blue-100 rounded hover:bg-blue-200" data-type="historial">Historial</button>
236
+ </div>
237
+ </div>
238
+ <div class="h-64">
239
+ <canvas id="categories-chart"></canvas>
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ <div class="card bg-white rounded-xl p-6">
245
+ <div class="flex items-center justify-between mb-4">
246
+ <h3 class="text-lg font-semibold text-gray-800">Próximos Vencimientos</h3>
247
+ <a href="#" class="text-sm text-blue-600 hover:text-blue-800" onclick="activarTab('pendientes')">Ver todos</a>
248
+ </div>
249
+ <div class="overflow-x-auto">
250
+ <table class="min-w-full divide-y divide-gray-200">
251
+ <thead class="bg-gray-50">
252
+ <tr>
253
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fecha</th>
254
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nombre</th>
255
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Descripción</th>
256
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total</th>
257
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Estado</th>
258
+ </tr>
259
+ </thead>
260
+ <tbody class="bg-white divide-y divide-gray-200" id="upcoming-payments">
261
+ <tr>
262
+ <td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">Cargando...</td>
263
+ </tr>
264
+ </tbody>
265
+ </table>
266
+ </div>
267
+ </div>
268
+ </div>
269
+
270
+ <!-- Pestaña Pendientes -->
271
+ <div class="tab-content hidden" id="pendientes-content">
272
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
273
+ <div class="lg:col-span-2">
274
+ <div class="card bg-white rounded-xl p-6">
275
+ <h3 class="text-lg font-semibold text-gray-800 mb-4">Nuevo Registro</h3>
276
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
277
+ <div>
278
+ <label for="fecha" class="block text-sm font-medium text-gray-700 mb-1">Fecha</label>
279
+ <input type="date" id="fecha" required
280
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
281
+ </div>
282
+ <div>
283
+ <label for="nombre" class="block text-sm font-medium text-gray-700 mb-1">Nombre</label>
284
+ <input type="text" id="nombre" placeholder="Ingrese nombre" required
285
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
286
+ </div>
287
+ <div class="md:col-span-2">
288
+ <label for="descripcion" class="block text-sm font-medium text-gray-700 mb-1">Descripción</label>
289
+ <textarea id="descripcion" placeholder="Ingrese descripción" required rows="3"
290
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition"></textarea>
291
+ </div>
292
+ <div>
293
+ <label for="total" class="block text-sm font-medium text-gray-700 mb-1">Total</label>
294
+ <input type="number" id="total" placeholder="0.00" step="0.01" required
295
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
296
+ </div>
297
+ <div class="flex items-end">
298
+ <button id="btn-agregar"
299
+ class="bg-blue-600 text-white py-2 px-6 rounded-lg hover:bg-blue-700 transition flex items-center">
300
+ <i class="fas fa-plus mr-2"></i> Agregar
301
+ </button>
302
+ <button id="btn-cancelar-edicion"
303
+ class="ml-2 bg-gray-200 text-gray-700 py-2 px-4 rounded-lg hover:bg-gray-300 transition hidden">
304
+ Cancelar
305
+ </button>
306
+ </div>
307
+ </div>
308
+ </div>
309
+ </div>
310
+
311
+ <div class="card bg-white rounded-xl p-6">
312
+ <h3 class="text-lg font-semibold text-gray-800 mb-4">Resumen</h3>
313
+ <div class="space-y-4">
314
+ <div>
315
+ <p class="text-sm font-medium text-gray-500">Total Pendiente</p>
316
+ <h3 class="text-2xl font-bold text-gray-800" id="pendientes-summary-total">$0.00</h3>
317
+ </div>
318
+ <div>
319
+ <p class="text-sm font-medium text-gray-500">Registros</p>
320
+ <h3 class="text-xl font-bold text-gray-800" id="pendientes-summary-count">0</h3>
321
+ </div>
322
+ <div class="pt-4 border-t border-gray-100">
323
+ <label for="buscar" class="block text-sm font-medium text-gray-700 mb-2">Buscar registros</label>
324
+ <div class="relative">
325
+ <input type="search" id="buscar" placeholder="Buscar por nombre, fecha o descripción"
326
+ class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
327
+ <div class="absolute left-3 top-2.5 text-gray-400">
328
+ <i class="fas fa-search"></i>
329
+ </div>
330
+ </div>
331
+ </div>
332
+ <div class="pt-4 border-t border-gray-100">
333
+ <button id="exportar-json"
334
+ class="w-full bg-green-600 text-white py-2 px-4 rounded-lg hover:bg-green-700 transition flex items-center justify-center mb-2">
335
+ <i class="fas fa-file-export mr-2"></i> Exportar JSON
336
+ </button>
337
+ <button id="exportar-excel"
338
+ class="w-full bg-green-600 text-white py-2 px-4 rounded-lg hover:bg-green-700 transition flex items-center justify-center mb-2">
339
+ <i class="fas fa-file-excel mr-2"></i> Exportar CSV
340
+ </button>
341
+ <button id="importar-json"
342
+ class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition flex items-center justify-center mb-2">
343
+ <i class="fas fa-file-import mr-2"></i> Importar JSON
344
+ </button>
345
+ <button id="importar-excel"
346
+ class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition flex items-center justify-center">
347
+ <i class="fas fa-file-csv mr-2"></i> Importar CSV
348
+ </button>
349
+ </div>
350
+ </div>
351
+ </div>
352
+ </div>
353
+
354
+ <div class="card bg-white rounded-xl p-6 mb-6">
355
+ <div class="flex justify-between items-center mb-4">
356
+ <h3 class="text-lg font-semibold text-gray-800">Registros Pendientes</h3>
357
+ <div class="flex space-x-2">
358
+ <button id="descargar-busqueda"
359
+ class="bg-gray-100 text-gray-700 py-1.5 px-3 rounded-lg hover:bg-gray-200 transition flex items-center text-sm">
360
+ <i class="fas fa-image mr-1"></i> Imagen
361
+ </button>
362
+ <button id="limpiar-datos"
363
+ class="bg-red-100 text-red-700 py-1.5 px-3 rounded-lg hover:bg-red-200 transition flex items-center text-sm">
364
+ <i class="fas fa-trash mr-1"></i> Limpiar
365
+ </button>
366
+ </div>
367
+ </div>
368
+ <div class="table-container overflow-auto rounded-lg border border-gray-200">
369
+ <table class="min-w-full divide-y divide-gray-200" id="tabla-pendientes">
370
+ <thead class="bg-gray-50">
371
+ <tr>
372
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fecha</th>
373
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nombre</th>
374
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Descripción</th>
375
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total</th>
376
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Acciones</th>
377
+ </tr>
378
+ </thead>
379
+ <tbody class="bg-white divide-y divide-gray-200" id="pendientes-body">
380
+ <tr>
381
+ <td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">Cargando datos...</td>
382
+ </tr>
383
+ </tbody>
384
+ <tfoot class="bg-gray-50">
385
+ <tr>
386
+ <td colspan="3" class="px-6 py-3 text-right text-sm font-medium text-gray-500">TOTAL PENDIENTE:</td>
387
+ <td class="px-6 py-3 text-left text-sm font-medium text-gray-900" id="suma-total">$0.00</td>
388
+ <td></td>
389
+ </tr>
390
+ </tfoot>
391
+ </table>
392
+ </div>
393
+ </div>
394
+ </div>
395
+
396
+ <!-- Pestaña Historial -->
397
+ <div class="tab-content hidden" id="historial-content">
398
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
399
+ <div class="lg:col-span-2">
400
+ <div class="card bg-white rounded-xl p-6">
401
+ <h3 class="text-lg font-semibold text-gray-800 mb-4">Filtros</h3>
402
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
403
+ <div>
404
+ <label for="fecha-inicio" class="block text-sm font-medium text-gray-700 mb-1">Fecha Inicio</label>
405
+ <input type="date" id="fecha-inicio"
406
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
407
+ </div>
408
+ <div>
409
+ <label for="fecha-fin" class="block text-sm font-medium text-gray-700 mb-1">Fecha Fin</label>
410
+ <input type="date" id="fecha-fin"
411
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
412
+ </div>
413
+ <div>
414
+ <label for="buscar-historial" class="block text-sm font-medium text-gray-700 mb-1">Buscar</label>
415
+ <div class="relative">
416
+ <input type="search" id="buscar-historial" placeholder="Buscar en historial"
417
+ class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition">
418
+ <div class="absolute left-3 top-2.5 text-gray-400">
419
+ <i class="fas fa-search"></i>
420
+ </div>
421
+ </div>
422
+ </div>
423
+ </div>
424
+ </div>
425
+ </div>
426
+
427
+ <div class="card bg-white rounded-xl p-6">
428
+ <h3 class="text-lg font-semibold text-gray-800 mb-4">Resumen</h3>
429
+ <div class="space-y-4">
430
+ <div>
431
+ <p class="text-sm font-medium text-gray-500">Total Pagado</p>
432
+ <h3 class="text-2xl font-bold text-gray-800" id="historial-summary-total">$0.00</h3>
433
+ </div>
434
+ <div>
435
+ <p class="text-sm font-medium text-gray-500">Registros</p>
436
+ <h3 class="text-xl font-bold text-gray-800" id="historial-summary-count">0</h3>
437
+ </div>
438
+ <div class="pt-4 border-t border-gray-100">
439
+ <button id="exportar-historial-json"
440
+ class="w-full bg-green-600 text-white py-2 px-4 rounded-lg hover:bg-green-700 transition flex items-center justify-center mb-2">
441
+ <i class="fas fa-file-export mr-2"></i> Exportar JSON
442
+ </button>
443
+ <button id="exportar-historial-excel"
444
+ class="w-full bg-green-600 text-white py-2 px-4 rounded-lg hover:bg-green-700 transition flex items-center justify-center">
445
+ <i class="fas fa-file-excel mr-2"></i> Exportar CSV
446
+ </button>
447
+ </div>
448
+ </div>
449
+ </div>
450
+ </div>
451
+
452
+ <div class="card bg-white rounded-xl p-6 mb-6">
453
+ <div class="flex justify-between items-center mb-4">
454
+ <h3 class="text-lg font-semibold text-gray-800">Historial de Pagos</h3>
455
+ <div class="flex items-center space-x-2">
456
+ <span class="text-sm text-gray-500" id="historial-filter-info">Mostrando todos los registros</span>
457
+ </div>
458
+ </div>
459
+ <div class="table-container overflow-auto rounded-lg border border-gray-200">
460
+ <table class="min-w-full divide-y divide-gray-200" id="tabla-historial">
461
+ <thead class="bg-gray-50">
462
+ <tr>
463
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fecha Pago</th>
464
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fecha Registro</th>
465
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nombre</th>
466
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Descripción</th>
467
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total</th>
468
+ </tr>
469
+ </thead>
470
+ <tbody class="bg-white divide-y divide-gray-200" id="historial-body">
471
+ <tr>
472
+ <td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">Cargando historial...</td>
473
+ </tr>
474
+ </tbody>
475
+ <tfoot class="bg-gray-50">
476
+ <tr>
477
+ <td colspan="4" class="px-6 py-3 text-right text-sm font-medium text-gray-500">TOTAL PAGADO:</td>
478
+ <td class="px-6 py-3 text-left text-sm font-medium text-gray-900" id="suma-total-historial">$0.00</td>
479
+ </tr>
480
+ </tfoot>
481
+ </table>
482
+ </div>
483
+ </div>
484
+ </div>
485
+
486
+ <!-- Pestaña Reportes -->
487
+ <div class="tab-content hidden" id="reportes-content">
488
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
489
+ <div class="card bg-white rounded-xl p-6">
490
+ <div class="flex items-center justify-between mb-4">
491
+ <h3 class="text-lg font-semibold text-gray-800">Pagos por Mes</h3>
492
+ <select id="report-year" class="text-sm border border-gray-300 rounded px-3 py-1 focus:ring-blue-500 focus:border-blue-500">
493
+ <option value="2023">2023</option>
494
+ <option value="2024" selected>2024</option>
495
+ </select>
496
+ </div>
497
+ <div class="h-80">
498
+ <canvas id="monthly-payments-chart"></canvas>
499
+ </div>
500
+ </div>
501
+
502
+ <div class="card bg-white rounded-xl p-6">
503
+ <div class="flex items-center justify-between mb-4">
504
+ <h3 class="text-lg font-semibold text-gray-800">Top Clientes</h3>
505
+ <select id="report-type" class="text-sm border border-gray-300 rounded px 3 py-1 focus:ring-blue-500 focus:border-blue-500">
506
+ <option value="pendientes">Pendientes</option>
507
+ <option value="historial" selected>Historial</option>
508
+ </select>
509
+ </div>
510
+ <div class="h-80">
511
+ <canvas id="top-clients-chart"></canvas>
512
+ </div>
513
+ </div>
514
+ </div>
515
+
516
+ <div class="card bg-white rounded-xl p-6 mb-6">
517
+ <div class="flex items-center justify-between mb-4">
518
+ <h3 class="text-lg font-semibold text-gray-800">Resumen Anual</h3>
519
+ <div class="flex space-x-2">
520
+ <button id="export-report-pdf" class="text-sm bg-red-600 text-white px-4 py-1.5 rounded hover:bg-red-700 flex items-center">
521
+ <i class="fas fa-file-pdf mr-2"></i> PDF
522
+ </button>
523
+ <button id="export-report-excel" class="text-sm bg-green-600 text-white px-4 py-1.5 rounded hover:bg-green-700 flex items-center">
524
+ <i class="fas fa-file-excel mr-2"></i> Excel
525
+ </button>
526
+ </div>
527
+ </div>
528
+ <div class="overflow-x-auto">
529
+ <table class="min-w-full divide-y divide-gray-200">
530
+ <thead class="bg-gray-50">
531
+ <tr>
532
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Año</th>
533
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ene</th>
534
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Feb</th>
535
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Mar</th>
536
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Abr</th>
537
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">May</th>
538
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Jun</th>
539
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Jul</th>
540
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ago</th>
541
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Sep</th>
542
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Oct</th>
543
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nov</th>
544
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Dic</th>
545
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total</th>
546
+ </tr>
547
+ </thead>
548
+ <tbody class="bg-white divide-y divide-gray-200" id="annual-summary">
549
+ <tr>
550
+ <td colspan="14" class="px-6 py-4 text-center text-sm text-gray-500">Cargando datos...</td>
551
+ </tr>
552
+ </tbody>
553
+ </table>
554
+ </div>
555
+ </div>
556
+ </div>
557
+
558
+ <!-- Pestaña Configuración -->
559
+ <div class="tab-content hidden" id="configuracion-content">
560
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
561
+ <div class="card bg-white rounded-xl p-6">
562
+ <h3 class="text-lg font-semibold text-gray-800 mb-4">Respaldo Automático</h3>
563
+ <p class="text-sm text-gray-600 mb-4">Selecciona una carpeta donde se guardarán automáticamente los archivos de respaldo cada vez que agregues o actualices un registro.</p>
564
+
565
+ <div class="mb-4">
566
+ <label class="block text-sm font-medium text-gray-700 mb-1">Carpeta de Respaldo</label>
567
+ <div class="flex">
568
+ <input type="text" id="backup-folder" placeholder="Ninguna carpeta seleccionada" readonly
569
+ class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg bg-gray-50">
570
+ <button onclick="seleccionarCarpeta()"
571
+ class="bg-blue-600 text-white px-4 py-2 rounded-r-lg hover:bg-blue-700 transition">
572
+ <i class="fas fa-folder-open mr-1"></i> Seleccionar
573
+ </button>
574
+ </div>
575
+ </div>
576
+
577
+ <div class="p-4 bg-blue-50 rounded-lg mb-4" id="backup-status">
578
+ <p class="text-sm text-blue-800">Selecciona una carpeta para activar backups.</p>
579
+ </div>
580
+ </div>
581
+
582
+ <div class="card bg-white rounded-xl p-6">
583
+ <h3 class="text-lg font-semibold text-gray-800 mb-4">Respaldo Programado</h3>
584
+ <p class="text-sm text-gray-600 mb-4">Configura respaldos periódicos que incluyan pendientes e historial con marca de tiempo.</p>
585
+
586
+ <div class="mb-4">
587
+ <label for="backup-interval" class="block text-sm font-medium text-gray-700 mb-1">Intervalo</label>
588
+ <select id="backup-interval" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">
589
+ <option value="0">Desactivado</option>
590
+ <option value="5">Cada 5 minutos</option>
591
+ <option value="15">Cada 15 minutos</option>
592
+ <option value="30">Cada 30 minutos</option>
593
+ <option value="60">Cada 1 hora</option>
594
+ <option value="120">Cada 2 horas</option>
595
+ <option value="1440">Diario</option>
596
+ </select>
597
+ </div>
598
+
599
+ <div class="mb 4">
600
+ <label for="backup-format" class="block text-sm font-medium text-gray-700 mb-1">Formato</label>
601
+ <select id="backup-format" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">
602
+ <option value="json">JSON</option>
603
+ <option value="csv">CSV</option>
604
+ </select>
605
+ </div>
606
+
607
+ <button onclick="guardarConfiguracionProgramada()"
608
+ class="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition flex items-center justify-center">
609
+ <i class="fas fa-save mr-2"></i> Guardar Configuración
610
+ </button>
611
+ </div>
612
+ </div>
613
+
614
+ <div class="card bg-white rounded-xl p-6">
615
+ <h3 class="text-lg font-semibold text-gray-800 mb-4">Integrar Código</h3>
616
+ <p class="text-sm text-gray-600 mb-4">Pega aquí el código JavaScript que deseas ejecutar en el sistema.</p>
617
+
618
+ <textarea id="code-area" rows="8" placeholder="Pega aquí el código JavaScript que deseas ejecutar..."
619
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition mb-4 font-mono text-sm"></textarea>
620
+
621
+ <div class="flex justify-end">
622
+ <button onclick="integrarCodigo()"
623
+ class="bg-purple-600 text-white py-2 px-6 rounded-lg hover:bg-purple-700 transition flex items-center">
624
+ <i class="fas fa-code mr-2"></i> Ejecutar Código
625
+ </button>
626
+ </div>
627
+
628
+ <div class="mt-4 p-3 bg-red-50 rounded-lg">
629
+ <p class="text-sm text-red-700"><i class="fas fa-exclamation-triangle mr-2"></i> ADVERTENCIA: Ejecutar código desconocido puede ser peligroso. Solo usa código de fuentes confiables.</p>
630
+ </div>
631
+ </div>
632
+ </div>
633
+ </div>
634
+ </div>
635
+ </div>
636
+
637
+ <script>
638
+ // --- Strict mode ---
639
+ "use strict";
640
+
641
+ /*******************************
642
+ * Variables Globales
643
+ *******************************/
644
+ let modoEdicion = false;
645
+ let idEdicion = null;
646
+ let backupFolderHandle = null;
647
+ let paymentsChart = null;
648
+ let categoriesChart = null;
649
+ let monthlyPaymentsChart = null;
650
+ let topClientsChart = null;
651
+ let btnAgregar = document.getElementById('btn-agregar');
652
+ let btnCancelarEdicion = document.getElementById('btn-cancelar-edicion');
653
+
654
+ /*******************************
655
+ * App Initialization
656
+ *******************************/
657
+ async function initializeApp() {
658
+ document.getElementById('fecha').valueAsDate = new Date();
659
+ configurarEventosGenerales();
660
+
661
+ // Set default tab to dashboard
662
+ activarTab('dashboard');
663
+
664
+ // Load initial data
665
+ try {
666
+ const pendientesStored = await memoryStorage.getPendientes();
667
+ if (!pendientesStored || pendientesStored.length === 0) {
668
+ await cargarDatosEjemplo();
669
+ }
670
+ await cargarDatos();
671
+ await cargarHistorial();
672
+ await updateDashboard();
673
+ await loadReports();
674
+ } catch (error) {
675
+ console.error("Error during initial data loading:", error);
676
+ alert("Error al cargar los datos iniciales: " + error.message);
677
+ }
678
+
679
+ cargarConfiguracionProgramada();
680
+ }
681
+
682
+ /*******************************
683
+ * Almacenamiento (localForage)
684
+ *******************************/
685
+ const memoryStorage = {
686
+ async getPendientes() {
687
+ const pendientes = await localforage.getItem('pendientes');
688
+ return pendientes || [];
689
+ },
690
+ async setPendientes(newData) {
691
+ if (!Array.isArray(newData)) {
692
+ return Promise.reject(new Error("Invalid data format for pendientes"));
693
+ }
694
+ return localforage.setItem('pendientes', newData);
695
+ },
696
+ async getHistorial() {
697
+ const historial = await localforage.getItem('historial');
698
+ return historial || [];
699
+ },
700
+ async setHistorial(newData) {
701
+ if (!Array.isArray(newData)) {
702
+ return Promise.reject(new Error("Invalid data format for historial"));
703
+ }
704
+ return localforage.setItem('historial', newData);
705
+ },
706
+ async addToHistorial(pendiente) {
707
+ if (!pendiente || typeof pendiente !== 'object') {
708
+ return Promise.reject(new Error("Invalid item format for historial"));
709
+ }
710
+ let historial = await this.getHistorial();
711
+ const pagoRegistro = {
712
+ ...pendiente,
713
+ fechaPago: new Date().toISOString().split('T')[0]
714
+ };
715
+ historial.push(pagoRegistro);
716
+ return this.setHistorial(historial);
717
+ },
718
+ async clearData() {
719
+ await localforage.removeItem('pendientes');
720
+ await localforage.removeItem('historial');
721
+ }
722
+ };
723
+
724
+ /*******************************
725
+ * Configuración Eventos Generales
726
+ *******************************/
727
+ function configurarEventosGenerales() {
728
+ // Tabs
729
+ document.querySelectorAll('.tab-link').forEach(tab => {
730
+ tab.addEventListener('click', (e) => {
731
+ e.preventDefault();
732
+ const tab = tab.dataset.tab;
733
+ activarTab(tab);
734
+ });
735
+ });
736
+
737
+ // Botón Agregar/Actualizar
738
+ btnAgregar?.addEventListener('click', guardarRegistro);
739
+
740
+ // Botón Cancelar Edición
741
+ btnCancelarEdicion?.addEventListener('click', resetearFormulario);
742
+
743
+ // Filtros de búsqueda
744
+ document.getElementById('buscar')?.addEventListener('input', cargarDatos);
745
+ document.getElementById('buscar-historial')?.addEventListener('input', cargarHistorial);
746
+ document.getElementById('fecha-inicio')?.addEventListener('change', cargarHistorial);
747
+ document.getElementById('fecha-fin')?.addEventListener('change', cargarHistorial);
748
+
749
+ // Descargar imagen
750
+ document.getElementById('descargar-busqueda')?.addEventListener('click', descargarResultadosComoImagen);
751
+
752
+ // Exportar/Importar
753
+ document.getElementById('exportar-json')?.addEventListener('click', exportarPendientesJSON);
754
+ document.getElementById('exportar-excel')?.addEventListener('click', exportarPendientesCSV);
755
+ document.getElementById('exportar-historial-json')?.addEventListener('click', exportarHistorialJSON);
756
+ document.getElementById('exportar-historial-excel')?.addEventListener('click', exportarHistorialCSV);
757
+ document.getElementById('importar-json')?.addEventListener('click', importarPendientesJSON);
758
+ document.getElementById('importar-excel')?.addEventListener('click', importarPendientesCSV);
759
+
760
+ // Limpiar datos
761
+ document.getElementById('limpiar-datos')?.addEventListener('click', async () => {
762
+ if (confirm('¿Está seguro de eliminar todos los datos (pendientes e historial)? Esta acción no se puede deshacer.')) {
763
+ try {
764
+ await memoryStorage.clearData();
765
+ await cargarDatos();
766
+ await cargarHistorial();
767
+ await updateDashboard();
768
+ await loadReports();
769
+ alert('Todos los datos han sido eliminados.');
770
+ await eliminarBackupsInternos();
771
+ resetearFormulario();
772
+ } catch (error) {
773
+ alert("Error al limpiar los datos: " + error.message);
774
+ }
775
+ }
776
+ });
777
+
778
+ // Reportes
779
+ document.getElementById('report-year')?.addEventListener('change', loadReports);
780
+ document.getElementById('report-type')?.addEventListener('change', loadReports);
781
+ }
782
+
783
+ function activarTab(tab) {
784
+ // Update active tab in sidebar
785
+ document.querySelectorAll('.tab-link').forEach(t => {
786
+ t.classList.remove('bg-blue-50', 'text-blue-700');
787
+ t.classList.add('text-gray-700', 'hover:bg-gray-100');
788
+
789
+ // Update icon colors
790
+ const icon = t.querySelector('i');
791
+ if (icon) {
792
+ icon.classList.remove('text-blue-600');
793
+ icon.classList.add('text-gray-500');
794
+ }
795
+ });
796
+
797
+ // Set active tab
798
+ const activeTab = document.querySelector(`.tab-link[data-tab="${tab}"]`);
799
+ if (activeTab) {
800
+ activeTab.classList.add('bg-blue-50', 'text-blue-700');
801
+ activeTab.classList.remove('text-gray-700', 'hover:bg-gray-100');
802
+
803
+ // Update icon color
804
+ const icon = activeTab.querySelector('i');
805
+ if (icon) {
806
+ icon.classList.add('text-blue-600');
807
+ icon.classList.remove('text-gray-500');
808
+ }
809
+ }
810
+
811
+ // Hide all tab contents
812
+ document.querySelectorAll('.tab-content').forEach(t => t.classList.add('hidden'));
813
+
814
+ // Show selected tab content
815
+ const contentElement = document.getElementById(`${tab}-content`);
816
+ if (contentElement) {
817
+ contentElement.classList.remove('hidden');
818
+ }
819
+
820
+ // Update title
821
+ const tabTitles = {
822
+ 'dashboard': 'Dashboard',
823
+ 'pendientes': 'Pendientes',
824
+ 'historial': 'Historial',
825
+ 'reportes': 'Reportes',
826
+ 'configuracion': 'Configuración'
827
+ };
828
+ document.getElementById('current-tab-title').textContent = tabTitles[tab] || tab;
829
+
830
+ // Load specific tab data if needed
831
+ if (tab === 'dashboard') {
832
+ updateDashboard();
833
+ } else if (tab === 'reportes') {
834
+ loadReports();
835
+ }
836
+ }
837
+
838
+ /*******************************
839
+ * Funciones principales
840
+ *******************************/
841
+ async function guardarRegistro() {
842
+ const fecha = document.getElementById('fecha').value;
843
+ const nombre = document.getElementById('nombre').value.trim();
844
+ const descripcion = document.getElementById('descripcion').value.trim();
845
+ const totalInput = document.getElementById('total').value;
846
+
847
+ if (!fecha || !nombre || !descripcion || totalInput === '') {
848
+ alert('Por favor, complete todos los campos.');
849
+ return;
850
+ }
851
+
852
+ const total = parseFloat(totalInput);
853
+ if (isNaN(total)) {
854
+ alert('El valor total debe ser un número válido.');
855
+ return;
856
+ }
857
+
858
+ try {
859
+ let pendientes = await memoryStorage.getPendientes();
860
+ let mensajeExito = '';
861
+
862
+ if (modoEdicion && idEdicion !== null) {
863
+ const index = pendientes.findIndex(item => item.id == idEdicion);
864
+ if (index !== -1) {
865
+ pendientes[index] = { id: parseInt(idEdicion), fecha, nombre, descripcion, total };
866
+ mensajeExito = 'Registro actualizado correctamente.';
867
+ } else {
868
+ alert("Error al actualizar: No se encontró el registro.");
869
+ resetearFormulario();
870
+ return;
871
+ }
872
+ } else {
873
+ const nuevoId = Date.now();
874
+ const nuevoRegistro = { id: nuevoId, fecha, nombre, descripcion, total };
875
+ pendientes.push(nuevoRegistro);
876
+ mensajeExito = 'Registro agregado correctamente.';
877
+ }
878
+
879
+ await memoryStorage.setPendientes(pendientes);
880
+ await generarBackupInterno().catch(err => console.error("Error during internal backup:", err));
881
+
882
+ resetearFormulario();
883
+ await cargarDatos();
884
+ await updateDashboard();
885
+ await loadReports();
886
+
887
+ } catch (error) {
888
+ alert(`Error al guardar el registro: ${error.message}`);
889
+ }
890
+ }
891
+
892
+ async function cargarDatos() {
893
+ const tbody = document.getElementById('pendientes-body');
894
+ const busqueda = document.getElementById('buscar').value.toLowerCase();
895
+
896
+ tbody.innerHTML = '<tr><td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">Cargando...</td></tr>';
897
+
898
+ try {
899
+ let pendientes = await memoryStorage.getPendientes();
900
+
901
+ // Filtrar
902
+ let filtrados = pendientes.filter(item =>
903
+ !busqueda ||
904
+ (item.nombre && item.nombre.toLowerCase().includes(busqueda)) ||
905
+ (item.descripcion && item.descripcion.toLowerCase().includes(busqueda)) ||
906
+ (item.fecha && item.fecha.includes(busqueda))
907
+ );
908
+
909
+ // Ordenar por fecha (ascendente)
910
+ filtrados.sort((a, b) => {
911
+ try { return new Date(a.fecha) - new Date(b.fecha); }
912
+ catch(e) { return 0; }
913
+ });
914
+
915
+ tbody.innerHTML = "";
916
+ let sumaTotal = 0;
917
+
918
+ if (filtrados.length === 0) {
919
+ const msg = busqueda ? "No se encontraron registros con ese criterio." : "No hay registros pendientes.";
920
+ tbody.innerHTML = `<tr><td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">${msg}</td></tr>`;
921
+ } else {
922
+ filtrados.forEach(item => {
923
+ let fechaFormateada = item.fecha;
924
+ try {
925
+ const dateParts = item.fecha.split('-');
926
+ if (dateParts.length === 3) {
927
+ const year = parseInt(dateParts[0]);
928
+ const month = parseInt(dateParts[1]) - 1;
929
+ const day = parseInt(dateParts[2]);
930
+ fechaFormateada = new Date(year, month, day).toLocaleDateString();
931
+ }
932
+ } catch (e) { console.warn(`Fecha inválida: ${item.fecha}`); }
933
+
934
+ const row = tbody.insertRow();
935
+ row.className = 'hover:bg-gray-50';
936
+
937
+ // Fecha
938
+ const cellFecha = row.insertCell();
939
+ cellFecha.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
940
+ cellFecha.textContent = fechaFormateada;
941
+
942
+ // Nombre
943
+ const cellNombre = row.insertCell();
944
+ cellNombre.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
945
+ cellNombre.textContent = item.nombre;
946
+
947
+ // Descripción
948
+ const cellDesc = row.insertCell();
949
+ cellDesc.className = 'px-6 py-4 text-sm text-gray-900';
950
+ cellDesc.textContent = item.descripcion;
951
+
952
+ // Total
953
+ const cellTotal = row.insertCell();
954
+ cellTotal.className = 'px-6 py-4 whitespace-nowrap text-sm text-right font-medium';
955
+ cellTotal.textContent = `$${item.total?.toFixed(2) ?? '0.00'}`;
956
+ cellTotal.style.color = item.total < 0 ? '#dc2626' : '#1f2937';
957
+
958
+ // Acciones
959
+ const cellAcciones = row.insertCell();
960
+ cellAcciones.className = 'px-6 py-4 whitespace-nowrap text-right text-sm font-medium';
961
+
962
+ const btnContainer = document.createElement('div');
963
+ btnContainer.className = 'flex space-x-2 justify-end';
964
+
965
+ // Botón Editar
966
+ const btnEditar = document.createElement('button');
967
+ btnEditar.className = 'action-btn text-blue-600 hover:text-blue-900';
968
+ btnEditar.title = 'Editar este registro';
969
+ btnEditar.innerHTML = '<i class="fas fa-edit"></i>';
970
+ btnEditar.addEventListener('click', () => editarRegistro(item.id));
971
+
972
+ // Botón Eliminar
973
+ const btnEliminar = document.createElement('button');
974
+ btnEliminar.className = 'action-btn text-red-600 hover:text-red-900';
975
+ btnEliminar.title = 'Eliminar este registro';
976
+ btnEliminar.innerHTML = '<i class="fas fa-trash"></i>';
977
+ btnEliminar.addEventListener('click', () => eliminarRegistro(item.id));
978
+
979
+ // Botón Pagado
980
+ const btnPagado = document.createElement('button');
981
+ btnPagado.className = 'action-btn text-green-600 hover:text-green-900';
982
+ btnPagado.title = 'Marcar como pagado';
983
+ btnPagado.innerHTML = '<i class="fas fa-check-circle"></i>';
984
+ btnPagado.addEventListener('click', () => marcarComoPagado(item.id));
985
+
986
+ btnContainer.appendChild(btnEditar);
987
+ btnContainer.appendChild(btnEliminar);
988
+ btnContainer.appendChild(btnPagado);
989
+ cellAcciones.appendChild(btnContainer);
990
+
991
+ // Sum total
992
+ if (typeof item.total === 'number' && !isNaN(item.total)) {
993
+ sumaTotal += item.total;
994
+ }
995
+ });
996
+ }
997
+
998
+ document.getElementById('suma-total').textContent = `$${sumaTotal.toFixed(2)}`;
999
+ document.getElementById('pendientes-summary-total').textContent = `$${sumaTotal.toFixed(2)}`;
1000
+ document.getElementById('pendientes-summary-count').textContent = filtrados.length;
1001
+
1002
+ } catch (error) {
1003
+ tbody.innerHTML = `<tr><td colspan="5" class="px-6 py-4 text-center text-sm text-red-500">Error al cargar datos: ${error.message}</td></tr>`;
1004
+ }
1005
+ }
1006
+
1007
+ async function cargarHistorial() {
1008
+ const tbody = document.getElementById('historial-body');
1009
+ const busqueda = document.getElementById('buscar-historial').value.toLowerCase();
1010
+ const fechaInicio = document.getElementById('fecha-inicio')?.value;
1011
+ const fechaFin = document.getElementById('fecha-fin')?.value;
1012
+
1013
+ tbody.innerHTML = '<tr><td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">Cargando...</td></tr>';
1014
+
1015
+ try {
1016
+ let historial = await memoryStorage.getHistorial();
1017
+
1018
+ // Filtrar
1019
+ historial = historial.filter(item => {
1020
+ // Filtro de búsqueda
1021
+ const matchSearch = !busqueda ||
1022
+ (item.nombre && item.nombre.toLowerCase().includes(busqueda)) ||
1023
+ (item.descripcion && item.descripcion.toLowerCase().includes(busqueda)) ||
1024
+ (item.fecha && item.fecha.includes(busqueda)) ||
1025
+ (item.fechaPago && item.fechaPago.includes(busqueda));
1026
+
1027
+ // Filtro por fecha
1028
+ let matchDate = true;
1029
+ if (fechaInicio) {
1030
+ matchDate = matchDate && item.fechaPago >= fechaInicio;
1031
+ }
1032
+ if (fechaFin) {
1033
+ matchDate = matchDate && item.fechaPago <= fechaFin;
1034
+ }
1035
+
1036
+ return matchSearch && matchDate;
1037
+ });
1038
+
1039
+ // Ordenar por fecha de pago (descendente)
1040
+ historial.sort((a, b) => {
1041
+ try { return new Date(b.fechaPago) - new Date(a.fechaPago); }
1042
+ catch(e) { return 0; }
1043
+ });
1044
+
1045
+ tbody.innerHTML = "";
1046
+ let sumaTotalHistorial = 0;
1047
+
1048
+ if (historial.length === 0) {
1049
+ const msg = busqueda ? "No se encontraron registros con ese criterio." : "El historial de pagos está vacío.";
1050
+ tbody.innerHTML = `<tr><td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">${msg}</td></tr>`;
1051
+ } else {
1052
+ historial.forEach(item => {
1053
+ let fechaPagoF = item.fechaPago;
1054
+ let fechaRegF = item.fecha;
1055
+ try {
1056
+ const dpParts = item.fechaPago?.split('-');
1057
+ if (dpParts?.length === 3) fechaPagoF = new Date(parseInt(dpParts[0]), parseInt(dpParts[1]) - 1, parseInt(dpParts[2])).toLocaleDateString();
1058
+ const drParts = item.fecha?.split('-');
1059
+ if (drParts?.length === 3) fechaRegF = new Date(parseInt(drParts[0]), parseInt(drParts[1]) - 1, parseInt(drParts[2])).toLocaleDateString();
1060
+ } catch (e) { /* Ignore date formatting errors silently */ }
1061
+
1062
+ const row = tbody.insertRow();
1063
+ row.className = 'hover:bg-gray-50';
1064
+
1065
+ // Fecha Pago
1066
+ const cellFechaPago = row.insertCell();
1067
+ cellFechaPago.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
1068
+ cellFechaPago.textContent = fechaPagoF;
1069
+
1070
+ // Fecha Registro
1071
+ const cellFechaReg = row.insertCell();
1072
+ cellFechaReg.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
1073
+ cellFechaReg.textContent = fechaRegF;
1074
+
1075
+ // Nombre
1076
+ const cellNombre = row.insertCell();
1077
+ cellNombre.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
1078
+ cellNombre.textContent = item.nombre;
1079
+
1080
+ // Descripción
1081
+ const cellDesc = row.insertCell();
1082
+ cellDesc.className = 'px-6 py-4 text-sm text-gray-900';
1083
+ cellDesc.textContent = item.descripcion;
1084
+
1085
+ // Total
1086
+ const cellTotal = row.insertCell();
1087
+ cellTotal.className = 'px-6 py-4 whitespace-nowrap text-sm text-right font-medium';
1088
+ cellTotal.textContent = `$${item.total?.toFixed(2) ?? '0.00'}`;
1089
+ cellTotal.style.color = item.total < 0 ? '#dc2626' : '#1f2937';
1090
+
1091
+ if (typeof item.total === 'number' && !isNaN(item.total)) {
1092
+ sumaTotalHistorial += item.total;
1093
+ }
1094
+ });
1095
+ }
1096
+
1097
+ document.getElementById('suma-total-historial').textContent = `$${sumaTotalHistorial.toFixed(2)}`;
1098
+ document.getElementById('historial-summary-total').textContent = `$${sumaTotalHistorial.toFixed(2)}`;
1099
+ document.getElementById('historial-summary-count').textContent = historial.length;
1100
+
1101
+ // Update filter info
1102
+ let filterInfo = "Mostrando todos los registros";
1103
+ if (fechaInicio || fechaFin) {
1104
+ filterInfo = `Filtrado por fecha: ${fechaInicio || 'inicio'} - ${fechaFin || 'fin'}`;
1105
+ }
1106
+ document.getElementById('historial-filter-info').textContent = filterInfo;
1107
+
1108
+ } catch (error) {
1109
+ tbody.innerHTML = `<tr><td colspan="5, class="px-6 py-4 text-center text-sm text-red-500">Error al cargar historial: ${error.message}</td></tr>`;
1110
+ }
1111
+ }
1112
+
1113
+ /*******************************
1114
+ * Funciones de edición/eliminación
1115
+ *******************************/
1116
+ async function eliminarRegistro(id) {
1117
+ if (!id) return;
1118
+
1119
+ if (confirm(`¿Está seguro de eliminar permanentemente el registro pendiente?`)) {
1120
+ try {
1121
+ let pendientes = await memoryStorage.getPendientes();
1122
+ const originalLength = pendientes.length;
1123
+ pendientes = pendientes.filter(item => item.id != id);
1124
+
1125
+ if (pendientes.length < originalLength) {
1126
+ await memoryStorage.setPendientes(pendientes);
1127
+ await cargarDatos();
1128
+ await updateDashboard();
1129
+ await loadReports();
1130
+ await generarBackupInterno().catch(err => console.error("Backup failed after delete:", err));
1131
+
1132
+ if (modoEdicion && idEdicion == id) {
1133
+ resetearFormulario();
1134
+ }
1135
+ } else {
1136
+ alert('Error: No se encontró el registro para eliminar.');
1137
+ }
1138
+ } catch (error) {
1139
+ alert(`Error al eliminar el registro: ${error.message}`);
1140
+ }
1141
+ }
1142
+ }
1143
+
1144
+ async function marcarComoPagado(id) {
1145
+ if (!id) return;
1146
+
1147
+ if (confirm(`¿Marcar este registro como pagado y moverlo al historial?`)) {
1148
+ try {
1149
+ let pendientes = await memoryStorage.getPendientes();
1150
+ const index = pendientes.findIndex(item => item.id == id);
1151
+
1152
+ if (index !== -1) {
1153
+ const itemPagado = pendientes[index];
1154
+ await memoryStorage.addToHistorial(itemPagado);
1155
+
1156
+ pendientes.splice(index, 1);
1157
+ await memoryStorage.setPendientes(pendientes);
1158
+
1159
+ await cargarDatos();
1160
+ await cargarHistorial();
1161
+ await updateDashboard();
1162
+ await loadReports();
1163
+ await generarBackupInterno().catch(err => console.error("Backup failed after mark paid:", err));
1164
+
1165
+ if (modoEdicion && idEdicion == id) {
1166
+ resetearFormulario();
1167
+ }
1168
+ } else {
1169
+ alert('Error: No se encontró el registro para marcar como pagado.');
1170
+ }
1171
+ } catch(error) {
1172
+ alert(`Error al marcar como pagado: ${error.message}`);
1173
+ }
1174
+ }
1175
+ }
1176
+
1177
+ async function editarRegistro(id) {
1178
+ if (!id) return;
1179
+
1180
+ try {
1181
+ let pendientes = await memoryStorage.getPendientes();
1182
+ const registro = pendientes.find(item => item.id == id);
1183
+
1184
+ if (registro) {
1185
+ document.getElementById('fecha').value = registro.fecha;
1186
+ document.getElementById('nombre').value = registro.nombre;
1187
+ document.getElementById('descripcion').value = registro.descripcion;
1188
+ document.getElementById('total').value = registro.total;
1189
+ modoEdicion = true;
1190
+ idEdicion = id;
1191
+
1192
+ btnAgregar.innerHTML = '<i class="fas fa-save mr-2"></i> Actualizar';
1193
+ btnAgregar.className = 'bg-yellow-600 text-white py-2 px-6 rounded-lg hover:bg-yellow-700 transition flex items-center';
1194
+ btnCancelarEdicion.classList.remove('hidden');
1195
+
1196
+ // Scroll to form
1197
+ const nombreInput = document.getElementById('nombre');
1198
+ if (nombreInput) {
1199
+ nombreInput.focus();
1200
+ nombreInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
1201
+ }
1202
+ } else {
1203
+ alert("Error: No se pudo encontrar el registro para editar.");
1204
+ resetearFormulario();
1205
+ }
1206
+ } catch (error) {
1207
+ alert(`Error al preparar la edición: ${error.message}`);
1208
+ resetearFormulario();
1209
+ }
1210
+ }
1211
+
1212
+ function resetearFormulario() {
1213
+ document.getElementById('fecha').valueAsDate = new Date();
1214
+ document.getElementById('nombre').value = '';
1215
+ document.getElementById('descripcion').value = '';
1216
+ document.getElementById('total').value = '';
1217
+ modoEdicion = false;
1218
+ idEdicion = null;
1219
+
1220
+ btnAgregar.innerHTML = '<i class="fas fa-plus mr-2"></i> Agregar';
1221
+ btnAgregar.className = 'bg-blue-600 text-white py-2 px-6 rounded-lg hover:bg-blue-700 transition flex items-center';
1222
+ btnCancelarEdicion.classList.add('hidden');
1223
+
1224
+ document.getElementById('nombre').focus();
1225
+ }
1226
+
1227
+ /*******************************
1228
+ * Dashboard y Reportes
1229
+ *******************************/
1230
+ async function updateDashboard() {
1231
+ try {
1232
+ const pendientes = await memoryStorage.getPendientes();
1233
+ const historial = await memoryStorage.getHistorial();
1234
+
1235
+ // Totales
1236
+ const totalPendiente = pendientes.reduce((sum, item) => sum + (item.total || 0), 0);
1237
+ const totalPagado = historial.reduce((sum, item) => sum + (item.total || 0), 0);
1238
+
1239
+ // Contadores
1240
+ const countPendientes = pendientes.length;
1241
+ const countHistorial = historial.length;
1242
+
1243
+ // Mes actual
1244
+ const now = new Date();
1245
+ const currentMonth = now.getMonth() + 1;
1246
+ const currentYear = now.getFullYear();
1247
+
1248
+ const monthPagado = historial
1249
+ .filter(item => {
1250
+ const dateParts = item.fechaPago?.split('-');
1251
+ if (dateParts?.length === 3) {
1252
+ return parseInt(dateParts[0]) === currentYear && parseInt(dateParts[1]) === currentMonth;
1253
+ }
1254
+ return false;
1255
+ })
1256
+ .reduce((sum, item) => sum + (item.total || 0), 0);
1257
+
1258
+ // Vencidos (más de 30 días)
1259
+ const countVencidos = pendientes.filter(item => {
1260
+ try {
1261
+ const itemDate = new Date(item.fecha);
1262
+ const diffTime = now - itemDate;
1263
+ const diffDays = diffTime / (1000 * 60 * 60 * 24);
1264
+ return diffDays > 30;
1265
+ } catch (e) {
1266
+ return false;
1267
+ }
1268
+ }).length;
1269
+
1270
+ // Actualizar UI
1271
+ document.getElementById('total-pendiente').textContent = `$${totalPendiente.toFixed(2)}`;
1272
+ document.getElementById('total-pagado').textContent = `$${totalPagado.toFixed(2)}`;
1273
+ document.getElementById('month-pagado').textContent = `$${monthPagado.toFixed(2)}`;
1274
+ document.getElementById('count-pendientes').textContent = countPendientes;
1275
+ document.getElementById('count-historial').textContent = countHistorial;
1276
+ document.getElementById('count-vencidos').textContent = countVencidos;
1277
+ document.getElementById('last-update').textContent = new Date().toLocaleTimeString();
1278
+
1279
+ // Próximos vencimientos (próximos 7 días)
1280
+ const upcomingPayments = pendientes
1281
+ .filter(item => {
1282
+ try {
1283
+ const itemDate = new Date(item.fecha);
1284
+ const diffTime = itemDate - now;
1285
+ const diffDays = diffTime / (1000 * 60 * 60 * 24);
1286
+ return diffDays > 0 && diffDays <= 7;
1287
+ } catch (e) {
1288
+ return false;
1289
+ }
1290
+ })
1291
+ .sort((a, b) => new Date(a.fecha) - new Date(b.fecha))
1292
+ .slice(0, 5);
1293
+
1294
+ const upcomingTbody = document.getElementById('upcoming-payments');
1295
+ upcomingTbody.innerHTML = '';
1296
+
1297
+ if (upcomingPayments.length === 0) {
1298
+ upcomingTbody.innerHTML = '<tr><td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">No hay pagos próximos en los próximos 7 días</td></tr>';
1299
+ } else {
1300
+ upcomingPayments.forEach(item => {
1301
+ const row = upcomingTbody.insertRow();
1302
+ row.className = 'hover:bg-gray-50';
1303
+
1304
+ // Fecha
1305
+ const cellFecha = row.insertCell();
1306
+ cellFecha.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
1307
+ try {
1308
+ const dateParts = item.fecha.split('-');
1309
+ if (dateParts.length === 3) {
1310
+ cellFecha.textContent = new Date(parseInt(dateParts[0]), parseInt(dateParts[1]) - 1, parseInt(dateParts[2])).toLocaleDateString();
1311
+ } else {
1312
+ cellFecha.textContent = item.fecha;
1313
+ }
1314
+ } catch (e) {
1315
+ cellFecha.textContent = item.fecha;
1316
+ }
1317
+
1318
+ // Nombre
1319
+ const cellNombre = row.insertCell();
1320
+ cellNombre.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
1321
+ cellNombre.textContent = item.nombre;
1322
+
1323
+ // Descripción
1324
+ const cellDesc = row.insertCell();
1325
+ cellDesc.className = 'px-6 py-4 text-sm text-gray-900';
1326
+ cellDesc.textContent = item.descripcion;
1327
+
1328
+ // Total
1329
+ const cellTotal = row.insertCell();
1330
+ cellTotal.className = 'px-6 py-4 whitespace-nowrap text-sm text-right font-medium';
1331
+ cellTotal.textContent = `$${item.total?.toFixed(2) ?? '0.00'}`;
1332
+ cellTotal.style.color = item.total < 0 ? '#dc2626' : '#1f2937';
1333
+
1334
+ // Estado
1335
+ const cellEstado = row.insertCell();
1336
+ cellEstado.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
1337
+
1338
+ const badge = document.createElement('span');
1339
+ badge.className = 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium';
1340
+
1341
+ try {
1342
+ const itemDate = new Date(item.fecha);
1343
+ const diffTime = itemDate - now;
1344
+ const diffDays = diffTime / (1000 * 60 * 60 * 24);
1345
+
1346
+ if (diffDays < 0) {
1347
+ badge.textContent = 'Vencido';
1348
+ badge.classList.add('bg-red-100', 'text-red-800');
1349
+ } else if (diffDays <= 3) {
1350
+ badge.textContent = 'Próximo';
1351
+ badge.classList.add('bg-yellow-100', 'text-yellow-800');
1352
+ } else {
1353
+ badge.textContent = 'Pendiente';
1354
+ badge.classList.add('bg-blue-100', 'text-blue-800');
1355
+ }
1356
+ } catch (e) {
1357
+ badge.textContent = 'Pendiente';
1358
+ badge.classList.add('bg-blue-100', 'text-blue-800');
1359
+ }
1360
+
1361
+ cellEstado.appendChild(badge);
1362
+ });
1363
+ }
1364
+
1365
+ // Actualizar gráficos
1366
+ actualizarGraficosDashboard(pendientes, historial);
1367
+
1368
+ } catch (error) {
1369
+ console.error("Error updating dashboard:", error);
1370
+ }
1371
+ }
1372
+
1373
+ function actualizarGraficosDashboard(pendientes, historial) {
1374
+ // Gráfico de evolución de pagos
1375
+ const ctxPayments = document.getElementById('payments-chart').getContext('2d');
1376
+
1377
+ // Datos de ejemplo para el gráfico
1378
+ const labels = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'];
1379
+ const currentMonth = new Date().getMonth();
1380
+ const currentYear = new Date().getFullYear();
1381
+
1382
+ // Calcular totales por mes
1383
+ const monthlyTotals = Array(12).fill(0);
1384
+ historial.forEach(item => {
1385
+ try {
1386
+ const dateParts = item.fechaPago?.split('-');
1387
+ if (dateParts?.length === 3) {
1388
+ const year = parseInt(dateParts[0]);
1389
+ const month = parseInt(dateParts[1]) - 1;
1390
+
1391
+ if (year === currentYear && month >= 0 && month < 12) {
1392
+ monthlyTotals[month] += item.total || 0;
1393
+ }
1394
+ }
1395
+ } catch (e) { /* Ignore errors */ }
1396
+ });
1397
+
1398
+ if (paymentsChart) {
1399
+ paymentsChart.destroy();
1400
+ }
1401
+
1402
+ paymentsChart = new Chart(ctxPayments, {
1403
+ type: 'line',
1404
+ data: {
1405
+ labels: labels,
1406
+ datasets: [{
1407
+ label: 'Pagos por mes',
1408
+ data: monthlyTotals,
1409
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
1410
+ borderColor: 'rgba(59, 130, 246, 1)',
1411
+ borderWidth: 2,
1412
+ tension: 0.1,
1413
+ fill: true
1414
+ }]
1415
+ },
1416
+ options: {
1417
+ responsive: true,
1418
+ maintainAspectRatio: false,
1419
+ plugins: {
1420
+ legend: {
1421
+ display: false
1422
+ }
1423
+ },
1424
+ scales: {
1425
+ y: {
1426
+ beginAtZero: true,
1427
+ grid: {
1428
+ color: 'rgba(0
1429
+ </html>