utn-cli 2.0.57 → 2.0.59

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.
@@ -249,6 +249,7 @@ export async function addServiceBackend(nombreServicio = null, opciones = { cerr
249
249
  function generarCRUDBackend(servicioPath, rutaPath, nombreServicio, tableInfo) {
250
250
  const { dbName, tableName, columns, primaryKey } = tableInfo;
251
251
  const dbNameSinDB = dbName.endsWith('-db') ? dbName.slice(0, -3) : dbName;
252
+ const tituloServicio = tableInfo.serviceTitle || nombreServicio;
252
253
 
253
254
  // 1. Generar funciones en el Servicio
254
255
  let contenidoServicio = fs.readFileSync(servicioPath, 'utf-8');
@@ -303,7 +304,7 @@ Router.get('/listar', async (solicitud, respuesta, next) => {
303
304
  try {
304
305
  return respuesta.json({ body: await ${nombreServicio}.listar(), error: undefined });
305
306
  } catch (error) {
306
- const MensajeDeError = 'No fue posible listar los registros';
307
+ const MensajeDeError = 'No fue posible listar los registros de ${tituloServicio}';
307
308
  console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
308
309
  return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
309
310
  }
@@ -322,7 +323,7 @@ Router.post('/agregar', async (solicitud, respuesta, next) => {
322
323
  Datos.LastUser = await Miscelaneo.generarLastUser(solicitud);
323
324
  return respuesta.json({ body: await ${nombreServicio}.agregar(Datos), error: undefined });
324
325
  } catch (error) {
325
- const MensajeDeError = 'No fue posible agregar el registro';
326
+ const MensajeDeError = 'No fue posible agregar el registro de ${tituloServicio}';
326
327
  console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
327
328
  return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
328
329
  }
@@ -341,7 +342,7 @@ Router.post('/actualizar', async (solicitud, respuesta, next) => {
341
342
  Datos.LastUser = await Miscelaneo.generarLastUser(solicitud);
342
343
  return respuesta.json({ body: await ${nombreServicio}.actualizar(Datos), error: undefined });
343
344
  } catch (error) {
344
- const MensajeDeError = 'No fue posible actualizar el registro';
345
+ const MensajeDeError = 'No fue posible actualizar el registro de ${tituloServicio}';
345
346
  console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
346
347
  return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
347
348
  }
@@ -358,7 +359,7 @@ Router.post('/borrar', async (solicitud, respuesta, next) => {
358
359
  try {
359
360
  return respuesta.json({ body: await ${nombreServicio}.borrar(solicitud.body), error: undefined });
360
361
  } catch (error) {
361
- const MensajeDeError = 'No fue posible borrar el registro';
362
+ const MensajeDeError = 'No fue posible borrar el registro de ${tituloServicio}';
362
363
  console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
363
364
  return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
364
365
  }
@@ -377,7 +378,7 @@ Router.get('/reporte', async (solicitud, respuesta, next) => {
377
378
  respuesta.attachment('Datos.csv');
378
379
  return respuesta.send(encodeURI(await ${nombreServicio}.reporte()));
379
380
  } catch (error) {
380
- const MensajeDeError = 'No fue posible generar el reporte';
381
+ const MensajeDeError = 'No fue posible generar el reporte de ${tituloServicio}';
381
382
  console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, \`Dirección IP: \${solicitud.ip}\`));
382
383
  return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
383
384
  }
@@ -86,6 +86,8 @@ export async function cloneFrontendComponent(nombreComponente = null, tituloCard
86
86
  const nombreCapitalizado = nombre.charAt(0).toUpperCase() + nombre.slice(1);
87
87
  const nombreClase = `GestionTabla${nombreCapitalizado}Component`;
88
88
  const nombreRuta = `gestion-tabla-${nombreLower}`;
89
+ const nombreModal = `Guardar${nombreCapitalizado}Component`;
90
+ const nombreCarpetaModal = `guardar-${nombreLower}`;
89
91
 
90
92
  const rutaFuente = path.join(__dirname, '../templates/frontend', 'src', 'app', 'Paginas', 'gestion-tabla-XYZ');
91
93
  const rutaDestino = path.join(process.cwd(), 'src', 'app', 'Paginas', nombreRuta);
@@ -102,103 +104,96 @@ export async function cloneFrontendComponent(nombreComponente = null, tituloCard
102
104
  process.exit(1);
103
105
  }
104
106
 
105
- console.log(`Clonando carpeta...`);
106
-
107
- function copiarYReemplazar(fuente, destino) {
108
- if (!fs.existsSync(destino)) {
109
- fs.mkdirSync(destino, { recursive: true });
107
+ console.log(`Clonando carpeta de gestión...`);
108
+ copiarYReemplazar(rutaFuente, rutaDestino, nombreLower, nombreClase, tableInfo, nombreModal, nombreCarpetaModal);
109
+
110
+ // Clonar el modal si tenemos tableInfo
111
+ if (tableInfo) {
112
+ const rutaFuenteModal = path.join(__dirname, '../templates/frontend', 'src', 'app', 'Componentes', 'guardar-incapacidad');
113
+ const rutaDestinoModal = path.join(process.cwd(), 'src', 'app', 'Componentes', nombreCarpetaModal);
114
+
115
+ if (fs.existsSync(rutaFuenteModal)) {
116
+ console.log(`Clonando carpeta del modal...`);
117
+ copiarYReemplazarModal(rutaFuenteModal, rutaDestinoModal, nombreLower, nombreModal, tableInfo);
110
118
  }
111
- const entradas = fs.readdirSync(fuente, { withFileTypes: true });
112
- entradas.forEach((entrada) => {
113
- const nombreOriginal = entrada.name;
114
- const nombreNuevo = nombreOriginal.replace(/XYZ/g, nombreLower);
115
- const rutaFuente = path.join(fuente, nombreOriginal);
116
- const rutaDestino = path.join(destino, nombreNuevo);
117
-
118
- if (entrada.isDirectory()) {
119
- copiarYReemplazar(rutaFuente, rutaDestino);
120
- } else if (entrada.isFile()) {
121
- let contenido = fs.readFileSync(rutaFuente, 'utf-8');
122
-
123
- // Reemplazos base
124
- contenido = contenido.replace(/GestionTablaXYZComponent/g, nombreClase);
125
- contenido = contenido.replace(/XYZ/g, nombreLower);
126
-
127
- // Si tenemos información de la tabla, personalizamos el componente
128
- if (tableInfo && entrada.name.endsWith('.ts')) {
129
- contenido = personalizarComponenteFrontend(contenido, tableInfo, nombreLower);
130
- }
131
-
132
- fs.writeFileSync(rutaDestino, contenido);
133
- }
134
- });
135
119
  }
136
120
 
137
- copiarYReemplazar(rutaFuente, rutaDestino);
138
121
  console.log(`Componente creado en: ${rutaDestino}`);
139
122
 
140
123
  // --- Modificar app.routes.ts ---
141
- console.log('Actualizando app.routes.ts...');
142
- const rutaRoutes = path.join(process.cwd(), 'src', 'app', 'app.routes.ts');
143
- if (fs.existsSync(rutaRoutes)) {
144
- let contenidoRoutes = fs.readFileSync(rutaRoutes, 'utf-8');
145
-
146
- const lineaImport = `import { ${nombreClase} } from './Paginas/${nombreRuta}/${nombreRuta}.component';`;
147
- const ultimoImportIndex = contenidoRoutes.lastIndexOf('import {');
148
- const finUltimoImport = contenidoRoutes.indexOf(';', ultimoImportIndex);
149
-
150
- if (finUltimoImport !== -1) {
151
- contenidoRoutes = contenidoRoutes.slice(0, finUltimoImport + 1) + '\n' + lineaImport + contenidoRoutes.slice(finUltimoImport + 1);
152
- } else {
153
- contenidoRoutes = lineaImport + '\n' + contenidoRoutes;
154
- }
124
+ // ... (rest of the code for routes and html update remains similar, but using nombreClase and nombreRuta)
125
+ actualizarArchivosConfiguracion(nombreClase, nombreRuta, titulo, descripcion);
155
126
 
156
- const cierreArray = contenidoRoutes.lastIndexOf('];');
157
- if (cierreArray !== -1) {
158
- const nuevaRuta = ` { path: '${nombreRuta}', component: ${nombreClase} },`;
159
- const textoAntesDeCierre = contenidoRoutes.slice(0, cierreArray).trimEnd();
160
- const necesitaComa = !textoAntesDeCierre.endsWith(',') && !textoAntesDeCierre.endsWith('[');
161
-
162
- contenidoRoutes = contenidoRoutes.slice(0, cierreArray) +
163
- (necesitaComa ? ',' : '') + '\n' +
164
- nuevaRuta + '\n' +
165
- contenidoRoutes.slice(cierreArray);
166
-
167
- fs.writeFileSync(rutaRoutes, contenidoRoutes);
168
- console.log('Ruta agregada exitosamente.');
169
- } else {
170
- console.error('No se pudo encontrar el array de rutas en app.routes.ts');
171
- }
172
- } else {
173
- console.error(`No se encontró el archivo de rutas en: ${rutaRoutes}`);
127
+ if (opciones.cerrarAlFinalizar) {
128
+ closeReadLine();
174
129
  }
130
+ }
175
131
 
176
- // --- Modificar contenedor-principal.component.html ---
177
- console.log('Actualizando contenedor-principal.component.html...');
178
- const rutaHtml = path.join(process.cwd(), 'src', 'app', 'Paginas', 'contenedor-principal', 'contenedor-principal.component.html');
179
- if (fs.existsSync(rutaHtml)) {
180
- let contenidoHtml = fs.readFileSync(rutaHtml, 'utf-8');
181
-
182
- const nuevaTarjeta = ` <app-tarjeta [rutaASeguir]="'${nombreRuta}'" titulo="${titulo}" descripcion="${descripcion}" icono="table_chart"></app-tarjeta>`;
183
-
184
- const ultimoDiv = contenidoHtml.lastIndexOf('</div>');
132
+ function copiarYReemplazar(fuente, destino, nombreLower, nombreClase, tableInfo, nombreModal, nombreCarpetaModal) {
133
+ if (!fs.existsSync(destino)) {
134
+ fs.mkdirSync(destino, { recursive: true });
135
+ }
136
+ const entradas = fs.readdirSync(fuente, { withFileTypes: true });
137
+ entradas.forEach((entrada) => {
138
+ const nombreOriginal = entrada.name;
139
+ const nombreNuevo = nombreOriginal.replace(/XYZ/g, nombreLower);
140
+ const rutaFuente = path.join(fuente, nombreOriginal);
141
+ const rutaDestino = path.join(destino, nombreNuevo);
142
+
143
+ if (entrada.isDirectory()) {
144
+ copiarYReemplazar(rutaFuente, rutaDestino, nombreLower, nombreClase, tableInfo, nombreModal, nombreCarpetaModal);
145
+ } else if (entrada.isFile()) {
146
+ let contenido = fs.readFileSync(rutaFuente, 'utf-8');
147
+
148
+ // Reemplazos base
149
+ contenido = contenido.replace(/GestionTablaXYZComponent/g, nombreClase);
150
+ contenido = contenido.replace(/XYZ/g, nombreLower);
151
+
152
+ // Si tenemos información de la tabla, personalizamos el componente
153
+ if (tableInfo) {
154
+ if (entrada.name.endsWith('.ts')) {
155
+ contenido = personalizarComponenteFrontend(contenido, tableInfo, nombreLower, nombreModal, nombreCarpetaModal);
156
+ } else if (entrada.name.endsWith('.html')) {
157
+ contenido = contenido.replace(/app-gestion-tabla-XYZ/g, `app-gestion-tabla-${nombreLower}`);
158
+ }
159
+ }
185
160
 
186
- if (ultimoDiv !== -1) {
187
- contenidoHtml = contenidoHtml.slice(0, ultimoDiv) + '\n' + nuevaTarjeta + '\n' + contenidoHtml.slice(ultimoDiv);
188
- fs.writeFileSync(rutaHtml, contenidoHtml);
189
- console.log('Tarjeta agregada exitosamente.');
190
- } else {
191
- console.error('No se encontró un </div> de cierre en el HTML del contenedor principal.');
161
+ fs.writeFileSync(rutaDestino, contenido);
192
162
  }
193
- } else {
194
- console.error(`No se encontró el archivo HTML en: ${rutaHtml}`);
195
- }
196
- if (opciones.cerrarAlFinalizar) {
197
- closeReadLine();
163
+ });
164
+ }
165
+
166
+ function copiarYReemplazarModal(fuente, destino, nombreLower, nombreModal, tableInfo) {
167
+ if (!fs.existsSync(destino)) {
168
+ fs.mkdirSync(destino, { recursive: true });
198
169
  }
170
+ const entradas = fs.readdirSync(fuente, { withFileTypes: true });
171
+ entradas.forEach((entrada) => {
172
+ const nombreOriginal = entrada.name;
173
+ const nombreNuevo = nombreOriginal.replace(/guardar-incapacidad/g, `guardar-${nombreLower}`);
174
+ const rutaFuente = path.join(fuente, nombreOriginal);
175
+ const rutaDestino = path.join(destino, nombreNuevo);
176
+
177
+ if (entrada.isDirectory()) {
178
+ copiarYReemplazarModal(rutaFuente, rutaDestino, nombreLower, nombreModal, tableInfo);
179
+ } else if (entrada.isFile()) {
180
+ let contenido = fs.readFileSync(rutaFuente, 'utf-8');
181
+
182
+ // Reemplazos base
183
+ contenido = contenido.replace(/GuardarIncapacidadComponent/g, nombreModal);
184
+ contenido = contenido.replace(/guardar-incapacidad/g, `guardar-${nombreLower}`);
185
+ contenido = contenido.replace(/Incapacidad/g, nombreLower.charAt(0).toUpperCase() + nombreLower.slice(1));
186
+ contenido = contenido.replace(/incapacidad/g, nombreLower);
187
+
188
+ // Personalización basada en tableInfo
189
+ contenido = personalizarModalFrontend(contenido, tableInfo, entrada.name);
190
+
191
+ fs.writeFileSync(rutaDestino, contenido);
192
+ }
193
+ });
199
194
  }
200
195
 
201
- function personalizarComponenteFrontend(contenido, tableInfo, nombreServicioLower) {
196
+ function personalizarComponenteFrontend(contenido, tableInfo, nombreServicioLower, nombreModal, nombreCarpetaModal) {
202
197
  const { columns, primaryKey } = tableInfo;
203
198
 
204
199
  // 1. Reemplazar columnas
@@ -216,9 +211,123 @@ function personalizarComponenteFrontend(contenido, tableInfo, nombreServicioLowe
216
211
  const endpointRegex = new RegExp('licencias/', 'g');
217
212
  contenido = contenido.replace(endpointRegex, `${nombreServicioLower}/`);
218
213
 
214
+ // 4. Reemplazar el Import y uso del modal
215
+ contenido = contenido.replace(/import \{ GuardarIncapacidadComponent \} from '\.\.\/\.\.\/Componentes\/guardar-incapacidad\/guardar-incapacidad\.component';/g,
216
+ `import { ${nombreModal} } from '../../Componentes/${nombreCarpetaModal}/${nombreCarpetaModal}.component';`);
217
+ contenido = contenido.replace(/GuardarIncapacidadComponent/g, nombreModal);
218
+
219
+ // Usar 'Registro' como clave en el modal
220
+ contenido = contenido.replace(/Incapacidad: incapacidad/g, 'Registro: incapacidad');
221
+
222
+ // 5. Limpiar cosas específicas de Incapacidades si existen
223
+ contenido = contenido.replace(/this\.obtenerTiposLicencias\(\);/g, '');
224
+ contenido = contenido.replace(/obtenerTiposLicencias\(\) \{[\s\S]*?\}/g, '');
225
+ contenido = contenido.replace(/public tiposDeLicencias: any\[\] = \[\];/g, '');
226
+ contenido = contenido.replace(/TiposDeLicencias: this\.tiposDeLicencias/g, '');
227
+
219
228
  return contenido;
220
229
  }
221
230
 
231
+ function personalizarModalFrontend(contenido, tableInfo, fileName) {
232
+ const { columns, primaryKey } = tableInfo;
233
+
234
+ if (fileName.endsWith('.ts')) {
235
+ // Generar controles del formulario
236
+ const controles = columns.map(col => {
237
+ return ` ${col.name}Control: [this.data.Registro?.${col.name}, Validators.required]`;
238
+ }).join(',\n');
239
+
240
+ contenido = contenido.replace(/grupoFormulario = this\._formBuilder\.group\(\{[\s\S]*?\}\);/,
241
+ `grupoFormulario = this._formBuilder.group({\n${controles}\n });`);
242
+
243
+ // Generar objeto de retorno en Guardar()
244
+ const objetoRetorno = columns.map(col => {
245
+ return ` ${col.name}: controles.${col.name}Control.value`;
246
+ }).join(',\n');
247
+
248
+ contenido = contenido.replace(/let incapacidad = this\.grupoFormulario\.controls;[\s\S]*?this\.dialogRef\.close\(\{[\s\S]*?\}\);/,
249
+ `let controles = this.grupoFormulario.controls;\n this.dialogRef.close({\n${objetoRetorno},\n ${primaryKey}: this.data.Registro?.${primaryKey}\n });`);
250
+
251
+ // Quitar TiposDeLicencias si existe
252
+ contenido = contenido.replace(/TiposDeLicencias = this\.data\.TiposDeLicencias;/g, '');
253
+ contenido = contenido.replace(/readonly Incapacidad = this\.data\.Incapacidad \?\? \{\};/g, '');
254
+ }
255
+
256
+ if (fileName.endsWith('.html')) {
257
+ // Generar campos del formulario
258
+ const campos = columns.map(col => {
259
+ const type = col.type.includes('INT') ? 'number' : (col.type.includes('DATE') ? 'date' : 'text');
260
+ return ` <mat-form-field class="campo_mitad">
261
+ <mat-label>${col.alias}</mat-label>
262
+ <input matInput ${type !== 'text' ? 'type="' + type + '"' : ''} formControlName="${col.name}Control" required />
263
+ </mat-form-field>`;
264
+ }).join('\n');
265
+
266
+ contenido = contenido.replace(/<form \[formGroup\]="grupoFormulario">[\s\S]*?<\/form>/,
267
+ `<form [formGroup]="grupoFormulario">\n <div class="fila">\n${campos}\n </div>\n </form>`);
268
+
269
+ // Reemplazar el título
270
+ const titulo = tableInfo.serviceTitle || 'Registro';
271
+ contenido = contenido.replace(/<h2>[\s\S]*?<\/h2>/, `<h2>{{data.Registro ? 'Editar' : 'Agregar'}} ${titulo}</h2>`);
272
+ contenido = contenido.replace(/{{Incapacidad\.NumeroBoleta\?'Guardar':'Agregar'}}/, `{{data.Registro ? 'Guardar' : 'Agregar'}}`);
273
+ }
274
+
275
+ return contenido;
276
+ }
277
+
278
+ function actualizarArchivosConfiguracion(nombreClase, nombreRuta, titulo, descripcion) {
279
+ // --- Modificar app.routes.ts ---
280
+ console.log('Actualizando app.routes.ts...');
281
+ const rutaRoutes = path.join(process.cwd(), 'src', 'app', 'app.routes.ts');
282
+ if (fs.existsSync(rutaRoutes)) {
283
+ let contenidoRoutes = fs.readFileSync(rutaRoutes, 'utf-8');
284
+
285
+ const lineaImport = `import { ${nombreClase} } from './Paginas/${nombreRuta}/${nombreRuta}.component';`;
286
+ if (!contenidoRoutes.includes(lineaImport)) {
287
+ const ultimoImportIndex = contenidoRoutes.lastIndexOf('import {');
288
+ const finUltimoImport = contenidoRoutes.indexOf(';', ultimoImportIndex);
289
+
290
+ if (finUltimoImport !== -1) {
291
+ contenidoRoutes = contenidoRoutes.slice(0, finUltimoImport + 1) + '\n' + lineaImport + contenidoRoutes.slice(finUltimoImport + 1);
292
+ } else {
293
+ contenidoRoutes = lineaImport + '\n' + contenidoRoutes;
294
+ }
295
+ }
296
+
297
+ if (!contenidoRoutes.includes(`path: '${nombreRuta}'`)) {
298
+ const cierreArray = contenidoRoutes.lastIndexOf('];');
299
+ if (cierreArray !== -1) {
300
+ const nuevaRuta = ` { path: '${nombreRuta}', component: ${nombreClase} },`;
301
+ const textoAntesDeCierre = contenidoRoutes.slice(0, cierreArray).trimEnd();
302
+ const necesitaComa = !textoAntesDeCierre.endsWith(',') && !textoAntesDeCierre.endsWith('[');
303
+
304
+ contenidoRoutes = contenidoRoutes.slice(0, cierreArray) +
305
+ (necesitaComa ? ',' : '') + '\n' +
306
+ nuevaRuta + '\n' +
307
+ contenidoRoutes.slice(cierreArray);
308
+
309
+ fs.writeFileSync(rutaRoutes, contenidoRoutes);
310
+ }
311
+ }
312
+ }
313
+
314
+ // --- Modificar contenedor-principal.component.html ---
315
+ console.log('Actualizando contenedor-principal.component.html...');
316
+ const rutaHtml = path.join(process.cwd(), 'src', 'app', 'Paginas', 'contenedor-principal', 'contenedor-principal.component.html');
317
+ if (fs.existsSync(rutaHtml)) {
318
+ let contenidoHtml = fs.readFileSync(rutaHtml, 'utf-8');
319
+ if (!contenidoHtml.includes(`rutaASeguir]="'${nombreRuta}'"`)) {
320
+ const nuevaTarjeta = ` <app-tarjeta [rutaASeguir]="'${nombreRuta}'" titulo="${titulo}" descripcion="${descripcion}" icono="table_chart"></app-tarjeta>`;
321
+ const ultimoDiv = contenidoHtml.lastIndexOf('</div>');
322
+ if (ultimoDiv !== -1) {
323
+ contenidoHtml = contenidoHtml.slice(0, ultimoDiv) + '\n' + nuevaTarjeta + '\n' + contenidoHtml.slice(ultimoDiv);
324
+ fs.writeFileSync(rutaHtml, contenidoHtml);
325
+ }
326
+ }
327
+ }
328
+ }
329
+
330
+
222
331
  export function showFrontendVersion() {
223
332
  mostrarVersion(path.join(__dirname, '../package.json'));
224
333
  closeReadLine();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utn-cli",
3
- "version": "2.0.57",
3
+ "version": "2.0.59",
4
4
  "description": "Herramienta CLI unificada para la gestión de plantillas en SIGU.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -353,6 +353,23 @@ Router.get('/cerrarSesion', async (solicitud, respuesta, next) => {
353
353
  }
354
354
  });
355
355
 
356
+ Router.delete('/cerrarSesionPorToken/:Token', async (solicitud, respuesta, next) => {
357
+ try {
358
+ if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
359
+ try {
360
+ return respuesta.json({ body: await Miscelaneo.cerrarSesionPorToken(solicitud.params.Token), error: undefined });
361
+ } catch (error) {
362
+ const MensajeDeError = 'No fue posible cerrar la sesión';
363
+ console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, `Dirección IP: ${solicitud.ip}`));
364
+ return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
365
+ }
366
+ }
367
+ return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
368
+ } catch (error) {
369
+ next(error);
370
+ }
371
+ });
372
+
356
373
  Router.get('/obtenerEnlaceDePerfil', async (solicitud, respuesta, next) => {
357
374
  try {
358
375
  if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
@@ -66,6 +66,7 @@ class Miscelaneo {
66
66
  JSON_VALUE(Solicitud, '$.method') AS Metodo,
67
67
  JSON_VALUE(Solicitud, '$.originalUrl') AS URL,
68
68
  IFNULL(JSON_VALUE(Solicitud, '$.country'), 'Costa Rica') AS Pais,
69
+ SUBSTRING_INDEX(JSON_VALUE(Solicitud, '$.headers.authorization'), ' ', -1) AS Token,
69
70
  CASE
70
71
  WHEN JSON_VALUE(Solicitud, '$.userAgent') LIKE '%Mobile%' THEN 'Móvil'
71
72
  WHEN JSON_VALUE(Solicitud, '$.userAgent') LIKE '%Android%' AND JSON_VALUE(Solicitud, '$.userAgent') NOT LIKE '%Mobile%' THEN 'Tablet'
@@ -85,6 +86,11 @@ class Miscelaneo {
85
86
  }
86
87
  }
87
88
 
89
+ async cerrarSesionPorToken(Token) {
90
+ await ejecutarConsultaSIGU("DELETE FROM `SIGU`.`SIGU_Sesiones` WHERE `Token` = ?", [Token]);
91
+ return true;
92
+ }
93
+
88
94
  async UsuariosActuales() {
89
95
  const ConexionSigu = await crearObjetoConexionSIGU();
90
96
  const Actuales = await ConexionSigu.query("SELECT COUNT(DISTINCT `Identificador`) AS `Total` FROM `SIGU`.`SIGU_Sesiones` WHERE `LastUpdate` >= NOW() - INTERVAL 2 HOUR");
@@ -10,7 +10,7 @@
10
10
  border-radius: 12px;
11
11
  margin-bottom: 20px;
12
12
  border: 1px solid #e0e6ed;
13
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
13
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
14
14
  text-align: left;
15
15
  }
16
16
 
@@ -80,7 +80,7 @@
80
80
  border: 1px solid #e0e6ed;
81
81
  border-radius: 12px;
82
82
  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
83
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
83
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
84
84
  text-align: left;
85
85
  }
86
86
 
@@ -110,17 +110,37 @@
110
110
  }
111
111
 
112
112
  /* Colores por método */
113
- .metodo-get { color: #0069B4; background-color: #e3f2fd; }
114
- .metodo-post { color: #518a5f; background-color: #e8f5e9; }
115
- .metodo-put { color: #FFA757; background-color: #fff3e0; }
116
- .metodo-delete { color: #F82617; background-color: #ffebee; }
117
- .metodo-default { color: #7d7d7d; background-color: #f5f5f5; }
113
+ .metodo-get {
114
+ color: #0069B4;
115
+ background-color: #e3f2fd;
116
+ }
117
+
118
+ .metodo-post {
119
+ color: #518a5f;
120
+ background-color: #e8f5e9;
121
+ }
122
+
123
+ .metodo-put {
124
+ color: #FFA757;
125
+ background-color: #fff3e0;
126
+ }
127
+
128
+ .metodo-delete {
129
+ color: #F82617;
130
+ background-color: #ffebee;
131
+ }
132
+
133
+ .metodo-default {
134
+ color: #7d7d7d;
135
+ background-color: #f5f5f5;
136
+ }
118
137
 
119
138
  .item-contenido {
120
139
  flex: 1;
121
140
  display: flex;
122
141
  flex-direction: column;
123
- min-width: 0; /* Evita que el flex desborde */
142
+ min-width: 0;
143
+ /* Evita que el flex desborde */
124
144
  }
125
145
 
126
146
  .item-titulo {
@@ -129,6 +149,22 @@
129
149
  font-weight: 600;
130
150
  word-break: break-all;
131
151
  margin-bottom: 2px;
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 8px;
155
+ flex-wrap: wrap;
156
+ }
157
+
158
+ .badge-sesion-actual {
159
+ background-color: #e8f5e9;
160
+ color: #2e7d32;
161
+ font-size: 10px;
162
+ padding: 2px 8px;
163
+ border-radius: 12px;
164
+ border: 1px solid #c8e6c9;
165
+ text-transform: uppercase;
166
+ font-weight: 700;
167
+ white-space: nowrap;
132
168
  }
133
169
 
134
170
  .item-detalles {
@@ -152,10 +188,21 @@
152
188
  height: 14px;
153
189
  }
154
190
 
155
- .detalle-dispositivo mat-icon { color: #673ab7; }
156
- .detalle-browser mat-icon { color: #5f6368; }
157
- .detalle-ip mat-icon { color: #0069B4; }
158
- .detalle-pais mat-icon { color: #F87600; }
191
+ .detalle-dispositivo mat-icon {
192
+ color: #673ab7;
193
+ }
194
+
195
+ .detalle-browser mat-icon {
196
+ color: #5f6368;
197
+ }
198
+
199
+ .detalle-ip mat-icon {
200
+ color: #0069B4;
201
+ }
202
+
203
+ .detalle-pais mat-icon {
204
+ color: #F87600;
205
+ }
159
206
 
160
207
  .item-fecha {
161
208
  font-size: 11px;
@@ -180,14 +227,14 @@ mat-progress-bar {
180
227
  flex-direction: column;
181
228
  align-items: flex-start;
182
229
  }
183
-
230
+
184
231
  .item-icono {
185
232
  margin-bottom: 12px;
186
233
  }
187
-
234
+
188
235
  .item-fecha {
189
236
  margin-left: 0;
190
237
  margin-top: 12px;
191
238
  align-items: flex-start;
192
239
  }
193
- }
240
+ }
@@ -2,49 +2,59 @@
2
2
  <div class="header-actividad">
3
3
  <h2><mat-icon>history</mat-icon> Actividad reciente de la cuenta</h2>
4
4
  <p>Historial de acciones realizadas en el sistema</p>
5
-
5
+
6
6
  <div class="mensaje-informativo">
7
7
  <mat-icon>info</mat-icon>
8
- <p>Si tiene dudas sobre la información que ve en pantalla, por favor comparta una captura de pantalla al correo <strong>soporte@utn.ac.cr</strong>.</p>
8
+ <p>Si tiene dudas sobre la información que ve en pantalla, por favor comparta una captura de pantalla al correo
9
+ <strong>soporte@utn.ac.cr</strong>.</p>
9
10
  </div>
10
11
  </div>
11
12
 
12
13
  @if(cargando){
13
- <mat-progress-bar mode="indeterminate"></mat-progress-bar>
14
+ <mat-progress-bar mode="indeterminate"></mat-progress-bar>
14
15
  }
15
-
16
+
16
17
  <div class="lista-actividad">
17
18
  @if(actividad.length === 0 && !cargando) {
18
- <p class="sin-datos">No hay actividad reciente.</p>
19
+ <p class="sin-datos">No hay actividad reciente.</p>
19
20
  }
20
21
 
21
22
  @for (item of actividad; track item.Consecutivo) {
22
- <div class="actividad-item">
23
- <div class="item-icono" [ngClass]="{
23
+ <div class="actividad-item">
24
+ <div class="item-icono" [ngClass]="{
24
25
  'metodo-get': item.Metodo === 'GET',
25
26
  'metodo-post': item.Metodo === 'POST',
26
27
  'metodo-put': item.Metodo === 'PUT',
27
28
  'metodo-delete': item.Metodo === 'DELETE'
28
29
  }">
29
- <mat-icon [matTooltip]="item.Metodo">{{ getIcon(item.Metodo) }}</mat-icon>
30
- </div>
31
- <div class="item-contenido">
32
- <div class="item-titulo">{{ item.URL }}</div>
33
- <div class="item-detalles">
34
- <span class="detalle-dispositivo">
35
- <mat-icon>{{ item.Dispositivo === 'Móvil' ? 'smartphone' : (item.Dispositivo === 'Tablet' ? 'tablet' : 'laptop') }}</mat-icon>
36
- {{ item.Dispositivo }}
37
- </span>
38
- <span class="detalle-browser"><mat-icon>public</mat-icon> {{ item.Navegador }}</span>
39
- <span class="detalle-ip"><mat-icon>lan</mat-icon> {{ item.IP }}</span>
40
- <span class="detalle-pais"><mat-icon>location_on</mat-icon> {{ item.Pais }}</span>
41
- </div>
30
+ <mat-icon [matTooltip]="item.Metodo">{{ getIcon(item.Metodo) }}</mat-icon>
31
+ </div>
32
+ <div class="item-contenido">
33
+ <div class="item-titulo">
34
+ {{ item.URL }}
35
+ @if(esSesionActual(item.Token)){
36
+ <span class="badge-sesion-actual">Sesión actual</span>
37
+ }
42
38
  </div>
43
- <div class="item-fecha">
44
- <span>{{ item.Fecha | date: 'dd/MM/yyyy' }}</span>
45
- <strong>{{ item.Fecha | date: 'HH:mm:ss' }}</strong>
39
+ <div class="item-detalles">
40
+ <span class="detalle-dispositivo">
41
+ <mat-icon>{{ item.Dispositivo === 'Móvil' ? 'smartphone' : (item.Dispositivo === 'Tablet' ? 'tablet' :
42
+ 'laptop') }}</mat-icon>
43
+ {{ item.Dispositivo }}
44
+ </span>
45
+ <span class="detalle-browser"><mat-icon>public</mat-icon> {{ item.Navegador }}</span>
46
+ <span class="detalle-ip"><mat-icon>lan</mat-icon> {{ item.IP }}</span>
47
+ <span class="detalle-pais"><mat-icon>location_on</mat-icon> {{ item.Pais }}</span>
46
48
  </div>
47
49
  </div>
50
+ <div class="item-fecha">
51
+ <span>{{ item.Fecha | date: 'dd/MM/yyyy' }}</span>
52
+ <strong>{{ item.Fecha | date: 'HH:mm:ss' }}</strong>
53
+ <button mat-icon-button color="warn" matTooltip="Cerrar esta sesión" (click)="cerrarSesion(item.Token)">
54
+ <mat-icon>logout</mat-icon>
55
+ </button>
56
+ </div>
57
+ </div>
48
58
  }
49
59
  </div>
50
- </div>
60
+ </div>
@@ -4,26 +4,30 @@ import { HttpClient } from '@angular/common/http';
4
4
  import { MatIconModule } from '@angular/material/icon';
5
5
  import { MatTooltipModule } from '@angular/material/tooltip';
6
6
  import { MatProgressBarModule } from '@angular/material/progress-bar';
7
+ import { MatButtonModule } from '@angular/material/button';
7
8
  import { DatosGlobalesService } from '../../../datos-globales.service';
8
9
 
9
10
  @Component({
10
11
  selector: 'app-gestion-actividad',
11
12
  standalone: true,
12
- imports: [CommonModule, MatIconModule, MatTooltipModule, MatProgressBarModule],
13
+ imports: [CommonModule, MatIconModule, MatTooltipModule, MatProgressBarModule, MatButtonModule],
13
14
  templateUrl: './gestion-actividad.component.html',
14
15
  styleUrls: ['./gestion-actividad.component.css']
15
16
  })
16
17
  export class GestionActividadComponent implements OnInit {
17
18
  public actividad: any[] = [];
18
19
  public cargando = false;
20
+ private tokenActual = '';
19
21
 
20
22
  constructor(private http: HttpClient, private datosGlobalesService: DatosGlobalesService) { }
21
23
 
22
24
  ngOnInit(): void {
25
+ this.tokenActual = this.datosGlobalesService.ObtenerToken().split(' ')[1] || '';
23
26
  this.obtenerActividad();
24
27
  }
25
28
 
26
29
  obtenerActividad(): void {
30
+ this.cargando = true;
27
31
  this.http.get(`${this.datosGlobalesService.ObtenerURL()}misc/ListarActividades`).subscribe({
28
32
  next: (datos: any) => {
29
33
  this.actividad = datos.body;
@@ -36,17 +40,27 @@ export class GestionActividadComponent implements OnInit {
36
40
  this.cargando = false;
37
41
  }
38
42
  });
39
- // this.cargando = true;
40
- // this.http.get<any[]>(`${this.datosGlobalesService.ObtenerURL()}Actividad`).subscribe({
41
- // next: (datos) => {
42
- // this.actividad = datos;
43
- // this.cargando = false;
44
- // },
45
- // error: (error) => {
46
- // console.error('Error al obtener actividad:', error);
47
- // this.cargando = false;
48
- // }
49
- // });
43
+ }
44
+
45
+ cerrarSesion(token: string): void {
46
+ if (confirm('¿Está seguro de que desea cerrar esta sesión?')) {
47
+ this.http.delete(`${this.datosGlobalesService.ObtenerURL()}misc/cerrarSesionPorToken/${token}`).subscribe({
48
+ next: () => {
49
+ if (token === this.tokenActual) {
50
+ this.datosGlobalesService.RedirigirALogin();
51
+ } else {
52
+ this.obtenerActividad();
53
+ }
54
+ },
55
+ error: (error) => {
56
+ console.error('Error al cerrar sesión:', error);
57
+ }
58
+ });
59
+ }
60
+ }
61
+
62
+ esSesionActual(token: string): boolean {
63
+ return token === this.tokenActual;
50
64
  }
51
65
 
52
66
  getIcon(metodo: string): string {
@@ -1,5 +1,240 @@
1
- .mensaje {
2
- margin-top: 1%;
1
+ .contenedor-actividad {
2
+ background-color: transparent;
3
+ margin: 0;
4
+ padding: 10px;
5
+ }
6
+
7
+ .header-actividad {
8
+ background-color: white;
9
+ padding: 20px 24px;
10
+ border-radius: 12px;
11
+ margin-bottom: 20px;
12
+ border: 1px solid #e0e6ed;
13
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
14
+ text-align: left;
15
+ }
16
+
17
+ .header-actividad h2 {
18
+ margin: 0;
19
+ color: #002f6b;
20
+ font-size: 1.5rem;
21
+ font-weight: 700;
3
22
  display: flex;
23
+ align-items: center;
24
+ gap: 12px;
25
+ }
26
+
27
+ .header-actividad h2 mat-icon {
28
+ font-size: 28px;
29
+ width: 28px;
30
+ height: 28px;
31
+ color: #0b4fce;
32
+ }
33
+
34
+ .header-actividad p {
35
+ margin: 8px 0 0 0;
36
+ color: #5f6368;
37
+ font-size: 0.95rem;
38
+ }
39
+
40
+ .mensaje-informativo {
41
+ display: flex;
42
+ align-items: center;
43
+ gap: 12px;
44
+ background-color: #e3f2fd;
45
+ border-left: 4px solid #1976d2;
46
+ padding: 12px 16px;
47
+ margin-top: 20px;
48
+ border-radius: 4px;
49
+ }
50
+
51
+ .mensaje-informativo mat-icon {
52
+ color: #1976d2;
53
+ font-size: 24px;
54
+ width: 24px;
55
+ height: 24px;
56
+ }
57
+
58
+ .mensaje-informativo p {
59
+ margin: 0 !important;
60
+ font-size: 13.5px !important;
61
+ color: #0d47a1 !important;
62
+ line-height: 1.4;
63
+ }
64
+
65
+ .mensaje-informativo strong {
66
+ color: #d32f2f;
67
+ }
68
+
69
+ .lista-actividad {
70
+ display: flex;
71
+ flex-direction: column;
72
+ gap: 12px;
73
+ }
74
+
75
+ .actividad-item {
76
+ display: flex;
77
+ align-items: center;
78
+ padding: 16px;
79
+ background-color: white;
80
+ border: 1px solid #e0e6ed;
81
+ border-radius: 12px;
82
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
83
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
84
+ text-align: left;
85
+ }
86
+
87
+ .actividad-item:hover {
88
+ transform: translateY(-2px);
89
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
90
+ border-color: #0b4fce;
91
+ background-color: #f0f7ff;
92
+ }
93
+
94
+ .item-icono {
95
+ margin-right: 12px;
96
+ display: flex;
97
+ align-items: center;
4
98
  justify-content: center;
99
+ width: 40px;
100
+ height: 40px;
101
+ border-radius: 50%;
102
+ background-color: #f1f3f4;
103
+ flex-shrink: 0;
104
+ }
105
+
106
+ .item-icono mat-icon {
107
+ font-size: 20px;
108
+ width: 20px;
109
+ height: 20px;
110
+ }
111
+
112
+ /* Colores por método */
113
+ .metodo-get {
114
+ color: #0069B4;
115
+ background-color: #e3f2fd;
116
+ }
117
+
118
+ .metodo-post {
119
+ color: #518a5f;
120
+ background-color: #e8f5e9;
121
+ }
122
+
123
+ .metodo-put {
124
+ color: #FFA757;
125
+ background-color: #fff3e0;
126
+ }
127
+
128
+ .metodo-delete {
129
+ color: #F82617;
130
+ background-color: #ffebee;
131
+ }
132
+
133
+ .metodo-default {
134
+ color: #7d7d7d;
135
+ background-color: #f5f5f5;
136
+ }
137
+
138
+ .item-contenido {
139
+ flex: 1;
140
+ display: flex;
141
+ flex-direction: column;
142
+ min-width: 0;
143
+ /* Evita que el flex desborde */
144
+ }
145
+
146
+ .item-titulo {
147
+ font-size: 14px;
148
+ color: #002f6b;
149
+ font-weight: 600;
150
+ word-break: break-all;
151
+ margin-bottom: 2px;
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 8px;
155
+ flex-wrap: wrap;
156
+ }
157
+
158
+ .badge-sesion-actual {
159
+ background-color: #e8f5e9;
160
+ color: #2e7d32;
161
+ font-size: 10px;
162
+ padding: 2px 8px;
163
+ border-radius: 12px;
164
+ border: 1px solid #c8e6c9;
165
+ text-transform: uppercase;
166
+ font-weight: 700;
167
+ white-space: nowrap;
168
+ }
169
+
170
+ .item-detalles {
171
+ display: flex;
172
+ flex-wrap: wrap;
173
+ gap: 8px;
174
+ font-size: 12px;
175
+ color: #5f6368;
176
+ }
177
+
178
+ .item-detalles span {
179
+ display: flex;
180
+ align-items: center;
181
+ gap: 3px;
182
+ white-space: nowrap;
183
+ }
184
+
185
+ .item-detalles mat-icon {
186
+ font-size: 14px;
187
+ width: 14px;
188
+ height: 14px;
189
+ }
190
+
191
+ .detalle-dispositivo mat-icon {
192
+ color: #673ab7;
193
+ }
194
+
195
+ .detalle-browser mat-icon {
196
+ color: #5f6368;
197
+ }
198
+
199
+ .detalle-ip mat-icon {
200
+ color: #0069B4;
201
+ }
202
+
203
+ .detalle-pais mat-icon {
204
+ color: #F87600;
205
+ }
206
+
207
+ .item-fecha {
208
+ font-size: 11px;
209
+ color: #9aa0a6;
210
+ white-space: nowrap;
211
+ margin-left: 12px;
212
+ display: flex;
213
+ flex-direction: column;
214
+ align-items: flex-end;
215
+ font-family: 'Roboto Mono', monospace;
216
+ flex-shrink: 0;
217
+ }
218
+
219
+ mat-progress-bar {
220
+ height: 4px;
221
+ border-radius: 2px;
222
+ margin-bottom: 10px;
223
+ }
224
+
225
+ @media (max-width: 600px) {
226
+ .actividad-item {
227
+ flex-direction: column;
228
+ align-items: flex-start;
229
+ }
230
+
231
+ .item-icono {
232
+ margin-bottom: 12px;
233
+ }
234
+
235
+ .item-fecha {
236
+ margin-left: 0;
237
+ margin-top: 12px;
238
+ align-items: flex-start;
239
+ }
5
240
  }
@@ -1,22 +1,60 @@
1
- <h3 mat-dialog-title>{{ data.titulo }}</h3>
1
+ <div class="contenedor-actividad">
2
+ <div class="header-actividad">
3
+ <h2><mat-icon>history</mat-icon> Actividad reciente de la cuenta</h2>
4
+ <p>Historial de acciones realizadas en el sistema</p>
2
5
 
3
- <mat-dialog-content>
4
- <p>{{ data.mensaje }}</p>
5
- @if(SolicitaMensaje){
6
- <mat-form-field class="mensaje">
7
- <mat-label>Agregar comentario</mat-label>
8
- <textarea matInput placeholder="Explicación del motivo de aprobación o rechazo" [(ngModel)]="MensajeBrindado"></textarea>
9
- </mat-form-field>
10
- }
11
-
12
- </mat-dialog-content>
6
+ <div class="mensaje-informativo">
7
+ <mat-icon>info</mat-icon>
8
+ <p>Si tiene dudas sobre la información que ve en pantalla, por favor comparta una captura de pantalla al correo
9
+ <strong>soporte@utn.ac.cr</strong>.</p>
10
+ </div>
11
+ </div>
13
12
 
14
- <mat-dialog-actions>
15
- @if(data.textoCerrar){
16
- <button mat-button (click)="onClose()">{{ data.textoCerrar }}</button>
13
+ @if(cargando){
14
+ <mat-progress-bar mode="indeterminate"></mat-progress-bar>
17
15
  }
18
16
 
19
- @if(data.textoAceptar){
20
- <button mat-button (click)="onAccept()" cdkFocusInitial>{{ data.textoAceptar }}</button>
21
- }
22
- </mat-dialog-actions>
17
+ <div class="lista-actividad">
18
+ @if(actividad.length === 0 && !cargando) {
19
+ <p class="sin-datos">No hay actividad reciente.</p>
20
+ }
21
+
22
+ @for (item of actividad; track item.Consecutivo) {
23
+ <div class="actividad-item">
24
+ <div class="item-icono" [ngClass]="{
25
+ 'metodo-get': item.Metodo === 'GET',
26
+ 'metodo-post': item.Metodo === 'POST',
27
+ 'metodo-put': item.Metodo === 'PUT',
28
+ 'metodo-delete': item.Metodo === 'DELETE'
29
+ }">
30
+ <mat-icon [matTooltip]="item.Metodo">{{ getIcon(item.Metodo) }}</mat-icon>
31
+ </div>
32
+ <div class="item-contenido">
33
+ <div class="item-titulo">
34
+ {{ item.URL }}
35
+ @if(esSesionActual(item.Token)){
36
+ <span class="badge-sesion-actual">Sesión actual</span>
37
+ }
38
+ </div>
39
+ <div class="item-detalles">
40
+ <span class="detalle-dispositivo">
41
+ <mat-icon>{{ item.Dispositivo === 'Móvil' ? 'smartphone' : (item.Dispositivo === 'Tablet' ? 'tablet' :
42
+ 'laptop') }}</mat-icon>
43
+ {{ item.Dispositivo }}
44
+ </span>
45
+ <span class="detalle-browser"><mat-icon>public</mat-icon> {{ item.Navegador }}</span>
46
+ <span class="detalle-ip"><mat-icon>lan</mat-icon> {{ item.IP }}</span>
47
+ <span class="detalle-pais"><mat-icon>location_on</mat-icon> {{ item.Pais }}</span>
48
+ </div>
49
+ </div>
50
+ <div class="item-fecha">
51
+ <span>{{ item.Fecha | date: 'dd/MM/yyyy' }}</span>
52
+ <strong>{{ item.Fecha | date: 'HH:mm:ss' }}</strong>
53
+ <button mat-icon-button color="warn" matTooltip="Cerrar esta sesión" (click)="cerrarSesion(item.Token)">
54
+ <mat-icon>logout</mat-icon>
55
+ </button>
56
+ </div>
57
+ </div>
58
+ }
59
+ </div>
60
+ </div>
@@ -1,65 +1,86 @@
1
- import { Component, Inject } from '@angular/core';
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { HttpClient } from '@angular/common/http';
4
+ import { MatIconModule } from '@angular/material/icon';
5
+ import { MatTooltipModule } from '@angular/material/tooltip';
6
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
2
7
  import { MatButtonModule } from '@angular/material/button';
3
- import {
4
- MAT_DIALOG_DATA,
5
- MatDialogActions,
6
- MatDialogContent,
7
- MatDialogRef,
8
- MatDialogTitle
9
- } from '@angular/material/dialog';
10
- import { MatFormFieldModule } from '@angular/material/form-field';
11
- import { MatInputModule } from '@angular/material/input';
12
- import { FormsModule } from '@angular/forms';
13
-
14
- export interface DialogData {
15
- titulo: string;
16
- mensaje: string;
17
- textoCerrar?: string;
18
- textoAceptar?: string;
19
- SolicitaMensaje?: string;
20
- onClose?: () => void;
21
- onAccept?: (MensajeBrindado: any) => void;
22
- }
8
+ import { MatDialog } from '@angular/material/dialog';
9
+ import { MensajeConfirmacionComponent } from '../mensaje-confirmacion/mensaje-confirmacion';
10
+ import { DatosGlobalesService } from '../../../datos-globales.service';
23
11
 
24
12
  @Component({
25
- selector: 'app-dialogo',
26
- templateUrl: './mensaje-confirmacion.component.html',
27
- styleUrls: ['./mensaje-confirmacion.component.css'],
28
- imports: [
29
- MatButtonModule,
30
- MatDialogTitle,
31
- MatDialogContent,
32
- MatDialogActions,
33
- MatFormFieldModule,
34
- MatInputModule,
35
- FormsModule
36
- ]
13
+ selector: 'app-gestion-actividad',
14
+ standalone: true,
15
+ imports: [CommonModule, MatIconModule, MatTooltipModule, MatProgressBarModule, MatButtonModule],
16
+ templateUrl: './gestion-actividad.component.html',
17
+ styleUrls: ['./gestion-actividad.component.css']
37
18
  })
38
- export class MensajeConfirmacionComponent {
19
+ export class GestionActividadComponent implements OnInit {
20
+ public actividad: any[] = [];
21
+ public cargando = false;
22
+ private tokenActual = '';
23
+
24
+ constructor(private http: HttpClient, private datosGlobalesService: DatosGlobalesService, private dialog: MatDialog) { }
39
25
 
40
- SolicitaMensaje: string | undefined;
26
+ ngOnInit(): void {
27
+ this.tokenActual = this.datosGlobalesService.ObtenerToken().split(' ')[1] || '';
28
+ this.obtenerActividad();
29
+ }
41
30
 
42
- public MensajeBrindado: string = '';
31
+ obtenerActividad(): void {
32
+ this.cargando = true;
33
+ this.http.get(`${this.datosGlobalesService.ObtenerURL()}misc/ListarActividades`).subscribe({
34
+ next: (datos: any) => {
35
+ this.actividad = datos.body;
36
+ },
37
+ error: (error) => {
38
+ console.error('Error al obtener actividad:', error);
39
+ this.cargando = false;
40
+ },
41
+ complete: () => {
42
+ this.cargando = false;
43
+ }
44
+ });
45
+ }
43
46
 
44
- constructor(
45
- public dialogRef: MatDialogRef<MensajeConfirmacionComponent>,
46
- @Inject(MAT_DIALOG_DATA) public data: DialogData
47
- ) {
48
- this.dialogRef.disableClose = true;
49
- this.SolicitaMensaje = this.data.SolicitaMensaje;
47
+ cerrarSesion(token: string): void {
48
+ this.dialog.open(MensajeConfirmacionComponent, {
49
+ data: {
50
+ titulo: 'Confirmación',
51
+ mensaje: '¿Está seguro de que desea cerrar esta sesión?',
52
+ textoCerrar: 'Cancelar',
53
+ textoAceptar: 'Cerrar sesión',
54
+ onClose: () => { },
55
+ onAccept: () => {
56
+ this.http.delete(`${this.datosGlobalesService.ObtenerURL()}misc/cerrarSesionPorToken/${token}`).subscribe({
57
+ next: () => {
58
+ if (token === this.tokenActual) {
59
+ this.datosGlobalesService.RedirigirALogin();
60
+ } else {
61
+ this.obtenerActividad();
62
+ }
63
+ },
64
+ error: (error) => {
65
+ console.error('Error al cerrar sesión:', error);
66
+ }
67
+ });
68
+ },
69
+ },
70
+ });
50
71
  }
51
72
 
52
- onClose(): void {
53
- if (this.data.onClose) {
54
- this.data.onClose();
55
- }
56
- this.dialogRef.close();
73
+ esSesionActual(token: string): boolean {
74
+ return token === this.tokenActual;
57
75
  }
58
76
 
59
- onAccept(): void {
60
- if (this.data.onAccept) {
61
- this.data.onAccept(this.MensajeBrindado);
77
+ getIcon(metodo: string): string {
78
+ switch (metodo) {
79
+ case 'GET': return 'search';
80
+ case 'POST': return 'add_circle';
81
+ case 'PUT': return 'edit';
82
+ case 'DELETE': return 'delete';
83
+ default: return 'help_outline';
62
84
  }
63
- this.dialogRef.close(this.MensajeBrindado);
64
85
  }
65
86
  }