utn-cli 2.0.53 → 2.0.55

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.53",
3
+ "version": "2.0.55",
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,23 @@ 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.get('/ListarActividades', async (solicitud, respuesta, next) => {
8
+ try {
9
+ if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
10
+ try {
11
+ return respuesta.json({ body: await Miscelaneo.ListarActividades(solicitud.headers.authorization), error: undefined });
12
+ } catch (error) {
13
+ const MensajeDeError = 'No fue posible listar las actividades';
14
+ console.error(new ManejadorDeErrores(MensajeDeError, ManejadorDeErrores.obtenerNumeroDeLinea(), true, `Dirección IP: ${solicitud.ip}`));
15
+ return respuesta.status(500).json({ body: undefined, error: MensajeDeError });
16
+ }
17
+ }
18
+ return respuesta.status(401).json({ body: undefined, error: ManejadorDeErrores.mensajeDeError401() });
19
+ } catch (error) {
20
+ next(error);
21
+ }
22
+ });
23
+
7
24
  Router.get('/UsuariosActuales', async (solicitud, respuesta, next) => {
8
25
  try {
9
26
  if (await Miscelaneo.validarTokenV2(solicitud.headers.authorization) && await Miscelaneo.validarAccesoDelOrigen(solicitud)) {
@@ -10,6 +10,12 @@ const app = express();
10
10
  app.use(express.json());
11
11
  app.use(helmet());
12
12
  app.use(async (solicitud, respuesta, next) => {
13
+ let Usuario = null;
14
+ if (solicitud.headers.authorization) {
15
+ Usuario = await Miscelaneo.obtenerDatosDelUsuario(solicitud.headers.authorization) ?? null;
16
+ } else {
17
+ Usuario = solicitud.user ?? null;
18
+ }
13
19
  const safeReqData = {
14
20
  timestamp: new Date().toISOString(),
15
21
  httpVersion: solicitud.httpVersion,
@@ -30,7 +36,7 @@ app.use(async (solicitud, respuesta, next) => {
30
36
  cookies: solicitud.cookies,
31
37
  signedCookies: solicitud.signedCookies,
32
38
  userAgent: solicitud.get('User-Agent'),
33
- authUser: solicitud.user ?? null,
39
+ authUser: Usuario,
34
40
  };
35
41
  try {
36
42
  const { ejecutarConsultaSIGU } = require('./servicios/Nucleo/db.js');
@@ -61,7 +67,9 @@ asignarRutasAExpress(app);
61
67
 
62
68
  app.use((error, solicitud, respuesta, next) => {
63
69
  const { envioDeCorreo } = require('./servicios/Nucleo/EnvioDeCorreos.js');
64
- envioDeCorreo(process.env.DESTINATARIODEINFORMESDEERROR, 'Informe de error desde: ' + process.env.NOMBRECANONICODELMODULO, error.stack);
70
+ const route = solicitud.originalUrl;
71
+ const message = `<b>Ruta consultada:</b> ${route}<BR /><BR />` + error.stack;
72
+ envioDeCorreo(process.env.DESTINATARIODEINFORMESDEERROR, 'Informe de error desde: ' + process.env.NOMBRECANONICODELMODULO, message);
65
73
  console.error(error.stack);
66
74
  respuesta.status(500).send('Error interno del servidor, el detalle del mismo fue enviado a ' + process.env.DESTINATARIODEINFORMESDEERROR);
67
75
  });
@@ -40,6 +40,51 @@ class Miscelaneo {
40
40
  this.EnlaceDeAcceso = undefined;
41
41
  };
42
42
 
43
+ async ListarActividades(Datos) {
44
+ try {
45
+ const decoded = await this.obtenerDatosDelUsuario(Datos);
46
+ const RUTAS_EXCLUIDAS = [
47
+ '/misc',
48
+ '/Actividad',
49
+ '/misc/ListarActividades',
50
+ '/misc/obtenerNotificaciones',
51
+ '/misc/configurarFrontend',
52
+ '/ConsentimientoInformado/ConsentimientoInformado',
53
+ '/misc/obtenerMensajesModulares',
54
+ '/misc/obtenerDetalleDelModulo',
55
+ '/misc/validarToken',
56
+ '/misc/UsuariosActuales',
57
+ '/misc/obtenerEnlaceDePortal',
58
+ '/Personas/PermisoExtra'
59
+ ];
60
+ const datos = await ejecutarConsultaSIGU(`
61
+ SELECT
62
+ Consecutivo,
63
+ LastUpdate AS Fecha,
64
+ JSON_VALUE(Solicitud, '$.ip') AS IP,
65
+ JSON_VALUE(Solicitud, '$.userAgent') AS Navegador,
66
+ JSON_VALUE(Solicitud, '$.method') AS Metodo,
67
+ JSON_VALUE(Solicitud, '$.originalUrl') AS URL,
68
+ IFNULL(JSON_VALUE(Solicitud, '$.country'), 'Costa Rica') AS Pais,
69
+ CASE
70
+ WHEN JSON_VALUE(Solicitud, '$.userAgent') LIKE '%Mobile%' THEN 'Móvil'
71
+ WHEN JSON_VALUE(Solicitud, '$.userAgent') LIKE '%Android%' AND JSON_VALUE(Solicitud, '$.userAgent') NOT LIKE '%Mobile%' THEN 'Tablet'
72
+ WHEN JSON_VALUE(Solicitud, '$.userAgent') LIKE '%iPad%' THEN 'Tablet'
73
+ ELSE 'PC'
74
+ END AS Dispositivo
75
+ FROM SIGU.SIGU_BitacoraDeSolicitudes
76
+ WHERE JSON_VALUE(Solicitud, '$.authUser.uid') = ?
77
+ AND JSON_VALUE(Solicitud, '$.originalUrl') NOT IN (?)
78
+ ORDER BY Consecutivo DESC
79
+ LIMIT 100
80
+ `, [decoded.uid, RUTAS_EXCLUIDAS]);
81
+ return datos;
82
+ } catch (error) {
83
+ console.error('Error al listar actividad:', error);
84
+ return [];
85
+ }
86
+ }
87
+
43
88
  async UsuariosActuales() {
44
89
  const ConexionSigu = await crearObjetoConexionSIGU();
45
90
  const Actuales = await ConexionSigu.query("SELECT COUNT(DISTINCT `Identificador`) AS `Total` FROM `SIGU`.`SIGU_Sesiones` WHERE `LastUpdate` >= NOW() - INTERVAL 2 HOUR");
@@ -494,15 +539,15 @@ class Miscelaneo {
494
539
  }
495
540
 
496
541
  async generarLastUser(Solicitud) {
497
- let Resultado = undefined;
498
- try {
499
- Resultado = await this.obtenerDatosDelUsuario(Solicitud.headers.authorization);
500
- if (!Resultado) {
501
- throw new ManejadorDeErrores(ManejadorDeErrores.mensajeDeErrorVerificacionDeToken(), ManejadorDeErrores.obtenerNumeroDeLinea());
502
- }
503
- } catch (error) {
504
- console.log(error);
505
- }
542
+ const Resultado = await this.obtenerDatosDelUsuario(Solicitud.headers.authorization);
543
+ // try {
544
+ // Resultado = await this.obtenerDatosDelUsuario(Solicitud.headers.authorization);
545
+ // if (!Resultado) {
546
+ // throw new ManejadorDeErrores(ManejadorDeErrores.mensajeDeErrorVerificacionDeToken(), ManejadorDeErrores.obtenerNumeroDeLinea());
547
+ // }
548
+ // } catch (error) {
549
+ // console.log(error);
550
+ // }
506
551
  let LastUser = '';
507
552
  if (Resultado) {
508
553
  LastUser = Resultado.uid;
@@ -0,0 +1,193 @@
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;
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;
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 { 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; }
118
+
119
+ .item-contenido {
120
+ flex: 1;
121
+ display: flex;
122
+ flex-direction: column;
123
+ min-width: 0; /* Evita que el flex desborde */
124
+ }
125
+
126
+ .item-titulo {
127
+ font-size: 14px;
128
+ color: #002f6b;
129
+ font-weight: 600;
130
+ word-break: break-all;
131
+ margin-bottom: 2px;
132
+ }
133
+
134
+ .item-detalles {
135
+ display: flex;
136
+ flex-wrap: wrap;
137
+ gap: 8px;
138
+ font-size: 12px;
139
+ color: #5f6368;
140
+ }
141
+
142
+ .item-detalles span {
143
+ display: flex;
144
+ align-items: center;
145
+ gap: 3px;
146
+ white-space: nowrap;
147
+ }
148
+
149
+ .item-detalles mat-icon {
150
+ font-size: 14px;
151
+ width: 14px;
152
+ height: 14px;
153
+ }
154
+
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; }
159
+
160
+ .item-fecha {
161
+ font-size: 11px;
162
+ color: #9aa0a6;
163
+ white-space: nowrap;
164
+ margin-left: 12px;
165
+ display: flex;
166
+ flex-direction: column;
167
+ align-items: flex-end;
168
+ font-family: 'Roboto Mono', monospace;
169
+ flex-shrink: 0;
170
+ }
171
+
172
+ mat-progress-bar {
173
+ height: 4px;
174
+ border-radius: 2px;
175
+ margin-bottom: 10px;
176
+ }
177
+
178
+ @media (max-width: 600px) {
179
+ .actividad-item {
180
+ flex-direction: column;
181
+ align-items: flex-start;
182
+ }
183
+
184
+ .item-icono {
185
+ margin-bottom: 12px;
186
+ }
187
+
188
+ .item-fecha {
189
+ margin-left: 0;
190
+ margin-top: 12px;
191
+ align-items: flex-start;
192
+ }
193
+ }
@@ -0,0 +1,50 @@
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>
5
+
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 <strong>soporte@utn.ac.cr</strong>.</p>
9
+ </div>
10
+ </div>
11
+
12
+ @if(cargando){
13
+ <mat-progress-bar mode="indeterminate"></mat-progress-bar>
14
+ }
15
+
16
+ <div class="lista-actividad">
17
+ @if(actividad.length === 0 && !cargando) {
18
+ <p class="sin-datos">No hay actividad reciente.</p>
19
+ }
20
+
21
+ @for (item of actividad; track item.Consecutivo) {
22
+ <div class="actividad-item">
23
+ <div class="item-icono" [ngClass]="{
24
+ 'metodo-get': item.Metodo === 'GET',
25
+ 'metodo-post': item.Metodo === 'POST',
26
+ 'metodo-put': item.Metodo === 'PUT',
27
+ 'metodo-delete': item.Metodo === 'DELETE'
28
+ }">
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>
42
+ </div>
43
+ <div class="item-fecha">
44
+ <span>{{ item.Fecha | date: 'dd/MM/yyyy' }}</span>
45
+ <strong>{{ item.Fecha | date: 'HH:mm:ss' }}</strong>
46
+ </div>
47
+ </div>
48
+ }
49
+ </div>
50
+ </div>
@@ -0,0 +1,61 @@
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';
7
+ import { DatosGlobalesService } from '../../../datos-globales.service';
8
+
9
+ @Component({
10
+ selector: 'app-gestion-actividad',
11
+ standalone: true,
12
+ imports: [CommonModule, MatIconModule, MatTooltipModule, MatProgressBarModule],
13
+ templateUrl: './gestion-actividad.component.html',
14
+ styleUrls: ['./gestion-actividad.component.css']
15
+ })
16
+ export class GestionActividadComponent implements OnInit {
17
+ public actividad: any[] = [];
18
+ public cargando = false;
19
+
20
+ constructor(private http: HttpClient, private datosGlobalesService: DatosGlobalesService) { }
21
+
22
+ ngOnInit(): void {
23
+ this.obtenerActividad();
24
+ }
25
+
26
+ obtenerActividad(): void {
27
+ this.http.get(`${this.datosGlobalesService.ObtenerURL()}misc/ListarActividades`).subscribe({
28
+ next: (datos: any) => {
29
+ this.actividad = datos.body;
30
+ },
31
+ error: (error) => {
32
+ console.error('Error al obtener actividad:', error);
33
+ this.cargando = false;
34
+ },
35
+ complete: () => {
36
+ this.cargando = false;
37
+ }
38
+ });
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
+ // });
50
+ }
51
+
52
+ getIcon(metodo: string): string {
53
+ switch (metodo) {
54
+ case 'GET': return 'search';
55
+ case 'POST': return 'add_circle';
56
+ case 'PUT': return 'edit';
57
+ case 'DELETE': return 'delete';
58
+ default: return 'help_outline';
59
+ }
60
+ }
61
+ }
@@ -1,4 +1,7 @@
1
+ import { Injectable } from '@angular/core';
1
2
  import { MatPaginatorIntl } from '@angular/material/paginator';
3
+
4
+ @Injectable()
2
5
  export class PaginadorPersonalizado extends MatPaginatorIntl {
3
6
  override itemsPerPageLabel = 'Elementos por página:';
4
7
  override nextPageLabel = 'Siguiente página';
@@ -38,6 +38,15 @@
38
38
  border: none;
39
39
  background: none;
40
40
  cursor: pointer;
41
+ transition: all 0.2s ease;
42
+ padding: 4px;
43
+ border-radius: 50%;
44
+ }
45
+
46
+ .botonDeNavegacion:hover {
47
+ background-color: rgba(25, 118, 210, 0.1);
48
+ transform: scale(1.1);
49
+ color: #0b4fce;
41
50
  }
42
51
 
43
52
  /* Contenedor */
@@ -107,27 +116,24 @@
107
116
 
108
117
  .pie-col {
109
118
  display: flex;
110
- gap: 10px;
119
+ gap: 5px;
111
120
  align-items: center;
112
121
  flex: 1;
113
- /* Cada columna ocupará el mismo espacio */
114
122
  overflow: hidden;
115
- /* Evita que el contenido desborde el tercio asignado */
116
123
  }
117
124
 
118
125
  .pie-col.izquierda {
119
126
  justify-content: flex-start;
120
- direction: ltr;
121
127
  }
122
128
 
123
129
  .pie-col.centro {
124
130
  justify-content: center;
131
+ flex: 0 1 auto;
132
+ min-width: 0;
125
133
  }
126
134
 
127
135
  .pie-col.derecha {
128
136
  justify-content: flex-end;
129
- flex-direction: row-reverse;
130
- direction: rtl;
131
137
  }
132
138
 
133
139
  /* Página */
@@ -225,3 +231,43 @@
225
231
  opacity: 1;
226
232
  }
227
233
  }
234
+
235
+ /* Estilos para el mat-menu de aplicaciones */
236
+ ::ng-deep .mat-mdc-menu-panel {
237
+ border-radius: 12px !important;
238
+ border: 1px solid rgba(0, 47, 107, 0.1) !important;
239
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15) !important;
240
+ margin-top: 8px !important;
241
+ background-color: white !important;
242
+ min-width: 200px !important;
243
+ }
244
+
245
+ ::ng-deep .mat-mdc-menu-item {
246
+ height: 48px !important;
247
+ padding: 0 20px !important;
248
+ transition: background-color 0.2s ease !important;
249
+ }
250
+
251
+ ::ng-deep .mat-mdc-menu-item:hover {
252
+ background-color: #f0f7ff !important;
253
+ }
254
+
255
+ ::ng-deep .mat-mdc-menu-item .mat-icon {
256
+ color: #002f6b !important;
257
+ margin-right: 12px !important;
258
+ font-size: 22px !important;
259
+ width: 22px !important;
260
+ height: 22px !important;
261
+ }
262
+
263
+ ::ng-deep .mat-mdc-menu-item span {
264
+ font-size: 14px !important;
265
+ font-weight: 500 !important;
266
+ color: #444 !important;
267
+ margin-left: 0 !important;
268
+ /* Material sometimes adds weird margins */
269
+ }
270
+
271
+ ::ng-deep .mat-mdc-menu-item:hover span {
272
+ color: #0b4fce !important;
273
+ }
@@ -7,14 +7,14 @@
7
7
  </div>
8
8
  <div class="pie-col derecha">
9
9
  @if(TienePermiso) {
10
- <button class="botonDeNavegacion" matTooltip="Salir" mat-button (click)="Salir()">
10
+ <button class="botonDeNavegacion" matTooltip="Salir" (click)="Salir()">
11
11
  <mat-icon>logout</mat-icon>
12
12
  </button>
13
- <button class="botonDeNavegacion" matTooltip="Perfil" mat-button (click)="irAPerfil()">
13
+ <button class="botonDeNavegacion" matTooltip="Perfil" (click)="irAPerfil()">
14
14
  <mat-icon>person</mat-icon>
15
15
  </button>
16
16
  } @else {
17
- <button class="botonDeNavegacion" matTooltip="Entrar" mat-button (click)="Entrar()">
17
+ <button class="botonDeNavegacion" matTooltip="Entrar" (click)="Entrar()">
18
18
  <mat-icon>login</mat-icon>
19
19
  </button>
20
20
  }
@@ -28,16 +28,16 @@
28
28
  <div [ngClass]="claseDelContenedor">
29
29
  @if(TienePermiso) {
30
30
  <div class="botonesDeNavegacion">
31
- <button class="botonDeNavegacion" matTooltip="Ir atrás" mat-button (click)="irAtras()">
31
+ <button class="botonDeNavegacion" matTooltip="Ir atrás" (click)="irAtras()">
32
32
  <mat-icon>arrow_back</mat-icon>
33
33
  </button>
34
- <button class="botonDeNavegacion" matTooltip="Ir al menú del módulo" mat-button (click)="irAlMenuDeModulo()">
34
+ <button class="botonDeNavegacion" matTooltip="Ir al menú del módulo" (click)="irAlMenuDeModulo()">
35
35
  <mat-icon>menu</mat-icon>
36
36
  </button>
37
- <!-- <button class="botonDeNavegacion" matTooltip="Ir al móudulo padre" mat-button (click)="irAlModuloPadre()">
37
+ <!-- <button class="botonDeNavegacion" matTooltip="Ir al móudulo padre" (click)="irAlModuloPadre()">
38
38
  <mat-icon>apps</mat-icon>
39
39
  </button> -->
40
- <button class="botonDeNavegacion" matTooltip="Ir al inicio" mat-button (click)="irAlInicio()">
40
+ <button class="botonDeNavegacion" matTooltip="Ir al inicio" (click)="irAlInicio()">
41
41
  <mat-icon>home</mat-icon>
42
42
  </button>
43
43
  </div>
@@ -79,8 +79,25 @@
79
79
  </div>
80
80
  </div>
81
81
  <div class="pie-col derecha">
82
+ <button class="botonDeNavegacion" matTooltip="Aplicaciones" [matMenuTriggerFor]="menuAplicaciones">
83
+ <mat-icon>keyboard_arrow_up</mat-icon>
84
+ </button>
85
+
86
+ <mat-menu #menuAplicaciones="matMenu">
87
+ <button mat-menu-item (click)="irASugerencias()">
88
+ <mat-icon>lightbulb</mat-icon>
89
+ <span>Sugerencias</span>
90
+ </button>
91
+ @if(TienePermiso) {
92
+ <button mat-menu-item (click)="irAActividad()">
93
+ <mat-icon>history</mat-icon>
94
+ <span>Actividad de la cuenta</span>
95
+ </button>
96
+ }
97
+ </mat-menu>
98
+
82
99
  @if(TienePermiso) {
83
- <button class="botonDeNavegacion" matTooltip="Mensajes" mat-button (click)="irAMensajes()">
100
+ <button class="botonDeNavegacion" matTooltip="Mensajes" (click)="irAMensajes()">
84
101
  @if (Mensajes.length > 0) {
85
102
  <mat-icon>mark_email_unread</mat-icon>
86
103
  } @else {
@@ -89,21 +106,18 @@
89
106
  </button>
90
107
  }
91
108
  @if(EnlaceDelVideo !== '-') {
92
- <button class="botonDeNavegacion" matTooltip="Vídeo" mat-button (click)="irAVideo()">
109
+ <button class="botonDeNavegacion" matTooltip="Vídeo" (click)="irAVideo()">
93
110
  <mat-icon>live_tv</mat-icon>
94
111
  </button>
95
112
  }
96
113
  @if(EnlaceDelManual !== '-') {
97
- <button class="botonDeNavegacion" matTooltip="Ayuda" mat-button (click)="irAAyuda()">
114
+ <button class="botonDeNavegacion" matTooltip="Ayuda" (click)="irAAyuda()">
98
115
  <mat-icon>help</mat-icon>
99
116
  </button>
100
117
  }
101
- <button class="botonDeNavegacion" matTooltip="Reporte" mat-button (click)="irASoporte()">
118
+ <button class="botonDeNavegacion" matTooltip="Reporte" (click)="irASoporte()">
102
119
  <mat-icon>support_agent</mat-icon>
103
120
  </button>
104
- <button class="botonDeNavegacion" matTooltip="Sugerencias" mat-button (click)="irASugerencias()">
105
- <mat-icon>lightbulb</mat-icon>
106
- </button>
107
121
  </div>
108
122
  </div>
109
123
  </div>
@@ -11,9 +11,11 @@ import { MensajesComponent } from '../../../Componentes/Nucleo/mensajes/mensajes
11
11
  import { MensajeConfirmacionHTMLComponent } from '../../../Componentes/Nucleo/mensaje-confirmacion-html/mensaje-confirmacion-html';
12
12
  import { ReporteDeSugerenciasComponent } from '../../../Componentes/Nucleo/reporte-de-sugerencias/reporte-de-sugerencias.component';
13
13
 
14
+ import { MatMenuModule } from '@angular/material/menu';
15
+
14
16
  @Component({
15
17
  selector: 'app-contenedor-componentes',
16
- imports: [RouterOutlet, MatIconModule, MatTooltipModule, CommonModule],
18
+ imports: [RouterOutlet, MatIconModule, MatTooltipModule, CommonModule, MatMenuModule],
17
19
  templateUrl: './contenedor-componentes.component.html',
18
20
  styleUrl: './contenedor-componentes.component.css'
19
21
  })
@@ -210,6 +212,11 @@ export class ContenedorComponentesComponent implements OnInit, OnDestroy {
210
212
  this.dialog.open(ReporteDeSugerenciasComponent);
211
213
  }
212
214
 
215
+ irAActividad(): void {
216
+ const url = new URL(window.location.href);
217
+ window.location.href = `${url.origin}/Actividad`;
218
+ }
219
+
213
220
  ngOnDestroy() {
214
221
  if (this.intervaloUsuarios) {
215
222
  clearInterval(this.intervaloUsuarios);
@@ -3,7 +3,6 @@ import { provideRouter } from '@angular/router';
3
3
 
4
4
  import { routes } from './app.routes';
5
5
  import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
6
- import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
7
6
  import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
8
7
  import { MatPaginatorIntl } from '@angular/material/paginator';
9
8
  import { PaginadorPersonalizado } from './Componentes/Nucleo/tabla/paginador-personalizado';
@@ -16,10 +15,8 @@ export const appConfig: ApplicationConfig = {
16
15
  { provide: MatPaginatorIntl, useClass: PaginadorPersonalizado },
17
16
  provideZoneChangeDetection({ eventCoalescing: true }),
18
17
  provideRouter(routes),
19
- provideClientHydration(withEventReplay()),
20
- provideAnimationsAsync(),
21
18
  provideHttpClient(withFetch()),
22
- provideClientHydration(),
19
+ provideClientHydration(withEventReplay()),
23
20
  provideCharts(withDefaultRegisterables()),
24
21
  ...AnalyticsModule.forRoot().providers!,
25
22
  provideHttpClient(withInterceptors([AuthInterceptor]))
@@ -1,4 +1,5 @@
1
1
  import { Routes } from '@angular/router';
2
+ import { GestionActividadComponent } from './Componentes/Nucleo/gestion-actividad/gestion-actividad.component';
2
3
  import { GestionTablaComponent } from './Paginas/gestion-tabla/gestion-tabla.component';
3
4
  import { ContenedorPrincipalComponent } from './Paginas/contenedor-principal/contenedor-principal.component';
4
5
  import { GestionTablaJefeComponent } from './Paginas/gestion-tabla-jefe/gestion-tabla-jefe.component';
@@ -7,6 +8,7 @@ import { GestionDeReportesComponent } from './Paginas/gestion-de-reportes/gestio
7
8
  import { GestionIframe1Component } from './Paginas/gestion-iframe1/gestion-iframe1.component';
8
9
 
9
10
  export const routes: Routes = [
11
+ { path: 'Actividad', component: GestionActividadComponent },
10
12
  { path: '', component: ContenedorPrincipalComponent },
11
13
  { path: 'tabla', component: GestionTablaComponent },
12
14
  { path: 'aprobaciones', component: GestionTablaJefeComponent},