utn-cli 2.0.33 → 2.0.35

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.33",
3
+ "version": "2.0.35",
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)) {
@@ -4,6 +4,14 @@ const Router = express.Router();
4
4
  const Miscelaneo = require('./../servicios/Nucleo/Miscelaneas.js');
5
5
  const ManejadorDeErrores = require('../servicios/Nucleo/ManejadorDeErrores.js');
6
6
 
7
+ Router.post('/Verificar2FA', async (solicitud, respuesta, next) => {
8
+ try {
9
+ return respuesta.json({ body: await Miscelaneo.Verificar2FA(solicitud), error: undefined });
10
+ } catch (error) {
11
+ next(error);
12
+ }
13
+ });
14
+
7
15
  Router.get('/DatosParaReporteCSV', async (solicitud, respuesta, next) => {
8
16
  try {
9
17
  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) {
@@ -1,4 +1,4 @@
1
- const { ejecutarConsulta, ejecutarConsultaSIGU, ejecutarConsultaCumulo } = require('./db.js');
1
+ const { ejecutarConsulta, ejecutarConsultaSIGU, ejecutarConsultaCumulo, crearObjetoConexionSIGU } = require('./db.js');
2
2
  const ManejadorDeErrores = require('./ManejadorDeErrores.js');
3
3
  const InformacionDelModulo = require('../InformacionDelModulo.js');
4
4
  const { envioDeCorreo } = require('./EnvioDeCorreos.js');
@@ -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,7 +711,7 @@ 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
  }
@@ -549,7 +722,7 @@ class Miscelaneo {
549
722
  "<p><b>Sistema: </b>" + this.NombreCanonicoDelModulo + "</p><br />"
550
723
  + "<p><b>Asunto: </b>Reporte de sugerencia</p><br />"
551
724
  + "<p><b>Detalle de la sugerencia: </b>" + Datos.detalle + "</p><br />"
552
- + "<p><b>Información del usuario: </b>" + await this.obtenerDatosDeLaPersona(Solicitud.headers.authorization) + "</p><br />");
725
+ + "<p><b>Información del usuario: </b>" + await this.obtenerDatosDelUsuario(Solicitud.headers.authorization) + "</p><br />");
553
726
  return;
554
727
  }
555
728
 
@@ -1068,7 +1241,7 @@ class Miscelaneo {
1068
1241
  if (!Resultado) {
1069
1242
  throw new ManejadorDeErrores(ManejadorDeErrores.mensajeDeErrorVerificacionDeToken(), ManejadorDeErrores.obtenerNumeroDeLinea(), encabezadoDeAutorizacion);
1070
1243
  }
1071
- Resultado.token = token;
1244
+ // Resultado.token = token;
1072
1245
  const DatosDeLaPersona = await ejecutarConsultaSIGU("SELECT `Identificacion`, `Nombre`, `PrimerApellido`,\
1073
1246
  `SegundoApellido`, CONCAT(`FechaNacimiento`) AS `FechaDeNacimiento`\
1074
1247
  FROM `SIGU`.`SIGU_Personas` WHERE `Identificador` = ?", [Resultado.uid]);
@@ -3,88 +3,106 @@ button:focus {
3
3
  }
4
4
 
5
5
  .contenedor {
6
-
7
- min-height: 20vh;
8
- padding: 2%;
9
- padding-top: 10%;
10
- padding-bottom: 5%;
11
-
6
+ max-height: 80vh;
7
+ display: flex;
8
+ flex-direction: column;
9
+ padding: 20px;
10
+ /* Padding uniforme en todos los lados */
12
11
  background-color: white;
12
+ overflow: hidden;
13
+ box-sizing: border-box;
13
14
  }
14
15
 
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;
16
+ .lista {
17
+ width: 100%;
18
+ /* Ocupa todo el ancho disponible del contenedor */
19
+ margin-bottom: 15px;
20
+ max-height: 300px;
21
+ overflow-y: auto;
22
+ overflow-x: hidden;
22
23
  display: flex;
23
- cursor: pointer;
24
- transition: background 0.3s;
24
+ flex-direction: column;
25
+ gap: 8px;
26
+ /* Espacio uniforme entre archivos */
25
27
  }
26
28
 
27
- .texto {
28
- width: 100%;
29
- text-align: center;
29
+ /* Estilo de la fila de archivo */
30
+ .lista .archivo {
31
+ border: solid 1px #007bff;
32
+ border-radius: 8px;
33
+ display: flex;
34
+ justify-content: space-between;
35
+ align-items: center;
36
+ padding: 8px 15px;
37
+ /* Espacio interno simétrico */
38
+ box-sizing: border-box;
30
39
  }
31
40
 
32
- .zona-archivo:hover,
33
- .zona-archivo.arrastrar {
34
- background: #e3f2fd;
41
+ .lista .archivo p {
42
+ margin: 0;
43
+ flex: 1;
44
+ /* El texto toma el espacio restante */
45
+ text-align: left;
46
+ padding-right: 10px;
35
47
  }
36
48
 
37
- .texto-clic {
38
- color: #007bff;
39
- font-weight: bold;
49
+ /* Zona de Arrastre */
50
+ .zona-archivo {
51
+ width: 100%;
52
+ /* Centrado automático */
53
+ height: 100px;
54
+ border: 2px dashed #3498db;
55
+ border-radius: 8px;
56
+ display: flex;
57
+ justify-content: center;
58
+ align-items: center;
40
59
  cursor: pointer;
41
- text-decoration: underline;
60
+ transition: background 0.3s;
61
+ box-sizing: border-box;
42
62
  }
43
63
 
44
- .pie {
45
- margin-top: 5%;
46
- margin-right: 5%;
64
+ /* Centrado del mensaje vacío */
65
+ .mensaje-vacio {
47
66
  display: flex;
48
- flex-direction: row-reverse;
67
+ justify-content: center;
68
+ align-items: center;
69
+ padding: 40px 0;
70
+ /* Margen superior e inferior igual */
71
+ color: #7f8c8d;
72
+ width: 100%;
49
73
  }
50
74
 
51
- .lista {
52
- width: 95%;
53
- margin-bottom: 2%;
54
- padding-left: 4%;
75
+ .mensaje-vacio p {
76
+ margin: 0;
55
77
  }
56
78
 
57
- .lista .archivo {
58
- border: solid 1px #007bff;
59
- ;
60
- border-radius: 10px;
61
- width: 100%;
79
+ .pie {
80
+ margin-top: 20px;
62
81
  display: flex;
63
- justify-content: center;
64
- align-items: center;
65
- margin-bottom: 1%;
82
+ justify-content: flex-end;
83
+ /* Alinea botones a la derecha */
84
+ gap: 10px;
66
85
  }
67
86
 
68
- .lista .archivo p {
69
-
70
- text-align: center;
71
- width: 70%;
72
- padding: 1%;
87
+ /* Estilizador de Scrollbar */
88
+ .lista::-webkit-scrollbar {
89
+ width: 6px;
73
90
  }
74
91
 
75
- .lista p:nth-child(odd) {
76
- margin-right: 2%;
92
+ .lista::-webkit-scrollbar-thumb {
93
+ background: #bdc3c7;
94
+ border-radius: 10px;
77
95
  }
78
96
 
97
+ /* Iconos */
79
98
  .descargar {
80
99
  color: #007bff;
81
- margin-right: 5%;
82
100
  }
83
101
 
84
102
  .eliminar {
85
- color: red;
103
+ color: #e74c3c;
86
104
  }
87
105
 
88
106
  .deshabilitado {
89
- color: gray;
107
+ color: #bdc3c7;
90
108
  }
@@ -2,7 +2,8 @@
2
2
  <div class="lista">
3
3
  @for(archivo of ListaArchivos;track $index){
4
4
  <div class="archivo">
5
- <p>{{archivo.Nombre}}</p>
5
+ <p style="text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{archivo.Nombre}}
6
+ </p>
6
7
  <button mat-icon-button (click)="DescargarArchivo(archivo.ArchivoId,archivo.Nombre)" matTooltip="Descargar">
7
8
  <!--pone deshabilitado si es estado coincide con el indicado -->
8
9
  <mat-icon class="descargar">download</mat-icon>
@@ -29,11 +30,13 @@
29
30
  </div>
30
31
  } @else {
31
32
  @if(ListaArchivos.length === 0) {
32
- <p>Sin archivos adjuntos</p>
33
+ <div class="mensaje-vacio">
34
+ <p>Sin archivos adjuntos</p>
35
+ </div>
33
36
  }
34
37
  }
35
38
  <div class="pie">
36
39
  <button mat-button [disabled]="!EsEditable" (click)="SubirArchivo()">Guardar</button>
37
40
  <button mat-button (click)="Cancelar()">Cerrar</button>
38
41
  </div>
39
- </div>
42
+ </div>
@@ -21,10 +21,17 @@ export class SubirArchivoComponent implements OnInit {
21
21
  Etiqueta = this.data.Etiqueta + this.Permiso;
22
22
  EsEditable = this.data.EsEditable;
23
23
  Token: any;
24
- constructor(private datosGlobalesService: DatosGlobalesService,
25
- private http: HttpClient
26
- ) { }
24
+ public RutaParaListar: string = 'misc/listarArchivos/';
25
+ public RutaParaDescargar: string = 'misc/descargarArchivo/';
26
+ constructor(private datosGlobalesService: DatosGlobalesService, private http: HttpClient) { }
27
+
27
28
  ngOnInit(): void {
29
+ if (this.data.RutaParaListar) {
30
+ this.RutaParaListar = this.data.RutaParaListar;
31
+ }
32
+ if (this.data.RutaParaDescargar) {
33
+ this.RutaParaDescargar = this.data.RutaParaDescargar;
34
+ }
28
35
  this.ListarArchivos(this.Etiqueta)
29
36
  }
30
37
 
@@ -33,7 +40,6 @@ export class SubirArchivoComponent implements OnInit {
33
40
  event.stopPropagation();
34
41
  const archivoZona = event.target as HTMLElement;
35
42
  archivoZona.classList.add('arrastrar');
36
-
37
43
  }
38
44
 
39
45
  ArrastrarAfuera(event: DragEvent) {
@@ -44,10 +50,8 @@ export class SubirArchivoComponent implements OnInit {
44
50
  Soltar(event: DragEvent) {
45
51
  event.preventDefault();
46
52
  event.stopPropagation();
47
-
48
53
  const archivoZona = event.target as HTMLElement;
49
54
  archivoZona.classList.remove('arrastrar');
50
-
51
55
  if (event.dataTransfer?.files.length && this.EsEditable) {
52
56
  this.Archivo = event.dataTransfer.files[0];
53
57
  }
@@ -89,7 +93,7 @@ export class SubirArchivoComponent implements OnInit {
89
93
  }
90
94
 
91
95
  ListarArchivos(Etiqueta: string) {
92
- this.http.get(this.datosGlobalesService.ObtenerURL() + 'misc/listarArchivos/' + Etiqueta)
96
+ this.http.get(this.datosGlobalesService.ObtenerURL() + this.RutaParaListar + Etiqueta)
93
97
  .subscribe({
94
98
  next: (data: any) => {
95
99
  this.ListaArchivos = data.body;
@@ -101,7 +105,7 @@ export class SubirArchivoComponent implements OnInit {
101
105
  }
102
106
 
103
107
  DescargarArchivo(ArchivoId: string, Nombre: string) {
104
- this.http.get(this.datosGlobalesService.ObtenerURL() + 'misc/descargarArchivo/' + ArchivoId + this.Permiso
108
+ this.http.get(this.datosGlobalesService.ObtenerURL() + this.RutaParaDescargar + ArchivoId + this.Permiso
105
109
  , { responseType: 'blob' })
106
110
  .subscribe((pdfBlob) => {
107
111
  const url = window.URL.createObjectURL(pdfBlob);