utn-cli 2.0.32 → 2.0.34

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utn-cli",
3
- "version": "2.0.32",
3
+ "version": "2.0.34",
4
4
  "description": "Herramienta CLI unificada para la gestión de plantillas en SIGU.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -4,6 +4,13 @@ const Router = express.Router();
4
4
  const Miscelaneo = require('../servicios/Nucleo/Miscelaneas.js');
5
5
  const { moitoreo } = require('../servicios/Nucleo/Monitoreo.js');
6
6
 
7
+ // Router.post("/DescargarArchivo", async (solicitud, respuesta) => {
8
+ // if (await Miscelaneo.validarIdentificadorAPI(solicitud.headers)) {
9
+ // return Miscelaneo.descargarArchivo(respuesta, solicitud.body);
10
+ // }
11
+ // return respuesta.status(401).json();
12
+ // });
13
+
7
14
  Router.post('/monitoreo/', async (solicitud, respuesta, next) => {
8
15
  try {
9
16
  if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
@@ -4,6 +4,7 @@ const Router = express.Router();
4
4
  const Servicio1 = require('../servicios/Servicio1.js');
5
5
  const Miscelaneo = require('../servicios/Nucleo/Miscelaneas.js');
6
6
  const ManejadorDeErrores = require('../servicios/Nucleo/ManejadorDeErrores.js');
7
+ // const API = require('../servicios/API.js')
7
8
 
8
9
  Router.get('/funcionDeEjemplo', async (solicitud, respuesta, next) => {
9
10
  try {
@@ -22,6 +23,26 @@ Router.get('/funcionDeEjemplo', async (solicitud, respuesta, next) => {
22
23
  }
23
24
  });
24
25
 
26
+ // Router.get("/descargarArchivo/:ArchivoId", async (solicitud, respuesta, next) => {
27
+ // try {
28
+ // if (await Miscelaneo.validarToken(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
29
+ // try {
30
+ // let Datos = { ArchivoId: solicitud.params.ArchivoId };
31
+ // Datos.Token = solicitud.headers.authorization;
32
+ // await API.DescargarArchivo(Datos, respuesta);
33
+ // return;
34
+ // } catch (error) {
35
+ // const MensajeDeError = "No fue posible obtener el archivo";
36
+ // console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, `Dirección IP: ${solicitud.ip}`));
37
+ // return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
38
+ // }
39
+ // }
40
+ // return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
41
+ // } catch (error) {
42
+ // next(error)
43
+ // }
44
+ // });
45
+
25
46
  // Router.get("/listar/:PaginadorAccion/:PaginadorIndice/:PaginadorTamanio/:PaginadorFiltro/:PaginadorColumnaParaFiltrar/:PaginadorColumnasParaFiltrar/:ColumnaParaOrdenar/:TipoDeOrden", async (solicitud, respuesta, next) => {
26
47
  // try {
27
48
  // if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
@@ -277,6 +277,23 @@ Router.post('/reporteDeIncidencia/:Datos', async (solicitud, respuesta, next) =>
277
277
  }
278
278
  });
279
279
 
280
+ Router.post('/reporteDeSugerencia/:Datos', async (solicitud, respuesta, next) => {
281
+ try {
282
+ if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
283
+ try {
284
+ return respuesta.json({ body: await Miscelaneo.reporteDeSugerencia(solicitud, solicitud.params.Datos), error: undefined });
285
+ } catch (error) {
286
+ const MensajeDeError = 'No fue posible reportar la incidencia';
287
+ console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, `Dirección IP: ${solicitud.ip}`));
288
+ return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
289
+ }
290
+ }
291
+ return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
292
+ } catch (error) {
293
+ next(error);
294
+ }
295
+ });
296
+
280
297
  Router.get('/cerrarSesion', async (solicitud, respuesta, next) => {
281
298
  try {
282
299
  if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
@@ -1,4 +1,5 @@
1
1
  const Miscelaneo = require('./../servicios/Nucleo/Miscelaneas.js');
2
+ const { Readable } = require('stream');
2
3
 
3
4
  class API {
4
5
 
@@ -6,6 +7,32 @@ class API {
6
7
 
7
8
  };
8
9
 
10
+ // async DescargarArchivo(Datos, Respuesta) {
11
+ // let URL = undefined;
12
+ // switch (process.env.ENV) {
13
+ // case 'desarrollo':
14
+ // URL = 'http://dgdh-oferentes-backend-desarrollo.181.193.85.44.nip.io';
15
+ // break;
16
+ // case 'calidad':
17
+ // URL = 'http://dgdh-oferentes-backend-calidad.181.193.85.44.nip.io';
18
+ // break;
19
+ // case 'pruebas':
20
+ // URL = 'http://dgdh-oferentes-backend-pruebas.181.193.85.44.nip.io';
21
+ // break;
22
+ // case 'production':
23
+ // URL = 'https://dgdh-oferentes-backend.sigu.utn.ac.cr';
24
+ // break;
25
+ // default:
26
+ // URL = 'http://localhost:81';
27
+ // }
28
+ // URL += '/api/DescargarArchivo';
29
+ // let resultado = await Miscelaneo.consumirBackend(URL, Datos, 'stream');
30
+ // Respuesta.setHeader('Content-Type', resultado.headers.get('content-type'));
31
+ // Respuesta.setHeader('Content-Disposition', resultado.headers.get('content-disposition'));
32
+ // const nodeStream = Readable.fromWeb(resultado.body);
33
+ // nodeStream.pipe(Respuesta);
34
+ // };
35
+
9
36
  // async cursosPorPersonaAnioYPeriodo(Cuerpo) {
10
37
  // let URL = undefined;
11
38
  // switch (process.env.ENV) {
@@ -68,7 +68,7 @@ class Miscelaneo {
68
68
  obtenerNombreCanonicoDelModulo() {
69
69
  return this.NombreCanonicoDelModulo;
70
70
  }
71
-
71
+
72
72
  obtenerVersionDelModulo() {
73
73
  return this.Version;
74
74
  }
@@ -351,7 +351,7 @@ class Miscelaneo {
351
351
  });
352
352
  await ejecutarConsultaSIGU("INSERT INTO `SIGU`.`SIGU_SolicitudesDeRestablecimientoDeClave` VALUES (?, ?, ?, NOW(4), ?)"
353
353
  , [CorreoElectronico[0]['Identificador'], Solicitud.body.Identificacion
354
- , SolicitudTextual, LastUser]);
354
+ , SolicitudTextual, LastUser]);
355
355
  await envioDeCorreo(CorreoElectronico[0]['CorreoElectronico'], "Solicitud de restablecimiento de clave",
356
356
  "<p>Estimada persona usuaria,<br /><br />"
357
357
  + "Se ha realizado una solicitud de cambio de clave para su cuenta de acceso a SIGU.</p>"
@@ -364,49 +364,222 @@ class Miscelaneo {
364
364
  return;
365
365
  }
366
366
 
367
+
367
368
  async Autenticar(Solicitud) {
368
369
  const crypto = require('crypto');
369
370
  const bcrypt = require('bcryptjs');
370
371
  const jwt = require('jsonwebtoken');
371
372
  const LastUser = await this.generarLastUser(Solicitud);
372
- const resultados = await ejecutarConsultaSIGU("SELECT `Identificador`, `Clave` FROM `SIGU`.`SIGU_Personas` WHERE `Activo` = TRUE AND `Identificacion` = ?"
373
- , [Solicitud.body.Identificacion]);
374
- const Resultado = await bcrypt.compare(crypto.createHash('md5').update(Solicitud.body.Clave).digest("hex"), resultados[0]['Clave']);
375
- if (Resultado) {
376
- console.log("La clave brindada para el usuario", Solicitud.body.Identificacion, "conincide");
377
- const Token = await jwt.sign({ Identificador: resultados[0]['Identificador'], uid: resultados[0]['Identificador'] }, await this.palabraSecretaParaTokens(), { expiresIn: '2h' });
378
- await ejecutarConsultaSIGU("INSERT INTO `SIGU`.`SIGU_Sesiones` VALUES (?, ?, ?, NOW(4), ?) ON DUPLICATE KEY UPDATE `Token` = ?, `LastUser` = ?"
379
- , [resultados[0]['Identificador'], Solicitud.headers.host.trim(), Token, LastUser, Token, LastUser]
380
- );
381
- await ejecutarConsultaSIGU("DELETE FROM `SIGU`.`SIGU_SesionesFallidas` WHERE `Identificador` = ?"
382
- , [resultados[0]['Identificador']]);
383
- await ejecutarConsultaSIGU("INSERT INTO `SIGU`.`SIGU_PermisosPersonasV2`\
384
- SELECT `PermisoId`, ?, NOW(4), USER() FROM `SIGU`.`SIGU_PermisosV2` WHERE `Nombre` LIKE '%Público%'\
385
- ON DUPLICATE KEY UPDATE `LastUser` = USER()", [resultados[0]['Identificador']]);
386
- return { Token, Dominio: ((process.env.ENV || 'local') === 'production' ? '.sigu.utn.ac.cr' : '.181.193.85.44.nip.io') };
387
- } else {
388
- console.log("La clave brindada para el usuario", Solicitud.body.Identificacion, "no conincide");
389
- const Resultados2 = await ejecutarConsultaSIGU("SELECT COUNT(*) AS `Total` FROM `SIGU`.`SIGU_ClavesTemporalesDeLasPersonas`\
390
- WHERE `Identificador` = ? AND `Clave` = ?"
391
- , [resultados[0]['Identificador'], Solicitud.body.Clave]);
392
- if (Resultados2[0]['Total'] > 0) {
393
- console.log("La clave brindada para el usuario", Solicitud.body.Identificacion, "no conincide, pero coincide la clave temporal");
394
- const bcrypt = require('bcryptjs');
395
- await ejecutarConsultaSIGU("UPDATE `SIGU`.`SIGU_Personas` SET\
396
- `Clave` = ?, `LastUpdate` = NOW(4), `LastUser` = ? WHERE `Identificacion` = ?"
397
- , [await bcrypt.hash(require('crypto').createHash('md5').update(Solicitud.body.Clave).digest("hex"), 10)
398
- , LastUser, Solicitud.body.Identificacion]);
399
- await ejecutarConsultaSIGU("DELETE FROM `SIGU`.`SIGU_ClavesTemporalesDeLasPersonas`\
400
- WHERE `Identificador` = ?", [resultados[0]['Identificador']]);
401
- await this.Autenticar(Solicitud);
373
+ const ConexionSigu = await crearObjetoConexionSIGU();
374
+
375
+ try {
376
+ const resultados = await ConexionSigu.query("SELECT `Identificador`, `Clave` FROM `SIGU`.`SIGU_Personas` WHERE `Activo` = TRUE AND `Identificacion` = ?", [Solicitud.body.Identificacion]);
377
+ const Identificador = resultados[0][0]['Identificador'];
378
+ const Resultado = await bcrypt.compare(crypto.createHash('md5').update(Solicitud.body.Clave).digest("hex"), resultados[0][0]['Clave']);
379
+
380
+ if (Resultado) {
381
+ console.log("La clave brindada para el usuario", Solicitud.body.Identificacion, "coincide");
382
+ const Token = await jwt.sign({ Identificador: Identificador, uid: Identificador }, await this.palabraSecretaParaTokens(), { expiresIn: '2h' });
383
+ await ConexionSigu.query("INSERT INTO `SIGU`.`SIGU_Sesiones` VALUES (?, ?, ?, NOW(4), ?) ON DUPLICATE KEY UPDATE `Token` = ?, `LastUser` = ?", [Identificador, Solicitud.headers.host.trim(), Token, LastUser, Token, LastUser]
384
+ );
385
+ await ConexionSigu.query("DELETE FROM `SIGU`.`SIGU_SesionesFallidas` WHERE `Identificador` = ?", [Identificador]);
386
+ const permisos = await ConexionSigu.query("\
387
+ WITH RECURSIVE`ModulosJerarquia` AS( \
388
+ SELECT`Nombre`, `Padre` \
389
+ FROM`SIGU`.`SIGU_ModulosV2` \
390
+ WHERE`Nombre` COLLATE utf8mb4_spanish_ci IN(\
391
+ SELECT`Modulo` \
392
+ FROM`SIGU`.`SIGU_PermisosV2` \
393
+ WHERE`Nombre` LIKE '%Público%' \
394
+ ) \
395
+ UNION ALL \
396
+ SELECT`m`.`Nombre`, `m`.`Padre` \
397
+ FROM`SIGU`.`SIGU_ModulosV2` `m` \
398
+ INNER JOIN`ModulosJerarquia` `mj` \
399
+ ON`mj`.`Padre` COLLATE utf8mb4_spanish_ci = \
400
+ `m`.`Nombre` COLLATE utf8mb4_spanish_ci \
401
+ ) \
402
+ SELECT DISTINCT \
403
+ `p`.`PermisoId` \
404
+ FROM`ModulosJerarquia` `mj` \
405
+ JOIN`SIGU`.`SIGU_PermisosV2` `p` \
406
+ ON`p`.`Modulo` COLLATE utf8mb4_spanish_ci = \
407
+ `mj`.`Nombre` COLLATE utf8mb4_spanish_ci; \
408
+ ");
409
+
410
+ for (const permiso of permisos[0]) {
411
+ await ConexionSigu.query(" \
412
+ INSERT INTO `SIGU`.`SIGU_PermisosPersonasV2` \
413
+ (`PermisoId`, `Identificador`, `LastUpdate`, `LastUser`) \
414
+ VALUES (?, ?, NOW(4), USER()) \
415
+ ON DUPLICATE KEY UPDATE \
416
+ `LastUser` = USER(), \
417
+ `LastUpdate` = NOW(4);", [permiso.PermisoId, Identificador]);
418
+ }
419
+
420
+ // OBTENER IP DEL USUARIO
421
+ const ipUsuario = (Solicitud.headers['x-forwarded-for'] || '').split(',').shift() || Solicitud.socket?.remoteAddress || Solicitud.connection?.remoteAddress || '-';
422
+
423
+ // VERIFICAR SI LA IP YA EXISTE
424
+ const ipExiste = await ConexionSigu.query("\
425
+ SELECT COUNT(*) AS Total \
426
+ FROM `SIGU`.`SIGU_DireccionesUsadasPorLosUsuarios` \
427
+ WHERE `Identificador` = ? AND `DireccionUsadaPorElUsuario` = ?", [Identificador, ipUsuario]);
428
+ if (ipExiste[0][0].Total === 0) {
429
+ console.log("IP nueva detectada para el usuario, iniciando 2FA");
430
+
431
+ // GENERAR CODIGO 2FA
432
+ let Codigo2FA = await ConexionSigu.query("SELECT UUID() AS `Dato`");
433
+ Codigo2FA = Codigo2FA[0][0].Dato;
434
+
435
+ // GUARDAR CODIGO 2FA
436
+ await ConexionSigu.query(" \
437
+ REPLACE INTO `SIGU`.`SIGU_CodigosDe2FAParaLosUsuarios` \
438
+ VALUES (?, ?, NOW(4), ?)", [
439
+ Identificador,
440
+ Codigo2FA,
441
+ LastUser
442
+ ]);
443
+
444
+ // OBTENER CORREO
445
+ const CorreoElectronico = await ConexionSigu.query(
446
+ "SELECT `CorreoElectronico` \
447
+ FROM `SIGU`.`SIGU_CorreosPersona` \
448
+ WHERE `Identificador` = ? \
449
+ AND `Principal` = TRUE"
450
+ , [Identificador]);
451
+
452
+ // ENVIAR CORREO
453
+ console.log(CorreoElectronico[0][0].CorreoElectronico)
454
+ await envioDeCorreo(
455
+ CorreoElectronico[0][0].CorreoElectronico,
456
+ "Código de verificación 2FA",
457
+ "<p>Hemos recibido su solicitud de acceso para 2FA.</p>" +
458
+ "<p>Para continuar con el proceso, por favor utilice el siguiente código único de verificación:</p>" +
459
+ "<h2>" + Codigo2FA + "</h2>" +
460
+ "<p>Si usted no inició este proceso, puede ignorar este mensaje.</p>"
461
+ );
462
+ return { Requiere2FA: true };
463
+
464
+ }
465
+
466
+ // SI LA IP YA EXISTE CONTINUA
467
+ await ConexionSigu.query("\
468
+ INSERT INTO `SIGU`.`SIGU_DireccionesUsadasPorLosUsuarios` \
469
+ (`DireccionUsadaPorElUsuario`, `Identificador`, `LastUpdate`, `LastUser`) \
470
+ VALUES (?, ?, NOW(4), ?) \
471
+ ON DUPLICATE KEY UPDATE `LastUpdate` = NOW(4), `LastUser` = ?;", [
472
+ ipUsuario,
473
+ Identificador,
474
+ LastUser,
475
+ LastUser
476
+ ]);
477
+ return { Token, Dominio: ((process.env.ENV || 'local') === 'production' ? '.sigu.utn.ac.cr' : '.181.193.85.44.nip.io') };
478
+
402
479
  } else {
403
- await ejecutarConsultaSIGU("INSERT INTO `SIGU`.`SIGU_SesionesFallidas` VALUES (?, ?, NOW(4))"
404
- , [resultados[0]['Identificador'], Solicitud.headers.host.trim()]);
480
+ console.log("La clave brindada para el usuario", Solicitud.body.Identificacion, "no conincide");
481
+
482
+ const Resultados2 = await ConexionSigu.query("SELECT COUNT(*) AS `Total` FROM `SIGU`.`SIGU_ClavesTemporalesDeLasPersonas` WHERE `Identificador` = ? AND `Clave` = ?", [Identificador, Solicitud.body.Clave]);
483
+
484
+ if (Resultados2[0][0]['Total'] > 0) {
485
+ console.log("La clave brindada para el usuario", Solicitud.body.Identificacion, "no conincide, pero coincide la clave temporal");
486
+ await ConexionSigu.query("UPDATE `SIGU`.`SIGU_Personas` SET `Clave` = ?, `LastUpdate` = NOW(4), `LastUser` = ? WHERE `Identificacion` = ?"
487
+ , [await bcrypt.hash(require('crypto').createHash('md5').update(Solicitud.body.Clave).digest("hex"), 10), LastUser, Solicitud.body.Identificacion]);
488
+
489
+ await ConexionSigu.query("DELETE FROM `SIGU`.`SIGU_ClavesTemporalesDeLasPersonas`WHERE `Identificador` = ?", [Identificador]);
490
+ return await this.Autenticar(Solicitud);
491
+
492
+ } else {
493
+ await ConexionSigu.query("INSERT INTO `SIGU`.`SIGU_SesionesFallidas` VALUES (?, ?, NOW(4))"
494
+ , [Identificador, Solicitud.headers.host.trim()]);
495
+ }
405
496
  }
497
+ } catch (error) {
498
+ console.log(error);
499
+ return;
500
+ } finally {
501
+ if (ConexionSigu) await ConexionSigu.end();
406
502
  }
407
503
  return;
408
504
  }
409
505
 
506
+ async Verificar2FA(Solicitud) {
507
+ const jwt = require('jsonwebtoken');
508
+ const ConexionSigu = await crearObjetoConexionSIGU();
509
+ const LastUser = await this.generarLastUser(Solicitud);
510
+ try {
511
+ const { Identificacion, Codigo } = Solicitud.body;
512
+ const resultados = await ConexionSigu.query(
513
+ "SELECT `Identificador` FROM `SIGU`.`SIGU_Personas` WHERE `Activo` = TRUE AND `Identificacion` = ?", [Identificacion]);
514
+
515
+ if (!resultados[0].length) {
516
+ return { error: "Usuario no encontrado" };
517
+ }
518
+ const Identificador = resultados[0][0]['Identificador'];
519
+ // Validar código 2FA
520
+ const codigoValido = await ConexionSigu.query(" \
521
+ SELECT COUNT(*) AS Total \
522
+ FROM `SIGU`.`SIGU_CodigosDe2FAParaLosUsuarios` \
523
+ WHERE `Identificador` = ? \
524
+ AND `CodigoDe2FAParaElUsuario` = ?", [Identificador, Codigo]);
525
+
526
+ if (codigoValido[0][0].Total === 0) {
527
+ // Eliminar código usado cuando falle
528
+ await ConexionSigu.query(" \
529
+ DELETE FROM `SIGU`.`SIGU_CodigosDe2FAParaLosUsuarios` \
530
+ WHERE `Identificador` = ? ", [Identificador]);
531
+ return { error: "Código inválido" };
532
+ }
533
+
534
+ // Eliminar código usado
535
+ await ConexionSigu.query(" \
536
+ DELETE FROM `SIGU`.`SIGU_CodigosDe2FAParaLosUsuarios` WHERE `Identificador` = ?", [Identificador]);
537
+
538
+ // Obtener IP del usuario
539
+ const ipUsuario = (Solicitud.headers['x-forwarded-for'] || '').split(',').shift() || Solicitud.socket?.remoteAddress || Solicitud.connection?.remoteAddress || '-';
540
+
541
+ // Guardar IP autorizada
542
+ await ConexionSigu.query(" \
543
+ INSERT INTO `SIGU`.`SIGU_DireccionesUsadasPorLosUsuarios` (`DireccionUsadaPorElUsuario`, `Identificador`, `LastUpdate`, `LastUser`) \
544
+ VALUES (?, ?, NOW(4), ?) \
545
+ ON DUPLICATE KEY UPDATE `LastUpdate` = NOW(4), `LastUser` = ?;", [
546
+ ipUsuario,
547
+ Identificador,
548
+ LastUser,
549
+ LastUser
550
+ ]);
551
+
552
+ // Generar token
553
+ const Token = await jwt.sign(
554
+ { Identificador: Identificador, uid: Identificador },
555
+ await this.palabraSecretaParaTokens(),
556
+ { expiresIn: '2h' }
557
+ );
558
+
559
+ // Registrar sesión
560
+ await ConexionSigu.query(" \
561
+ INSERT INTO `SIGU`.`SIGU_Sesiones` \
562
+ VALUES (?, ?, ?, NOW(4), ?) \
563
+ ON DUPLICATE KEY UPDATE `Token` = ?, `LastUser` = ?", [
564
+ Identificador,
565
+ Solicitud.headers.host.trim(),
566
+ Token,
567
+ LastUser,
568
+ Token,
569
+ LastUser
570
+ ]);
571
+ return {
572
+ Token,
573
+ Dominio: ((process.env.ENV || 'local') === 'production' ? '.sigu.utn.ac.cr' : '.181.193.85.44.nip.io')
574
+ };
575
+ } catch (error) {
576
+ console.log(error);
577
+ return { error: "Error verificando 2FA" };
578
+ } finally {
579
+ if (ConexionSigu) await ConexionSigu.end();
580
+ }
581
+ }
582
+
410
583
  async ListadoDePaisesParaCrearCuenta() {
411
584
  const Resultado = await ejecutarConsultaSIGU("SELECT REPLACE(MID(`COLUMN_TYPE`, 6, CHAR_LENGTH(`COLUMN_TYPE`) - 6), \"'\", '') AS `Datos` FROM `information_schema`.`COLUMNS`\
412
585
  WHERE `TABLE_SCHEMA` = 'SIGU' AND `TABLE_NAME` = 'SIGU_Personas' AND\
@@ -538,11 +711,21 @@ class Miscelaneo {
538
711
  + "<p><b>Detalle de la incidencia: </b>" + Datos.detalle + "</p><br />"
539
712
  + "<p><b>Resultado esperado: </b>" + Datos.resultado + "</p><br />"
540
713
  + "<p><b>Información de contacto: </b>" + Datos.concato + "</p><br />"
541
- + "<p><b>Información del usuario: </b>" + await this.obtenerDatosDeLaPersona(Solicitud.headers.authorization) + "</p><br />"
714
+ + "<p><b>Información del usuario: </b>" + await this.obtenerDatosDelUsuario(Solicitud.headers.authorization) + "</p><br />"
542
715
  , [DatosDelArchivo.rutaDeArchivo]);
543
716
  return;
544
717
  }
545
718
 
719
+ async reporteDeSugerencia(Solicitud, Datos) {
720
+ // const DatosDelArchivo = await this.cargarArchivo(Solicitud, Datos);
721
+ await envioDeCorreo('msavatar@utn.ac.cr', "Reporte de sugerencia",
722
+ "<p><b>Sistema: </b>" + this.NombreCanonicoDelModulo + "</p><br />"
723
+ + "<p><b>Asunto: </b>Reporte de sugerencia</p><br />"
724
+ + "<p><b>Detalle de la sugerencia: </b>" + Datos.detalle + "</p><br />"
725
+ + "<p><b>Información del usuario: </b>" + await this.obtenerDatosDelUsuario(Solicitud.headers.authorization) + "</p><br />");
726
+ return;
727
+ }
728
+
546
729
  async cerrarSesion(Token) {
547
730
  let Resultado = undefined;
548
731
  try {
@@ -1058,7 +1241,7 @@ class Miscelaneo {
1058
1241
  if (!Resultado) {
1059
1242
  throw new ManejadorDeErrores(ManejadorDeErrores.mensajeDeErrorVerificacionDeToken(), ManejadorDeErrores.obtenerNumeroDeLinea(), encabezadoDeAutorizacion);
1060
1243
  }
1061
- Resultado.token = token;
1244
+ // Resultado.token = token;
1062
1245
  const DatosDeLaPersona = await ejecutarConsultaSIGU("SELECT `Identificacion`, `Nombre`, `PrimerApellido`,\
1063
1246
  `SegundoApellido`, CONCAT(`FechaNacimiento`) AS `FechaDeNacimiento`\
1064
1247
  FROM `SIGU`.`SIGU_Personas` WHERE `Identificador` = ?", [Resultado.uid]);
@@ -25,15 +25,10 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
25
25
 
26
26
  export class ReporteDeIncidenciasComponent implements OnInit {
27
27
  formulario!: FormGroup;
28
-
29
28
  archivos: File[] = [];
30
-
31
29
  readonly dialogRef = inject(MatDialogRef<ReporteDeIncidenciasComponent>);
32
-
33
30
  constructor(private fb: FormBuilder, private datosGlobalesService: DatosGlobalesService, private http: HttpClient) { }
34
-
35
31
  @ViewChild('EntradDeArchivo') EntradDeArchivo!: ElementRef<HTMLInputElement>;
36
-
37
32
  Archivo: any;
38
33
 
39
34
  ngOnInit(): void {
@@ -0,0 +1,37 @@
1
+ .contenedor {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ }
5
+
6
+ .fila {
7
+ width: 100%;
8
+ min-width: 500px;
9
+ }
10
+
11
+ .campo {
12
+ width: 100%;
13
+ }
14
+
15
+ .zona-archivo {
16
+ width: 90%;
17
+ height: 10vh;
18
+ margin-left: 4%;
19
+ border: 2px dashed #3498db;
20
+ padding: 1%;
21
+ align-items: center;
22
+ display: flex;
23
+ cursor: pointer;
24
+ transition: background 0.3s;
25
+ }
26
+
27
+ .texto {
28
+ width: 100%;
29
+ text-align: center;
30
+ }
31
+
32
+ .texto-clic {
33
+ color: #007bff;
34
+ font-weight: bold;
35
+ cursor: pointer;
36
+ text-decoration: underline;
37
+ }
@@ -0,0 +1,17 @@
1
+ <h2 mat-dialog-title>Reporte de la sugerencia</h2>
2
+ <mat-dialog-content>
3
+ <div class="contenedor">
4
+ <form [formGroup]="formulario">
5
+ <div class="fila">
6
+ <mat-form-field class="campo">
7
+ <mat-label>Detalle de la sugerencia</mat-label>
8
+ <textarea matInput formControlName="detalle" required></textarea>
9
+ </mat-form-field>
10
+ </div>
11
+ </form>
12
+ </div>
13
+ </mat-dialog-content>
14
+ <mat-dialog-actions>
15
+ <button mat-button (click)="Cerrar()">Cerrar</button>
16
+ <button mat-button (click)="Enviar()">Enviar</button>
17
+ </mat-dialog-actions>
@@ -0,0 +1,54 @@
1
+ import { Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core';
2
+ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
3
+ import { MatFormFieldModule } from '@angular/material/form-field';
4
+ import { MatInputModule } from '@angular/material/input';
5
+ import { ReactiveFormsModule } from '@angular/forms';
6
+ import { MatDialogActions, MatDialogContent, MatDialogRef, MatDialogTitle } from '@angular/material/dialog';
7
+ import { MatButtonModule } from '@angular/material/button';
8
+ import { DatosGlobalesService } from '../../../datos-globales.service';
9
+ import { HttpClient, HttpHeaders } from '@angular/common/http';
10
+
11
+ @Component({
12
+ selector: 'app-reporte-de-sugerencias',
13
+ templateUrl: './reporte-de-sugerencias.component.html',
14
+ styleUrls: ['./reporte-de-sugerencias.component.css'],
15
+ imports: [
16
+ MatFormFieldModule,
17
+ MatInputModule,
18
+ ReactiveFormsModule,
19
+ MatDialogContent,
20
+ MatButtonModule,
21
+ MatDialogTitle,
22
+ MatDialogActions
23
+ ]
24
+ })
25
+
26
+ export class ReporteDeSugerenciasComponent implements OnInit {
27
+ formulario!: FormGroup;
28
+ readonly dialogRef = inject(MatDialogRef<ReporteDeSugerenciasComponent>);
29
+ constructor(private fb: FormBuilder, private datosGlobalesService: DatosGlobalesService, private http: HttpClient) { }
30
+
31
+ ngOnInit(): void {
32
+ this.formulario = this.fb.group({
33
+ detalle: ['', Validators.required]
34
+ });
35
+ }
36
+
37
+ Cerrar(): void {
38
+ this.dialogRef.close();
39
+ }
40
+
41
+ Enviar() {
42
+ const Datos = JSON.stringify(this.formulario.value);
43
+ this.http.post(this.datosGlobalesService.ObtenerURL() + 'misc/reporteDeSugerencia/' + Datos, {})
44
+ .subscribe({
45
+ next: (data: any) => {
46
+ alert('Mensaje enviado');
47
+ },
48
+ error: (error) => {
49
+ console.error('Ocurrió un error al informar de la sugerencia:', error);
50
+ }
51
+ })
52
+ this.Cerrar();
53
+ }
54
+ }
@@ -96,7 +96,7 @@
96
96
  <ng-container matColumnDef="expand">
97
97
  <th mat-header-cell *matHeaderCellDef aria-label="Fila expandible"></th>
98
98
  <td mat-cell *matCellDef="let element">
99
- <button mat-icon-button aria-label="Expander fila"
99
+ <button mat-icon-button aria-label="Expandir fila"
100
100
  (click)="(elementoExpandible = elementoExpandible === element ? null : element); $event.stopPropagation()">
101
101
  @if (elementoExpandible === element) {
102
102
  <mat-icon>keyboard_arrow_up</mat-icon>
@@ -85,6 +85,9 @@
85
85
  <button class="botonDeNavegacion" matTooltip="Reporte" mat-button (click)="irASoporte()">
86
86
  <mat-icon>support_agent</mat-icon>
87
87
  </button>
88
+ <button class="botonDeNavegacion" matTooltip="Sugerencias" mat-button (click)="irASugerencias()">
89
+ <mat-icon>lightbulb</mat-icon>
90
+ </button>
88
91
  </div>
89
92
  </div>
90
93
  </div>
@@ -6,9 +6,10 @@ import { Location, CommonModule } from '@angular/common';
6
6
  import { MatIconModule } from '@angular/material/icon';
7
7
  import { MatTooltipModule } from '@angular/material/tooltip';
8
8
  import { MatDialog } from '@angular/material/dialog';
9
- import { ReporteDeIncidenciasComponent } from '../../../Componentes/Nucleo/reporte-de-incidencias/reporte-de-incidencias.component'
9
+ import { ReporteDeIncidenciasComponent } from '../../../Componentes/Nucleo/reporte-de-incidencias/reporte-de-incidencias.component';
10
10
  import { MensajesComponent } from '../../../Componentes/Nucleo/mensajes/mensajes.component';
11
11
  import { MensajeConfirmacionHTMLComponent } from '../../../Componentes/Nucleo/mensaje-confirmacion-html/mensaje-confirmacion-html';
12
+ import { ReporteDeSugerenciasComponent } from '../../../Componentes/Nucleo/reporte-de-sugerencias/reporte-de-sugerencias.component';
12
13
 
13
14
  @Component({
14
15
  selector: 'app-contenedor-componentes',
@@ -189,4 +190,8 @@ export class ContenedorComponentesComponent {
189
190
  window.location.href = datos.body;
190
191
  })
191
192
  }
193
+
194
+ irASugerencias(): void {
195
+ this.dialog.open(ReporteDeSugerenciasComponent);
196
+ }
192
197
  }