utn-cli 2.0.7 → 2.0.9

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.
@@ -159,6 +159,8 @@ export async function updateBackend() {
159
159
 
160
160
  copiarDirectorios(directoriodePlantillas, directorioDestino, archivosAExcluir);
161
161
  mostrarVersion(path.join(__dirname, '../package.json')); // Show main CLI version
162
+ fs.renameSync('gitignore', '.gitignore');
163
+ await inicializarProyectoBackend();
162
164
  console.log('Proyecto de backend actualizado exitosamente.');
163
165
  closeReadLine();
164
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utn-cli",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "description": "Herramienta CLI unificada para la gestión de plantillas en SIGU.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,12 +1,26 @@
1
- .contenedor {
1
+ .contenedor { /* This is the outermost div in graficos.component.html */
2
2
  display: flex;
3
3
  flex-wrap: wrap;
4
- gap: 2rem;
4
+ gap: 1rem;
5
5
  justify-content: center;
6
+ width: 100%;
7
+ /* Remove height: 100% from here */
6
8
  }
7
9
 
8
- .grafico {
9
- width: 400px;
10
+ .grafico { /* This is the div containing the h3 and canvas */
11
+ position: relative;
12
+ width: 100%; /* Take 100% of parent's width */
13
+ height: 100%; /* Allow height to stretch based on parent */
14
+ max-height: 50vh; /* Further reduce max-height to 50% of viewport height for increased vertical responsiveness */
15
+ padding: 0.5rem;
16
+ box-sizing: border-box;
17
+ overflow: hidden; /* Prevent content from spilling outside */
18
+ }
19
+
20
+ .grafico canvas {
21
+ width: 100% !important;
22
+ height: 100% !important;
23
+ display: block; /* Remove any default inline spacing */
10
24
  }
11
25
 
12
26
  h3 {
@@ -1,25 +1,45 @@
1
- <div class="contenedor">
2
- @if(Datos && TipoDeGrafico==='Barras') {
3
- <!-- Gráfico de Barras -->
4
- <div class="grafico" [ngStyle]="{ width: Ancho, height: Alto }">
5
- <h3>{{Titulo}}</h3>
6
- <canvas baseChart [data]="barChartData" [options]="barChartOptions" [type]="'bar'"></canvas>
7
- </div>
8
- }
9
-
10
- @if(Datos && TipoDeGrafico==='Pie') {
11
- <!-- Gráfico de Pie -->
12
- <div class="grafico" [ngStyle]="{ width: Ancho, height: Alto }">
13
- <h3>{{Titulo}}</h3>
14
- <canvas baseChart [data]="pieChartData" [options]="pieChartOptions" [type]="'pie'"></canvas>
15
- </div>
16
- }
17
-
18
- @if(Datos && TipoDeGrafico==='Línea') {
19
- <!-- Gráfico de Línea -->
20
- <div class="grafico" [ngStyle]="{ width: Ancho, height: Alto }">
21
- <h3>{{Titulo}}</h3>
22
- <canvas baseChart [data]="lineChartData" [options]="lineChartOptions" [type]="'line'"></canvas>
23
- </div>
24
- }
25
- </div>
1
+ <div class="contenedor">
2
+
3
+ @if(sinDatosReales) {
4
+ <div style="text-align:center; padding: 20px;">
5
+ <p><b>No se encontraron datos</b></p>
6
+ </div>
7
+ } @else {
8
+
9
+ @if(Datos && TipoDeGrafico==='Barras') {
10
+ <div class="grafico">
11
+ <h3>{{Titulo}}</h3>
12
+ <canvas baseChart
13
+ [data]="barChartData"
14
+ [options]="barChartOptions"
15
+ [type]="'bar'"
16
+ [plugins]="[pluginMostrarValores]">
17
+ </canvas>
18
+ </div>
19
+ }
20
+
21
+ @if(Datos && TipoDeGrafico==='Pie') {
22
+ <div class="grafico">
23
+ <h3>{{Titulo}}</h3>
24
+ <canvas baseChart
25
+ [data]="pieChartData"
26
+ [options]="pieChartOptions"
27
+ [type]="'pie'">
28
+ </canvas>
29
+ </div>
30
+ }
31
+
32
+ @if(Datos && TipoDeGrafico==='Línea') {
33
+ <div class="grafico">
34
+ <h3>{{Titulo}}</h3>
35
+ <canvas baseChart
36
+ [data]="lineChartData"
37
+ [options]="lineChartOptions"
38
+ [type]="'line'">
39
+ </canvas>
40
+ </div>
41
+ }
42
+
43
+ }
44
+
45
+ </div>
@@ -1,80 +1,252 @@
1
- import { Component, Input } from '@angular/core';
2
- import { BaseChartDirective } from 'ng2-charts';
3
- import { ChartConfiguration, ChartData } from 'chart.js';
4
- import { NgStyle } from '@angular/common';
5
-
6
- @Component({
7
- selector: 'app-graficos',
8
- standalone: true,
9
- imports: [BaseChartDirective, NgStyle],
10
- templateUrl: './graficos.component.html',
11
- styleUrl: './graficos.component.css'
12
- })
13
- export class GraficosComponent {
14
-
15
- @Input() public TipoDeGrafico: string = '';
16
- @Input() public Datos: [{}] = [{}];
17
- @Input() public Titulo: string = '';
18
- @Input() public Ancho: string = '400px';
19
- @Input() public Alto: string = '300px';
20
-
21
- // === Gráfico de Barras ===
22
- public barChartOptions: ChartConfiguration<'bar'>['options'] = { responsive: true, };
23
- public barChartData: ChartData<'bar'> = { labels: [], datasets: [] };
24
-
25
- // === Gráfico de Pie ===
26
- public pieChartOptions: ChartConfiguration<'pie'>['options'] = { responsive: true, };
27
- public pieChartData: ChartData<'pie', number[], string | string[]> = { labels: [], datasets: [] };
28
-
29
- // === Gráfico de Línea ===
30
- public lineChartOptions: ChartConfiguration<'line'>['options'] = { responsive: true, };
31
- public lineChartData: ChartData<'line'> = {
32
- labels: [],
33
- datasets: [
34
- {
35
- data: [65, 59, 80, 81, 56],
36
- label: 'Tendencia de Ventas',
37
- fill: true,
38
- tension: 0.4, // suaviza la línea
39
- borderColor: 'blue',
40
- backgroundColor: 'rgba(30,144,255,0.2)'
41
- },
42
- {
43
- data: [28, 48, 40, 19, 86],
44
- label: 'Tendencia de Compras',
45
- fill: true,
46
- tension: 0.4,
47
- borderColor: 'green',
48
- backgroundColor: 'rgba(34,139,34,0.2)'
49
- }
50
- ]
51
- };
52
-
53
- constructor() { }
54
-
55
- ngOnChanges() {
56
- if (this.TipoDeGrafico === 'Pie') {
57
- const Etiquetas: string[] = [];
58
- const Datos: number[] = [];
59
- this.Datos.forEach((elemento: any) => {
60
- Etiquetas.push(elemento.Etiqueta);
61
- Datos.push(elemento.Total);
62
- });
63
- this.pieChartData.datasets = [{ data: Datos }];
64
- this.pieChartData.labels = Etiquetas;
65
- }
66
- if (this.TipoDeGrafico === 'Barras') {
67
- const ejesHorizontales = [...new Set(this.Datos.map((item: any) => item.EjeHorizontal))];
68
- const etiquetas = [...new Set(this.Datos.map((item: any) => item.Etiqueta))];
69
- const datasets = etiquetas.map(etiqueta => ({
70
- label: etiqueta,
71
- data: ejesHorizontales.map(mes => {
72
- const registro: any = this.Datos.find((item: any) => item.EjeHorizontal === mes && item.Etiqueta === etiqueta);
73
- return registro ? registro.Total : 0;
74
- })
75
- }));
76
- this.barChartData.labels = ejesHorizontales;
77
- this.barChartData.datasets = datasets;
78
- }
79
- }
80
- }
1
+ import { Component, Input } from '@angular/core';
2
+ import { BaseChartDirective } from 'ng2-charts';
3
+ import { ChartConfiguration, ChartData, Plugin } from 'chart.js';
4
+
5
+ @Component({
6
+ selector: 'app-graficos',
7
+ standalone: true,
8
+ imports: [BaseChartDirective],
9
+ templateUrl: './graficos.component.html',
10
+ styleUrl: './graficos.component.css'
11
+ })
12
+ export class GraficosComponent {
13
+
14
+ @Input() public TipoDeGrafico: string = '';
15
+ @Input() public Datos: any[] = [];
16
+ @Input() public Titulo: string = '';
17
+ @Input() public Ancho: string = '400px';
18
+ @Input() public Alto: string = '300px';
19
+
20
+ // =========================
21
+ // ESTILO UNIFICADO (GLOBAL)
22
+ // =========================
23
+ private readonly GROSOR_LINEA: number = 4;
24
+ private readonly RADIO_PUNTO: number = 4;
25
+ private readonly RADIO_PUNTO_HOVER: number = 6;
26
+
27
+ private readonly BORDE_BARRA: number = 2;
28
+ private readonly BORDE_PIE: number = 2;
29
+
30
+ // =========================
31
+ // VALIDACIÓN DE DATOS VACÍOS
32
+ // =========================
33
+ get sinDatos(): boolean {
34
+ return !this.Datos || this.Datos.length === 0;
35
+ }
36
+
37
+ get sinDatosReales(): boolean {
38
+ if (!this.Datos || this.Datos.length === 0) return true;
39
+
40
+ return this.Datos.every((x: any) => {
41
+ const valor = Number(x.Total);
42
+ return isNaN(valor) || valor === 0;
43
+ });
44
+ }
45
+
46
+ // =========================
47
+ // CONFIGURACIÓN BARRAS
48
+ // =========================
49
+ public barChartOptions: ChartConfiguration<'bar'>['options'] = {
50
+ responsive: true,
51
+ maintainAspectRatio: false,
52
+ layout: {
53
+ padding: {
54
+ bottom: 20
55
+ }
56
+ },
57
+ scales: {
58
+ x: {
59
+ ticks: {
60
+ autoSkip: false,
61
+ maxRotation: 45,
62
+ minRotation: 45,
63
+ align: 'start',
64
+ crossAlign: 'near',
65
+ },
66
+ afterFit: (axis) => {
67
+ axis.height = 80;
68
+ }
69
+ },
70
+ y: {
71
+ beginAtZero: true
72
+ }
73
+ }
74
+ };
75
+
76
+ public barChartData: ChartData<'bar'> = {
77
+ labels: [],
78
+ datasets: []
79
+ };
80
+
81
+ // =========================
82
+ // CONFIGURACIÓN PIE
83
+ // =========================
84
+ public pieChartOptions: ChartConfiguration<'pie'>['options'] = {
85
+ responsive: true,
86
+ maintainAspectRatio: false
87
+ };
88
+
89
+ public pieChartData: ChartData<'pie', number[], string | string[]> = {
90
+ labels: [],
91
+ datasets: []
92
+ };
93
+
94
+ // =========================
95
+ // CONFIGURACIÓN LÍNEA
96
+ // =========================
97
+ public lineChartOptions: ChartConfiguration<'line'>['options'] = {
98
+ responsive: true,
99
+ maintainAspectRatio: false,
100
+ layout: {
101
+ padding: {
102
+ bottom: 20
103
+ }
104
+ },
105
+ scales: {
106
+ x: {
107
+ ticks: {
108
+ autoSkip: false,
109
+ maxRotation: 45,
110
+ minRotation: 45,
111
+ align: 'start',
112
+ crossAlign: 'near',
113
+ },
114
+ afterFit: (axis) => {
115
+ axis.height = 80;
116
+ }
117
+ },
118
+ y: {
119
+ beginAtZero: true
120
+ }
121
+ }
122
+ };
123
+
124
+ public lineChartData: ChartData<'line'> = {
125
+ labels: [],
126
+ datasets: []
127
+ };
128
+
129
+ // =========================
130
+ // PLUGIN MOSTRAR VALORES (BARRAS)
131
+ // =========================
132
+ public pluginMostrarValores: Plugin<'bar'> = {
133
+ id: 'pluginMostrarValores',
134
+ afterDatasetsDraw: (chart) => {
135
+ const { ctx } = chart;
136
+
137
+ ctx.save();
138
+ ctx.font = 'bold 12px Arial';
139
+ ctx.fillStyle = '#000';
140
+ ctx.textAlign = 'center';
141
+
142
+ chart.data.datasets.forEach((dataset, datasetIndex) => {
143
+ const meta = chart.getDatasetMeta(datasetIndex);
144
+
145
+ meta.data.forEach((bar: any, index: number) => {
146
+ const valor = dataset.data[index];
147
+ if (valor === null || valor === undefined) return;
148
+
149
+ const x = bar.x;
150
+ const y = bar.y - 6;
151
+
152
+ ctx.fillText(String(valor), x, y);
153
+ });
154
+ });
155
+
156
+ ctx.restore();
157
+ }
158
+ };
159
+
160
+ constructor() { }
161
+
162
+ ngOnChanges() {
163
+
164
+ // ✅ Si no hay datos, limpia todo para evitar gráficos raros
165
+ if (this.sinDatosReales) {
166
+ this.barChartData = { labels: [], datasets: [] };
167
+ this.pieChartData = { labels: [], datasets: [] };
168
+ this.lineChartData = { labels: [], datasets: [] };
169
+ return;
170
+ }
171
+
172
+ // =========================
173
+ // PIE
174
+ // =========================
175
+ if (this.TipoDeGrafico === 'Pie') {
176
+
177
+ const Etiquetas: string[] = [];
178
+ const Datos: number[] = [];
179
+
180
+ this.Datos.forEach((elemento: any) => {
181
+ Etiquetas.push(elemento.Etiqueta);
182
+ Datos.push(Number(elemento.Total));
183
+ });
184
+
185
+ this.pieChartData.datasets = [
186
+ {
187
+ data: Datos,
188
+
189
+ // ✅ borde uniforme para que no se vea "flaco"
190
+ borderWidth: this.BORDE_PIE
191
+ }
192
+ ];
193
+
194
+ this.pieChartData.labels = Etiquetas;
195
+ }
196
+
197
+ // =========================
198
+ // BARRAS
199
+ // =========================
200
+ if (this.TipoDeGrafico === 'Barras') {
201
+
202
+ const ejesHorizontales = [...new Set(this.Datos.map((item: any) => item.EjeHorizontal))];
203
+ const etiquetas = [...new Set(this.Datos.map((item: any) => item.Etiqueta))];
204
+
205
+ const datasets = etiquetas.map(etiqueta => ({
206
+ label: etiqueta,
207
+ data: ejesHorizontales.map(eje => {
208
+ const registro: any = this.Datos.find((item: any) =>
209
+ item.EjeHorizontal === eje && item.Etiqueta === etiqueta
210
+ );
211
+ return registro ? Number(registro.Total) : 0;
212
+ }),
213
+
214
+ // ✅ borde uniforme
215
+ borderWidth: this.BORDE_BARRA
216
+ }));
217
+
218
+ this.barChartData.labels = ejesHorizontales;
219
+ this.barChartData.datasets = datasets;
220
+ }
221
+
222
+ // =========================
223
+ // LÍNEA
224
+ // =========================
225
+ if (this.TipoDeGrafico === 'Línea') {
226
+
227
+ const ejesHorizontales = [...new Set(this.Datos.map((item: any) => item.EjeHorizontal))];
228
+ const etiquetas = [...new Set(this.Datos.map((item: any) => item.Etiqueta))];
229
+
230
+ const datasets = etiquetas.map(etiqueta => ({
231
+ label: etiqueta,
232
+ data: ejesHorizontales.map(eje => {
233
+ const registro: any = this.Datos.find((item: any) =>
234
+ item.EjeHorizontal === eje && item.Etiqueta === etiqueta
235
+ );
236
+ return registro ? Number(registro.Total) : 0;
237
+ }),
238
+
239
+ // ✅ estilo uniforme con todo el sistema
240
+ borderWidth: this.GROSOR_LINEA,
241
+ pointRadius: this.RADIO_PUNTO,
242
+ pointHoverRadius: this.RADIO_PUNTO_HOVER,
243
+
244
+ fill: false,
245
+ tension: 0.35
246
+ }));
247
+
248
+ this.lineChartData.labels = ejesHorizontales;
249
+ this.lineChartData.datasets = datasets;
250
+ }
251
+ }
252
+ }
@@ -1,4 +1,4 @@
1
- <div class="contenedor" (click)="onClick()">
1
+ <div class="contenedor" (click)="onClick()" [style.border]="ColorDeBorde ? '1px solid ' + ColorDeBorde : ''">
2
2
  <div class="cabecera_contenedor">
3
3
  <div class="cabecera">
4
4
  <p class="titulo">
@@ -22,6 +22,7 @@ export class TarjetaComponent implements AfterViewInit {
22
22
  @Input() rutaASeguir: string = '';
23
23
  @Input() cantidadMaxima: number | undefined;
24
24
  @Input() cantidadAMostrar: number = 0;
25
+ @Input() ColorDeBorde: string = '';
25
26
 
26
27
  constructor(private ruta: Router) { }
27
28
 
@@ -24,7 +24,7 @@
24
24
  [mostrarElBotonDeDescargar] = "true"
25
25
  [mostrarElBotonDeFiltro] = "true"
26
26
  [mostrarPaginador] = "true"
27
- [estadosQuePermitenEdicion] = "accionesSiempreClickeable"
27
+ [estadosQuePermitenEdicion] = "estadosQuePermitenEdicion"
28
28
  [subEstadosQuePermitenEdicion] = "subEstadosQuePermitenEdicion"
29
29
  [paginarResultados] = "PaginarResultados"
30
30
  [TotalDeRegistros] = "TotalDeRegistros"
package/GEMINI.md DELETED
File without changes