utn-cli 2.0.78 → 2.0.80

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.78",
3
+ "version": "2.0.80",
4
4
  "description": "Herramienta CLI unificada para la gestión de plantillas en SIGU.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -11,7 +11,8 @@ app.use(express.json());
11
11
  app.use(helmet());
12
12
  app.use(async (solicitud, respuesta, next) => {
13
13
  let Usuario = null;
14
- if (solicitud.headers.authorization) {
14
+ // Si length es > a 43 es un JWT, de lo contario es un UUID
15
+ if (solicitud.headers.authorization.length > 43) {
15
16
  Usuario = await Miscelaneo.obtenerDatosDelUsuario(solicitud.headers.authorization) ?? null;
16
17
  } else {
17
18
  Usuario = solicitud.user ?? null;
@@ -160,10 +160,10 @@ class Miscelaneo {
160
160
  </div>`;
161
161
  }
162
162
 
163
- GenerarReporteHTMLRegistrosVerticales(ElementosParaLaTabla, ParametrosExcluidos = [], ParametrosExtra = []) {
164
- //Ejemplo ElementosParaLaTabla:
163
+ GenerarReporteHTMLRegistrosVerticales(ElementosParaLaTabla, ParametrosExcluidos = [], ParametrosExtra = [], MostrarEncabezado = true) {
164
+ // Ejemplo ElementosParaLaTabla:
165
165
  // [
166
- // {
166
+ // {
167
167
  // "Fecha del traslado": "16-03-2026",
168
168
  // "Justificacion": "Traslado por mantenimiento",
169
169
  // "Dependencia que entrega": "Juan Pérez",
@@ -192,16 +192,21 @@ class Miscelaneo {
192
192
  // Estado: "Excelente",
193
193
  // IdentificadorOrigen: 101,
194
194
  // IdentificadorDestino: 202
195
- // }
196
195
  // ]
197
- // Espera un Array
198
- //Ejemplo ParametrosExcluidos:
199
- //const ParametrosExcluidos = ['IdentificadorOrigen', 'Justificacion', 'IdentificadorDestino'];
200
- //Valores devueltos por el QUERY que no sean necesarios mostrar en la tabla.
196
+ // Espera un Array de objetos.
197
+ //
198
+ // Ejemplo ParametrosExcluidos:
199
+ // const ParametrosExcluidos = ['IdentificadorOrigen', 'Justificacion', 'IdentificadorDestino'];
200
+ // Valores devueltos por el QUERY que no sean necesarios mostrar en la tabla.
201
+ //
202
+ // Ejemplo ParametrosExtra:
203
+ // const ParametrosExtra = ['Toma física'];
204
+ // En caso de necesitar un espacio extra en la tabla que no se encuentra en la lista principal.
205
+ //
206
+ // Ejemplo MostrarEncabezado:
207
+ // MostrarEncabezado = true (Valor por defecto, muestra la barra azul "Registro N° 1")
208
+ // MostrarEncabezado = false (Oculta la barra azul, ideal para usar la función con un único registro)
201
209
 
202
- //Ejemplo ParametrosExtra:
203
- //const ParametrosExtra = ['Toma física'];
204
- //En caso de necesitar un espacio extra en la tabla que no se encuentra en la lista principal.
205
210
  if (!ElementosParaLaTabla?.length) return '<p>No hay datos para mostrar.</p>';
206
211
 
207
212
  const baseColumnas = Object.keys(ElementosParaLaTabla[0]).filter(col => !ParametrosExcluidos.includes(col));
@@ -221,12 +226,16 @@ class Miscelaneo {
221
226
  `;
222
227
  }).join('');
223
228
 
224
- htmlFinal += `
225
- <div style="margin-top: 20px; page-break-inside: avoid; border: 1px solid #ccc; border-radius: 8px; overflow: hidden;">
226
-
229
+ const encabezadoHTML = MostrarEncabezado ? `
227
230
  <div style="background-color: #002f6b; color: white; padding: 6px 10px; font-weight: bold; font-size: 14px;">
228
231
  Registro N° ${index + 1}
229
232
  </div>
233
+ ` : '';
234
+
235
+ htmlFinal += `
236
+ <div style="margin-top: 20px; page-break-inside: avoid; border: 1px solid #ccc; border-radius: 8px; overflow: hidden;">
237
+
238
+ ${encabezadoHTML}
230
239
 
231
240
  <table style="border: none; border-radius: 0; margin-top: 0;">
232
241
  <tbody>
@@ -0,0 +1,133 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ .contenedor {
8
+ width: 100%;
9
+ /* Ocupa todo el espacio que le dé el padre (cdkDrag) */
10
+ background: #ffffff 0% 0% no-repeat padding-box;
11
+ box-shadow: 0px 3px 6px #00000029;
12
+ border-radius: 10px;
13
+ opacity: 1;
14
+ padding: 16px;
15
+ cursor: pointer;
16
+ display: flex;
17
+ flex-direction: column;
18
+ height: 100%;
19
+ /* Asegura que todas las tarjetas tengan la misma altura si se desea */
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ /* Eliminamos los media queries antiguos que forzaban anchos en vw */
24
+
25
+ .contenedor .cabecera_contenedor {
26
+ display: flex;
27
+ flex-direction: row;
28
+ align-items: flex-start;
29
+ /* Alinea los elementos al inicio (arriba) */
30
+ justify-content: space-between;
31
+ /* Separa el texto del icono */
32
+ gap: 8px;
33
+ /* Espacio mínimo entre texto e icono */
34
+ }
35
+
36
+ .contenedor .cabecera_contenedor .icono {
37
+ background: #eef2ff 0% 0% no-repeat padding-box;
38
+ opacity: 1;
39
+ border-radius: 50%;
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ width: 48px;
44
+ height: 48px;
45
+ min-width: 48px;
46
+ /* Evita que flexbox lo encoja */
47
+ color: #1b3069;
48
+ font-size: x-small;
49
+ font-weight: bold;
50
+ }
51
+
52
+ .contenedor .cabecera_contenedor .icono .numero {
53
+ font-size: large;
54
+ }
55
+
56
+ .contenedor .cabecera {
57
+ flex: 1;
58
+ /* Permite que el texto ocupe todo el espacio sobrante */
59
+ min-width: 0;
60
+ /* Permite truncar texto si es muy largo en móviles */
61
+ }
62
+
63
+ .contenedor .cabecera .titulo {
64
+ text-align: left;
65
+ font-family: "Roboto";
66
+ font-size: large;
67
+ font-weight: 600;
68
+ letter-spacing: 0px;
69
+ color: #000000;
70
+ opacity: 1;
71
+ }
72
+
73
+ .contenedor .cabecera .descripcion {
74
+ text-align: left;
75
+ font-family: "Roboto";
76
+ font-size: small;
77
+ letter-spacing: 0px;
78
+ color: #000000;
79
+ opacity: 1;
80
+ }
81
+
82
+ .contenedor .contenido {
83
+ text-align: left;
84
+ font-family: "Roboto";
85
+ font-size: small;
86
+ letter-spacing: 0px;
87
+ color: #000000;
88
+ opacity: 1;
89
+ margin-left: 3%;
90
+ margin-top: 5%;
91
+ }
92
+
93
+ .contenedor .cantidad {
94
+ min-height: 20px;
95
+ width: 100%;
96
+ justify-content: center;
97
+ font-family: "Roboto";
98
+ font-size: large;
99
+ font-weight: 600;
100
+ letter-spacing: 0px;
101
+ color: #000000;
102
+ opacity: 1;
103
+ }
104
+
105
+ .contenedor .izquierda {
106
+ margin-top: 20%;
107
+ align-items: start;
108
+ color: #1b3069;
109
+ font-weight: 700;
110
+ }
111
+
112
+ .contenedor .pie {
113
+ border-top: 2px solid #eef2ff;
114
+ opacity: 1;
115
+ padding: 5%;
116
+ position: relative;
117
+ }
118
+
119
+ .contenedor .linea {
120
+ width: 0%;
121
+ border: 1px solid #95d03a;
122
+ opacity: 1;
123
+ }
124
+
125
+ .contenedor .pie p {
126
+ text-align: left;
127
+ font: normal normal normal 15px/20px Roboto;
128
+ letter-spacing: 0px;
129
+ color: #1b3069;
130
+ opacity: 1;
131
+ font-size: small;
132
+ text-decoration: none;
133
+ }
@@ -0,0 +1,35 @@
1
+ <div class="contenedor" (click)="onClick()" [style.border-left]="ColorDeBorde ? '10px solid ' + ColorDeBorde : ''">
2
+ <div class="cabecera_contenedor">
3
+ <div class="cabecera">
4
+ <p class="titulo">
5
+ {{ titulo }}
6
+ </p>
7
+ <p class="descripcion">
8
+ {{ descripcion }}
9
+ </p>
10
+ </div>
11
+ <div class="icono">
12
+ @if(icono){
13
+ <mat-icon [fontIcon]="icono"></mat-icon>
14
+ } @if(numero){
15
+ <p class="numero">{{ numero }}</p>
16
+ }
17
+ </div>
18
+ </div>
19
+ <div class="contenido">
20
+ @if(contenido.length>0){ @for(item of contenido[0];track item){
21
+ <p>{{ item }}</p>
22
+ }
23
+ <hr />
24
+ @for(item of contenido[1];track item){
25
+ <p>{{ item }}</p>
26
+ } }
27
+ </div>
28
+ <div [class]="cantidadLugar">
29
+ <p>{{ (cantidadAMostrar !== 0 ? cantidadAMostrar : '') }}</p>
30
+ </div>
31
+ <!-- <hr [id]="'porcentaje'+titulo" class="linea" [matTooltip]="'Cantidad: ' + cantidad" matTooltipPosition="above"/>
32
+ <div class="pie">
33
+ <p>{{Etiqueta}}</p>
34
+ </div> -->
35
+ </div>
@@ -0,0 +1,53 @@
1
+ import { AfterViewInit, Component, EventEmitter, Input, Output } from "@angular/core";
2
+ import { MatIconModule } from "@angular/material/icon";
3
+ import { MatCardModule } from "@angular/material/card";
4
+ import { MatTooltipModule } from "@angular/material/tooltip";
5
+ import { Router } from "@angular/router";
6
+
7
+ @Component({
8
+ selector: "app-tarjeta-personalizada",
9
+ standalone: true,
10
+ imports: [MatIconModule, MatCardModule, MatTooltipModule],
11
+ templateUrl: "./tarjeta-personalizada.component.html",
12
+ styleUrl: "./tarjeta-personalizada.component.css"
13
+ })
14
+ export class TarjetaPersonalizadaComponent implements AfterViewInit {
15
+ @Input() titulo: string = "";
16
+ @Input() descripcion: string = "";
17
+ @Input() icono: any = null;
18
+ @Input() numero: any = null;
19
+ @Input() contenido: any = [];
20
+ @Input() cantidad: number | undefined;
21
+ @Input() cantidadLugar: string = "cantidad";
22
+ @Input() rutaASeguir: string = '';
23
+ @Input() cantidadMaxima: number | undefined;
24
+ @Input() cantidadAMostrar: number = 0;
25
+ @Input() Etiqueta: string = "CONTINUAR";
26
+ @Input() ColorDeBorde: string = "";
27
+ @Output() etiquetaClick = new EventEmitter<void>();
28
+
29
+ constructor(private ruta: Router) { }
30
+
31
+ onClick() {
32
+ if(this.etiquetaClick!==undefined){
33
+ this.etiquetaClick.emit();
34
+ return;
35
+ }
36
+ this.ruta.navigate([this.rutaASeguir]);
37
+ }
38
+
39
+ ngOnInit() {
40
+ this.cantidadAMostrar = (this.cantidadMaxima ? this.cantidadMaxima : 0) - (this.cantidad ? this.cantidad : 0);
41
+ }
42
+
43
+ ngAfterViewInit(): void {
44
+ if (this.cantidad) {
45
+ let porcentaje = 100;
46
+ if (this.cantidadMaxima) {
47
+ porcentaje = (this.cantidad / this.cantidadMaxima) * 100;
48
+ }
49
+ const linea = document.getElementById('porcentaje' + this.titulo) as HTMLHRElement;
50
+ linea.style.width = `${porcentaje}%`;
51
+ }
52
+ }
53
+ }
@@ -1,35 +1,31 @@
1
1
  <div class="contenido" cdkDropList cdkDropListOrientation="mixed" (cdkDropListDropped)="drop($event)">
2
2
  @for (tarjeta of tarjetas; track tarjeta.titulo) {
3
- <div cdkDrag>
4
- @switch (tarjeta.type) {
5
- @case ('single') {
6
- <app-tarjeta
7
- [rutaASeguir]="tarjeta.rutaASeguir"
8
- [titulo]="tarjeta.titulo"
9
- [descripcion]="tarjeta.descripcion"
10
- [icono]="tarjeta.icono"
11
- [ColorDeBorde]="tarjeta.ColorDeBorde ? tarjeta.ColorDeBorde : ''">
12
- </app-tarjeta>
13
- }
14
- @case ('multiple') {
15
- <app-tarjeta-multiple
16
- [titulo]="tarjeta.titulo"
17
- [descripcion]="tarjeta.descripcion"
18
- [rutas]="tarjeta.rutas"
19
- [ColorDeBorde]="tarjeta.ColorDeBorde ? tarjeta.ColorDeBorde : ''">
20
- </app-tarjeta-multiple>
21
- }
22
- @case ('report') {
23
- <app-tarjeta-reporte
24
- [reporteAGenerar]="tarjeta.reporteAGenerar"
25
- [titulo]="tarjeta.titulo"
26
- [descripcion]="tarjeta.descripcion"
27
- [icono]="tarjeta.icono"
28
- [ColorDeBorde]="tarjeta.ColorDeBorde ? tarjeta.ColorDeBorde : ''"
29
- (GenerarReporte)="GenerarReporte($event)">
30
- </app-tarjeta-reporte>
31
- }
32
- }
33
- </div>
3
+ <div cdkDrag>
4
+ @switch (tarjeta.type) {
5
+ @case ('single') {
6
+ <app-tarjeta [rutaASeguir]="tarjeta.rutaASeguir" [titulo]="tarjeta.titulo" [descripcion]="tarjeta.descripcion"
7
+ [icono]="tarjeta.icono" [ColorDeBorde]="tarjeta.ColorDeBorde ? tarjeta.ColorDeBorde : ''"
8
+ [cantidad]="tarjeta.cantidad ?? 0" [cantidadMaxima]="tarjeta.cantidadMaxima ?? 0">>
9
+ </app-tarjeta>
10
+ }
11
+ @case ('multiple') {
12
+ <app-tarjeta-multiple [titulo]="tarjeta.titulo" [descripcion]="tarjeta.descripcion" [rutas]="tarjeta.rutas"
13
+ [ColorDeBorde]="tarjeta.ColorDeBorde ? tarjeta.ColorDeBorde : ''">
14
+ </app-tarjeta-multiple>
15
+ }
16
+ @case ('report') {
17
+ <app-tarjeta-reporte [reporteAGenerar]="tarjeta.reporteAGenerar" [titulo]="tarjeta.titulo"
18
+ [descripcion]="tarjeta.descripcion" [icono]="tarjeta.icono"
19
+ [ColorDeBorde]="tarjeta.ColorDeBorde ? tarjeta.ColorDeBorde : ''" (GenerarReporte)="GenerarReporte($event)">
20
+ </app-tarjeta-reporte>
21
+ }
22
+ @case ('custom') {
23
+ <app-tarjeta-personalizada [titulo]="tarjeta.titulo" [descripcion]="tarjeta.descripcion" [icono]="tarjeta.icono"
24
+ [Etiqueta]="tarjeta.etiqueta" [ColorDeBorde]="tarjeta.ColorDeBorde ? tarjeta.ColorDeBorde : ''"
25
+ (etiquetaClick)="EjecutarAccionPersonalizada(tarjeta.accion)">
26
+ </app-tarjeta-personalizada>
27
+ }
28
+ }
29
+ </div>
34
30
  }
35
31
  </div>
@@ -1,18 +1,19 @@
1
1
  import { Component, OnInit } from "@angular/core";
2
- import { CommonModule } from "@angular/common";
2
+ import { CommonModule } from "@angular/common";
3
3
  import { TarjetaComponent } from "../../Componentes/Nucleo/tarjeta/tarjeta.component";
4
4
  import { TarjetaMultipleComponent } from "../../Componentes/Nucleo/tarjeta-multiple/tarjeta-multiple.component";
5
5
  import { TarjetaReporteComponent } from "../../Componentes/Nucleo/tarjeta-reporte/tarjeta-reporte.component";
6
+ import { TarjetaPersonalizadaComponent } from "../../Componentes/Nucleo/tarjeta-personalizada/tarjeta-personalizada.component";
6
7
  import { HttpClient } from "@angular/common/http";
7
8
  import { DatosGlobalesService } from '../../datos-globales.service';
8
9
  import { DragDropModule, CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
9
10
 
10
11
  interface BaseTarjetaConfig {
11
- type: 'single' | 'multiple' | 'report';
12
+ type: 'single' | 'multiple' | 'report' | 'custom';
12
13
  position: number;
13
14
  titulo: string;
14
15
  descripcion: string;
15
- acceso?: boolean;
16
+ acceso?: boolean;
16
17
  ColorDeBorde?: string;
17
18
  }
18
19
 
@@ -20,6 +21,8 @@ interface TarjetaConfig extends BaseTarjetaConfig {
20
21
  type: 'single';
21
22
  rutaASeguir: string;
22
23
  icono: string;
24
+ cantidad?: number;
25
+ cantidadMaxima?: number;
23
26
  }
24
27
 
25
28
  interface RutaMenuItem {
@@ -38,12 +41,19 @@ interface TarjetaReporteConfig extends BaseTarjetaConfig {
38
41
  icono: string;
39
42
  }
40
43
 
41
- type AnyTarjetaConfig = TarjetaConfig | TarjetaMultipleConfig | TarjetaReporteConfig;
44
+ interface TarjetaPersonalizadaConfig extends BaseTarjetaConfig {
45
+ type: 'custom';
46
+ icono: string;
47
+ etiqueta: string;
48
+ accion: string;
49
+ }
50
+
51
+ type AnyTarjetaConfig = TarjetaConfig | TarjetaMultipleConfig | TarjetaReporteConfig | TarjetaPersonalizadaConfig;
42
52
 
43
53
  @Component({
44
54
  selector: "app-contenedor-principal",
45
55
  standalone: true,
46
- imports: [TarjetaComponent, TarjetaMultipleComponent, TarjetaReporteComponent, CommonModule, DragDropModule],
56
+ imports: [TarjetaComponent, TarjetaMultipleComponent, TarjetaReporteComponent, TarjetaPersonalizadaComponent, CommonModule, DragDropModule],
47
57
  templateUrl: "./contenedor-principal.component.html",
48
58
  styleUrl: "./contenedor-principal.component.css"
49
59
  })
@@ -185,53 +195,52 @@ export class ContenedorPrincipalComponent implements OnInit {
185
195
  }
186
196
  ];
187
197
 
188
- // 2. Check for extra permissions (Personas card)
189
- this.http.get(`${this.datosGlobalesService.ObtenerURL()}Personas/PermisoExtra`).subscribe({
190
- next: (datos: any) => {
191
- if (datos.body) {
192
- const personasCard: TarjetaMultipleConfig = {
193
- type: 'multiple',
194
- position: 25,
195
- titulo: 'Personas',
196
- descripcion: 'Módulo para el mantenimiento de los datos relacionados con personas',
197
- rutas: [
198
- { nombre: 'Personas', ruta: 'tablaPersonas' },
199
- { nombre: 'Correos', ruta: 'tablaCorreosPersonas' },
200
- { nombre: 'Cuentas bancarias', ruta: 'tablaCuentasPersonas' },
201
- { nombre: 'Teléfonos', ruta: 'tablaTelefonosPersonas' },
202
- { nombre: 'Ubicación', ruta: 'tablaUbicacionPersona' },
203
- ]
204
- };
205
- baseTarjetas.push(personasCard);
206
- }
207
-
208
- // 3. Load user-specific positions from backend
209
- this.http.get(`${this.datosGlobalesService.ObtenerURL()}ConfiguracionDeTarjetas/obtener`).subscribe({
210
- next: (res: any) => {
211
- if (res.body && res.body.length > 0) {
212
- const savedConfigs = res.body;
213
- baseTarjetas.forEach(tarjeta => {
214
- const config = savedConfigs.find((c: any) => c.Titulo === tarjeta.titulo);
215
- if (config) {
216
- tarjeta.position = config.Posicion;
217
- if (config.ColorDeBorde) {
218
- tarjeta.ColorDeBorde = config.ColorDeBorde;
219
- }
220
- }
221
- });
198
+ // // 2. Check for extra permissions (Personas card)
199
+ // this.http.get(`${this.datosGlobalesService.ObtenerURL()}Personas/PermisoExtra`).subscribe({
200
+ // next: (datos: any) => {
201
+ // if (datos.body) {
202
+ // const personasCard: TarjetaMultipleConfig = {
203
+ // type: 'multiple',
204
+ // position: 25,
205
+ // titulo: 'Personas',
206
+ // descripcion: 'Módulo para el mantenimiento de los datos relacionados con personas',
207
+ // rutas: [
208
+ // { nombre: 'Personas', ruta: 'tablaPersonas' },
209
+ // { nombre: 'Correos', ruta: 'tablaCorreosPersonas' },
210
+ // { nombre: 'Cuentas bancarias', ruta: 'tablaCuentasPersonas' },
211
+ // { nombre: 'Teléfonos', ruta: 'tablaTelefonosPersonas' },
212
+ // { nombre: 'Ubicación', ruta: 'tablaUbicacionPersona' },
213
+ // ]
214
+ // };
215
+ // baseTarjetas.push(personasCard);
216
+ // }
217
+ // },
218
+ // error: (error) => {
219
+ // console.error('Problemas al intentar obtener los datos: ', error);
220
+ // this.tarjetas = baseTarjetas.sort((a, b) => a.position - b.position);
221
+ // },
222
+ // });
223
+ // 3. Load user-specific positions from backend
224
+ this.http.get(`${this.datosGlobalesService.ObtenerURL()}ConfiguracionDeTarjetas/obtener`).subscribe({
225
+ next: (res: any) => {
226
+ if (res.body && res.body.length > 0) {
227
+ const savedConfigs = res.body;
228
+ baseTarjetas.forEach(tarjeta => {
229
+ const config = savedConfigs.find((c: any) => c.Titulo === tarjeta.titulo);
230
+ if (config) {
231
+ tarjeta.position = config.Posicion;
232
+ if (config.ColorDeBorde) {
233
+ tarjeta.ColorDeBorde = config.ColorDeBorde;
234
+ }
222
235
  }
223
- this.tarjetas = baseTarjetas.sort((a, b) => a.position - b.position);
224
- },
225
- error: (error) => {
226
- console.error('Error al obtener configuración de tarjetas:', error);
227
- this.tarjetas = baseTarjetas.sort((a, b) => a.position - b.position);
228
- }
229
- });
236
+ });
237
+ }
238
+ this.tarjetas = baseTarjetas.sort((a, b) => a.position - b.position);
230
239
  },
231
240
  error: (error) => {
232
- console.error('Problemas al intentar obtener los datos: ', error);
241
+ console.error('Error al obtener configuración de tarjetas:', error);
233
242
  this.tarjetas = baseTarjetas.sort((a, b) => a.position - b.position);
234
- },
243
+ }
235
244
  });
236
245
  }
237
246