utn-cli 2.1.1 → 2.1.3

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.
Files changed (35) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/commands/commit.js +14 -1
  3. package/commands/frontend.js +1 -1
  4. package/package.json +1 -1
  5. package/templates/backend/package.json +3 -3
  6. package/templates/backend/rutas/misc.js +69 -0
  7. package/templates/backend/servicios/Nucleo/Miscelaneas.js +188 -22
  8. package/templates/backend/servicios/Nucleo/ReportePDF.js +1 -1
  9. package/templates/bd/docker-scripts/1-crear estructura.sql +8 -0
  10. package/templates/frontend/package.json +9 -8
  11. package/templates/frontend/public/Manual.md +1 -0
  12. package/templates/frontend/src/app/Componentes/Nucleo/gestion-actividad/gestion-actividad.component.ts +11 -3
  13. package/templates/frontend/src/app/Componentes/Nucleo/graficos/graficos.component.ts +2 -4
  14. package/templates/frontend/src/app/Componentes/Nucleo/manual/manual.component.css +318 -0
  15. package/templates/frontend/src/app/Componentes/Nucleo/manual/manual.component.html +43 -0
  16. package/templates/frontend/src/app/Componentes/Nucleo/manual/manual.component.ts +77 -0
  17. package/templates/frontend/src/app/Componentes/Nucleo/mensajes/mensajes.component.ts +5 -3
  18. package/templates/frontend/src/app/Componentes/Nucleo/reporte-de-incidencias/reporte-de-incidencias.component.ts +12 -4
  19. package/templates/frontend/src/app/Componentes/Nucleo/reporte-de-sugerencias/reporte-de-sugerencias.component.ts +12 -3
  20. package/templates/frontend/src/app/Componentes/Nucleo/subir-archivo/subir-archivo.component.html +13 -10
  21. package/templates/frontend/src/app/Componentes/Nucleo/subir-archivo/subir-archivo.component.ts +18 -6
  22. package/templates/frontend/src/app/Componentes/Nucleo/tarjeta/tarjeta.component.html +2 -2
  23. package/templates/frontend/src/app/Componentes/Nucleo/tarjeta/tarjeta.component.ts +9 -13
  24. package/templates/frontend/src/app/Componentes/Nucleo/tarjeta-personalizada/tarjeta-personalizada.component.ts +7 -9
  25. package/templates/frontend/src/app/Componentes/Nucleo/tarjeta-reporte/tarjeta-reporte.component.ts +1 -6
  26. package/templates/frontend/src/app/Paginas/Nucleo/contenedor-componentes/contenedor-componentes.component.css +72 -0
  27. package/templates/frontend/src/app/Paginas/Nucleo/contenedor-componentes/contenedor-componentes.component.html +17 -4
  28. package/templates/frontend/src/app/Paginas/Nucleo/contenedor-componentes/contenedor-componentes.component.ts +45 -57
  29. package/templates/frontend/src/app/Paginas/contenedor-principal/contenedor-principal.component.css +11 -0
  30. package/templates/frontend/src/app/Paginas/contenedor-principal/contenedor-principal.component.html +4 -4
  31. package/templates/frontend/src/app/Paginas/contenedor-principal/contenedor-principal.component.ts +65 -69
  32. package/templates/frontend/src/app/Paginas/gestion-de-reportes/gestion-de-reportes.component.html +14 -3
  33. package/templates/frontend/src/app/Paginas/gestion-de-reportes/gestion-de-reportes.component.ts +52 -14
  34. package/templates/frontend/src/app/app.routes.ts +4 -0
  35. package/templates/frontend/src/app/datos-globales.service.ts +4 -1
@@ -1,4 +1,4 @@
1
- import { Component, Input } from '@angular/core';
1
+ import { Component, Input, OnChanges } from '@angular/core';
2
2
  import { BaseChartDirective } from 'ng2-charts';
3
3
  import { ChartConfiguration, ChartData, Plugin } from 'chart.js';
4
4
 
@@ -9,7 +9,7 @@ import { ChartConfiguration, ChartData, Plugin } from 'chart.js';
9
9
  templateUrl: './graficos.component.html',
10
10
  styleUrl: './graficos.component.css'
11
11
  })
12
- export class GraficosComponent {
12
+ export class GraficosComponent implements OnChanges {
13
13
 
14
14
  @Input() public TipoDeGrafico: string = '';
15
15
  @Input() public Datos: any[] = [];
@@ -162,8 +162,6 @@ export class GraficosComponent {
162
162
  }
163
163
  };
164
164
 
165
- constructor() { }
166
-
167
165
  ngOnChanges() {
168
166
 
169
167
  // ✅ Si no hay datos, limpia todo para evitar gráficos raros
@@ -0,0 +1,318 @@
1
+ /* ═══════════════════════════════════════════════════════════
2
+ Layout general — rompe el text-align:center del contenedor
3
+ ══════════════════════════════════════════════════════════ */
4
+ :host {
5
+ display: block;
6
+ text-align: left;
7
+ }
8
+
9
+ .manual-layout {
10
+ display: grid;
11
+ grid-template-columns: 260px 1fr;
12
+ gap: 0;
13
+ align-items: start;
14
+ min-height: 100%;
15
+ font-family: 'Roboto', 'Helvetica Neue', sans-serif;
16
+ }
17
+
18
+ /* ═══════════════════════════════════════════════════════════
19
+ Índice lateral
20
+ ══════════════════════════════════════════════════════════ */
21
+ .manual-toc {
22
+ position: sticky;
23
+ top: 0;
24
+ max-height: calc(100vh - 120px);
25
+ overflow-y: auto;
26
+ border-right: 2px solid #e0e8f5;
27
+ padding: 1.25rem 0.75rem 2rem 0;
28
+ background: #f7faff;
29
+ scrollbar-width: thin;
30
+ scrollbar-color: #b0c4de transparent;
31
+ }
32
+
33
+ .toc-titulo {
34
+ font-size: 0.7rem;
35
+ font-weight: 700;
36
+ letter-spacing: 0.1em;
37
+ text-transform: uppercase;
38
+ color: #002f6b;
39
+ padding: 0 0 0.6rem 1rem;
40
+ border-bottom: 1px solid #d0ddf0;
41
+ margin-bottom: 0.6rem;
42
+ }
43
+
44
+ .toc-lista {
45
+ list-style: none;
46
+ padding: 0;
47
+ margin: 0;
48
+ }
49
+
50
+ .toc-lista li {
51
+ margin: 0;
52
+ }
53
+
54
+ .toc-enlace {
55
+ display: block;
56
+ width: 100%;
57
+ background: none;
58
+ border: none;
59
+ cursor: pointer;
60
+ text-align: left;
61
+ font-family: inherit;
62
+ font-size: 0.8rem;
63
+ line-height: 1.4;
64
+ color: #444;
65
+ padding: 0.3rem 0.75rem;
66
+ border-radius: 0 6px 6px 0;
67
+ transition: background 0.15s, color 0.15s;
68
+ white-space: normal;
69
+ word-break: break-word;
70
+ }
71
+
72
+ .toc-enlace:hover {
73
+ background: #dce9ff;
74
+ color: #002f6b;
75
+ }
76
+
77
+ .toc-nivel-1 .toc-enlace {
78
+ font-weight: 700;
79
+ color: #002f6b;
80
+ font-size: 0.82rem;
81
+ padding-left: 1rem;
82
+ }
83
+
84
+ .toc-nivel-2 .toc-enlace {
85
+ font-weight: 600;
86
+ color: #1976d2;
87
+ padding-left: 1.5rem;
88
+ }
89
+
90
+ .toc-nivel-3 .toc-enlace {
91
+ font-weight: 400;
92
+ color: #555;
93
+ padding-left: 2.25rem;
94
+ font-size: 0.78rem;
95
+ }
96
+
97
+ /* ═══════════════════════════════════════════════════════════
98
+ Área principal del artículo
99
+ ══════════════════════════════════════════════════════════ */
100
+ .manual-main {
101
+ padding: 1.5rem 2.5rem 4rem 2rem;
102
+ min-width: 0;
103
+ }
104
+
105
+ /* ═══════════════════════════════════════════════════════════
106
+ Estados: cargando / error
107
+ ══════════════════════════════════════════════════════════ */
108
+ .manual-estado {
109
+ padding: 1.25rem 1.5rem;
110
+ border-radius: 8px;
111
+ background: #f0f4ff;
112
+ color: #002f6b;
113
+ font-size: 0.95rem;
114
+ border-left: 4px solid #1976d2;
115
+ }
116
+
117
+ .manual-error {
118
+ background: #fff0f0;
119
+ color: #b00020;
120
+ border-left-color: #b00020;
121
+ }
122
+
123
+ /* ═══════════════════════════════════════════════════════════
124
+ Tipografía del artículo
125
+ ══════════════════════════════════════════════════════════ */
126
+ .manual-articulo {
127
+ color: #1a1a1a;
128
+ font-size: 0.95rem;
129
+ line-height: 1.8;
130
+ }
131
+
132
+ /* Encabezados */
133
+ .manual-articulo h1 {
134
+ font-size: 1.75rem;
135
+ font-weight: 700;
136
+ color: #002f6b;
137
+ margin: 0 0 1.25rem;
138
+ padding-bottom: 0.5rem;
139
+ border-bottom: 3px solid #1976d2;
140
+ scroll-margin-top: 1rem;
141
+ }
142
+
143
+ .manual-articulo h2 {
144
+ font-size: 1.25rem;
145
+ font-weight: 700;
146
+ color: #002f6b;
147
+ margin: 2.5rem 0 0.75rem;
148
+ padding: 0.4rem 0.9rem;
149
+ background: #e8f0fe;
150
+ border-left: 4px solid #1976d2;
151
+ border-radius: 0 6px 6px 0;
152
+ scroll-margin-top: 1rem;
153
+ }
154
+
155
+ .manual-articulo h3 {
156
+ font-size: 1.05rem;
157
+ font-weight: 600;
158
+ color: #0b4fce;
159
+ margin: 1.75rem 0 0.5rem;
160
+ scroll-margin-top: 1rem;
161
+ }
162
+
163
+ .manual-articulo h4 {
164
+ font-size: 0.95rem;
165
+ font-weight: 600;
166
+ color: #333;
167
+ margin: 1.25rem 0 0.4rem;
168
+ scroll-margin-top: 1rem;
169
+ }
170
+
171
+ /* Párrafos */
172
+ .manual-articulo p {
173
+ margin: 0 0 0.9rem;
174
+ }
175
+
176
+ /* Listas */
177
+ .manual-articulo ul,
178
+ .manual-articulo ol {
179
+ margin: 0.25rem 0 1rem 1.4rem;
180
+ }
181
+
182
+ .manual-articulo li {
183
+ margin-bottom: 0.3rem;
184
+ }
185
+
186
+ .manual-articulo li p {
187
+ margin: 0;
188
+ }
189
+
190
+ /* Código inline */
191
+ .manual-articulo code {
192
+ background: #eef2ff;
193
+ color: #1a237e;
194
+ border-radius: 3px;
195
+ padding: 0.1em 0.45em;
196
+ font-family: 'Consolas', 'Courier New', monospace;
197
+ font-size: 0.87em;
198
+ }
199
+
200
+ /* Bloques de código */
201
+ .manual-articulo pre {
202
+ background: #f3f4f6;
203
+ border: 1px solid #dde1ea;
204
+ border-left: 4px solid #1976d2;
205
+ border-radius: 0 6px 6px 0;
206
+ padding: 1rem 1.25rem;
207
+ overflow-x: auto;
208
+ margin: 0.75rem 0 1.25rem;
209
+ }
210
+
211
+ .manual-articulo pre code {
212
+ background: none;
213
+ padding: 0;
214
+ color: #1a1a1a;
215
+ font-size: 0.88em;
216
+ line-height: 1.65;
217
+ }
218
+
219
+ /* Tablas */
220
+ .manual-articulo table {
221
+ width: 100%;
222
+ border-collapse: collapse;
223
+ margin: 0.75rem 0 1.5rem;
224
+ font-size: 0.9rem;
225
+ border: 1px solid #d0daf0;
226
+ border-radius: 6px;
227
+ overflow: hidden;
228
+ }
229
+
230
+ .manual-articulo thead {
231
+ background: #002f6b;
232
+ color: #fff;
233
+ }
234
+
235
+ .manual-articulo th {
236
+ text-align: left;
237
+ padding: 0.6rem 1rem;
238
+ font-weight: 600;
239
+ font-size: 0.88rem;
240
+ letter-spacing: 0.02em;
241
+ }
242
+
243
+ .manual-articulo td {
244
+ padding: 0.55rem 1rem;
245
+ border-top: 1px solid #e0e8f5;
246
+ vertical-align: top;
247
+ color: #222;
248
+ }
249
+
250
+ .manual-articulo tbody tr:nth-child(even) td {
251
+ background: #f5f8ff;
252
+ }
253
+
254
+ .manual-articulo tbody tr:hover td {
255
+ background: #ebf1ff;
256
+ }
257
+
258
+ /* Separador */
259
+ .manual-articulo hr {
260
+ border: none;
261
+ border-top: 1px solid #dde3f0;
262
+ margin: 2rem 0;
263
+ }
264
+
265
+ /* Citas / notas */
266
+ .manual-articulo blockquote {
267
+ margin: 0.75rem 0 1rem;
268
+ padding: 0.65rem 1rem;
269
+ background: #fffbea;
270
+ border-left: 4px solid #f9a825;
271
+ border-radius: 0 6px 6px 0;
272
+ color: #4a3c00;
273
+ font-size: 0.92rem;
274
+ }
275
+
276
+ .manual-articulo blockquote p {
277
+ margin: 0;
278
+ }
279
+
280
+ /* Énfasis */
281
+ .manual-articulo strong {
282
+ font-weight: 700;
283
+ color: #002f6b;
284
+ }
285
+
286
+ /* Links */
287
+ .manual-articulo a {
288
+ color: #1976d2;
289
+ text-decoration: underline;
290
+ }
291
+
292
+ .manual-articulo a:focus {
293
+ outline: 2px solid #1976d2;
294
+ outline-offset: 2px;
295
+ border-radius: 2px;
296
+ }
297
+
298
+ /* ═══════════════════════════════════════════════════════════
299
+ Responsivo — pantallas pequeñas
300
+ ══════════════════════════════════════════════════════════ */
301
+ @media (max-width: 768px) {
302
+ .manual-layout {
303
+ grid-template-columns: 1fr;
304
+ }
305
+
306
+ .manual-toc {
307
+ position: static;
308
+ max-height: none;
309
+ border-right: none;
310
+ border-bottom: 2px solid #e0e8f5;
311
+ padding: 1rem;
312
+ margin-bottom: 0.5rem;
313
+ }
314
+
315
+ .manual-main {
316
+ padding: 1rem 1rem 3rem;
317
+ }
318
+ }
@@ -0,0 +1,43 @@
1
+ <div class="manual-layout">
2
+
3
+ <!-- ── Índice lateral ─────────────────────────────────── -->
4
+ <aside class="manual-toc" aria-label="Índice del manual">
5
+ <p class="toc-titulo">Contenido</p>
6
+ <nav>
7
+ <ul class="toc-lista">
8
+ @for (entrada of toc; track entrada.id) {
9
+ <li [class]="'toc-nivel-' + entrada.nivel">
10
+ <button class="toc-enlace" (click)="irA(entrada.id)" [title]="entrada.texto">
11
+ {{ entrada.texto }}
12
+ </button>
13
+ </li>
14
+ }
15
+ </ul>
16
+ </nav>
17
+ </aside>
18
+
19
+ <!-- ── Artículo principal ─────────────────────────────── -->
20
+ <main class="manual-main" role="main" aria-label="Manual de usuario">
21
+
22
+ @if (cargando) {
23
+ <div class="manual-estado" role="status" aria-live="polite" aria-busy="true">
24
+ <p>Cargando manual…</p>
25
+ </div>
26
+ }
27
+
28
+ @if (error) {
29
+ <div class="manual-estado manual-error" role="alert">
30
+ <p>
31
+ No se pudo cargar el manual. Verifique que el archivo
32
+ <code>Manual.md</code> esté en la carpeta <code>public/</code>.
33
+ </p>
34
+ </div>
35
+ }
36
+
37
+ @if (!cargando && !error) {
38
+ <article #contenidoManual class="manual-articulo" [innerHTML]="contenido"></article>
39
+ }
40
+
41
+ </main>
42
+
43
+ </div>
@@ -0,0 +1,77 @@
1
+ import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
2
+ import { HttpClient } from '@angular/common/http';
3
+ import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
4
+ import { CommonModule } from '@angular/common';
5
+ import { DatosGlobalesService } from '../../../datos-globales.service';
6
+ import { marked } from 'marked';
7
+ import { Subject } from 'rxjs';
8
+ import { takeUntil } from 'rxjs/operators';
9
+
10
+ interface EntradaToc {
11
+ nivel: number;
12
+ texto: string;
13
+ id: string;
14
+ }
15
+
16
+ const DIACRITICOS = new RegExp('[̀-ͯ]', 'g');
17
+
18
+ @Component({
19
+ selector: 'app-manual',
20
+ standalone: true,
21
+ imports: [CommonModule],
22
+ templateUrl: './manual.component.html',
23
+ styleUrl: './manual.component.css'
24
+ })
25
+ export class ManualComponent implements OnInit, OnDestroy {
26
+ public contenido: SafeHtml = '';
27
+ public toc: EntradaToc[] = [];
28
+ public cargando: boolean = true;
29
+ public error: boolean = false;
30
+ private _destroy$ = new Subject<void>();
31
+ @ViewChild('contenidoManual') contenidoManualRef!: ElementRef;
32
+
33
+ constructor(private http: HttpClient, private sanitizer: DomSanitizer, private datosGlobalesService: DatosGlobalesService) { }
34
+
35
+ ngOnInit(): void {
36
+ this.http.get(`${this.datosGlobalesService.ObtenerURL()}misc/VistaDelManual`).pipe(takeUntil(this._destroy$)).subscribe({ error: () => { } });
37
+
38
+ this.http.get('/Manual.md', { responseType: 'text' }).pipe(takeUntil(this._destroy$)).subscribe({
39
+ next: (markdown) => {
40
+ const html = marked.parse(markdown) as string;
41
+
42
+ const parser = new DOMParser();
43
+ const doc = parser.parseFromString(html, 'text/html');
44
+
45
+ doc.querySelectorAll('h1, h2, h3').forEach((heading) => {
46
+ const texto = heading.textContent ?? '';
47
+ const id = 'sec-' + texto
48
+ .toLowerCase()
49
+ .normalize('NFD')
50
+ .replace(DIACRITICOS, '')
51
+ .replace(/[^a-z0-9\s-]/g, '')
52
+ .trim()
53
+ .replace(/\s+/g, '-');
54
+ heading.id = id;
55
+ const nivel = parseInt(heading.tagName[1], 10);
56
+ this.toc.push({ nivel, texto, id });
57
+ });
58
+
59
+ this.contenido = this.sanitizer.bypassSecurityTrustHtml(doc.body.innerHTML);
60
+ this.cargando = false;
61
+ },
62
+ error: () => {
63
+ this.error = true;
64
+ this.cargando = false;
65
+ }
66
+ });
67
+ }
68
+
69
+ ngOnDestroy(): void {
70
+ this._destroy$.next();
71
+ this._destroy$.complete();
72
+ }
73
+
74
+ irA(id: string): void {
75
+ this.contenidoManualRef?.nativeElement.querySelector('#' + id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
76
+ }
77
+ }
@@ -1,5 +1,5 @@
1
1
  import { Component, Inject } from '@angular/core';
2
- import { HttpClient, HttpHeaders } from '@angular/common/http';
2
+ import { HttpClient } from '@angular/common/http';
3
3
  import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
4
4
  import { MatIconModule } from '@angular/material/icon';
5
5
  import { MatButtonModule } from '@angular/material/button';
@@ -8,6 +8,8 @@ import { MatDialogContent, MatDialogActions, MatDialogTitle } from '@angular/mat
8
8
  import { CommonModule } from '@angular/common';
9
9
  import { DatosGlobalesService } from '../../../datos-globales.service';
10
10
 
11
+ interface FilaMensaje { llave: string; valor: string; tachado: boolean }
12
+
11
13
  @Component({
12
14
  selector: 'app-mensajes',
13
15
  templateUrl: './mensajes.component.html',
@@ -24,7 +26,7 @@ import { DatosGlobalesService } from '../../../datos-globales.service';
24
26
  ]
25
27
  })
26
28
  export class MensajesComponent {
27
- public arregloDeDatos: { llave: any; valor: any; tachado: any }[] = [];
29
+ public arregloDeDatos: FilaMensaje[] = [];
28
30
 
29
31
  constructor(private http: HttpClient, public dialogRef: MatDialogRef<MensajesComponent>
30
32
  , @Inject(MAT_DIALOG_DATA) public data: any, private datosGlobalesService: DatosGlobalesService) {
@@ -35,7 +37,7 @@ export class MensajesComponent {
35
37
  this.dialogRef.close();
36
38
  }
37
39
 
38
- enviar(item: { llave: any; valor: any; tachado: any }) {
40
+ enviar(item: FilaMensaje) {
39
41
  item.tachado = true;
40
42
  this.http
41
43
  .post(`${this.datosGlobalesService.ObtenerURL()}misc/actualizarNotificacion`, { FechaYHoraDeCreacion: item.llave }).subscribe();
@@ -1,4 +1,4 @@
1
- import { Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core';
1
+ import { Component, ElementRef, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
2
2
  import { FormBuilder, FormGroup, Validators } from '@angular/forms';
3
3
  import { MatFormFieldModule } from '@angular/material/form-field';
4
4
  import { MatInputModule } from '@angular/material/input';
@@ -6,7 +6,9 @@ import { ReactiveFormsModule } from '@angular/forms';
6
6
  import { MatDialogActions, MatDialogContent, MatDialogRef, MatDialogTitle } from '@angular/material/dialog';
7
7
  import { MatButtonModule } from '@angular/material/button';
8
8
  import { DatosGlobalesService } from '../../../datos-globales.service';
9
- import { HttpClient, HttpHeaders } from '@angular/common/http';
9
+ import { HttpClient } from '@angular/common/http';
10
+ import { Subject } from 'rxjs';
11
+ import { takeUntil } from 'rxjs/operators';
10
12
 
11
13
  @Component({
12
14
  selector: 'app-reporte-de-incidencias',
@@ -23,10 +25,10 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
23
25
  ]
24
26
  })
25
27
 
26
- export class ReporteDeIncidenciasComponent implements OnInit {
28
+ export class ReporteDeIncidenciasComponent implements OnInit, OnDestroy {
27
29
  formulario!: FormGroup;
28
- archivos: File[] = [];
29
30
  readonly dialogRef = inject(MatDialogRef<ReporteDeIncidenciasComponent>);
31
+ private _destroy$ = new Subject<void>();
30
32
  constructor(private fb: FormBuilder, private datosGlobalesService: DatosGlobalesService, private http: HttpClient) { }
31
33
  @ViewChild('EntradDeArchivo') EntradDeArchivo!: ElementRef<HTMLInputElement>;
32
34
  Archivo: any;
@@ -51,6 +53,7 @@ export class ReporteDeIncidenciasComponent implements OnInit {
51
53
  const Datos = JSON.stringify(this.formulario.value);
52
54
  this.http.post(this.datosGlobalesService.ObtenerURL() + 'misc/reporteDeIncidencia/' + Datos,
53
55
  archivo)
56
+ .pipe(takeUntil(this._destroy$))
54
57
  .subscribe({
55
58
  next: (data: any) => {
56
59
  alert('Mensaje enviado');
@@ -70,6 +73,11 @@ export class ReporteDeIncidenciasComponent implements OnInit {
70
73
  }
71
74
  }
72
75
 
76
+ ngOnDestroy(): void {
77
+ this._destroy$.next();
78
+ this._destroy$.complete();
79
+ }
80
+
73
81
  AbrirGestorDeArchivos() {
74
82
  this.EntradDeArchivo.nativeElement.click();
75
83
  }
@@ -1,4 +1,4 @@
1
- import { Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core';
1
+ import { Component, OnDestroy, OnInit, inject } from '@angular/core';
2
2
  import { FormBuilder, FormGroup, Validators } from '@angular/forms';
3
3
  import { MatFormFieldModule } from '@angular/material/form-field';
4
4
  import { MatInputModule } from '@angular/material/input';
@@ -6,7 +6,9 @@ import { ReactiveFormsModule } from '@angular/forms';
6
6
  import { MatDialogActions, MatDialogContent, MatDialogRef, MatDialogTitle } from '@angular/material/dialog';
7
7
  import { MatButtonModule } from '@angular/material/button';
8
8
  import { DatosGlobalesService } from '../../../datos-globales.service';
9
- import { HttpClient, HttpHeaders } from '@angular/common/http';
9
+ import { HttpClient } from '@angular/common/http';
10
+ import { Subject } from 'rxjs';
11
+ import { takeUntil } from 'rxjs/operators';
10
12
 
11
13
  @Component({
12
14
  selector: 'app-reporte-de-sugerencias',
@@ -23,9 +25,10 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
23
25
  ]
24
26
  })
25
27
 
26
- export class ReporteDeSugerenciasComponent implements OnInit {
28
+ export class ReporteDeSugerenciasComponent implements OnInit, OnDestroy {
27
29
  formulario!: FormGroup;
28
30
  readonly dialogRef = inject(MatDialogRef<ReporteDeSugerenciasComponent>);
31
+ private _destroy$ = new Subject<void>();
29
32
  constructor(private fb: FormBuilder, private datosGlobalesService: DatosGlobalesService, private http: HttpClient) { }
30
33
 
31
34
  ngOnInit(): void {
@@ -41,6 +44,7 @@ export class ReporteDeSugerenciasComponent implements OnInit {
41
44
  Enviar() {
42
45
  const Datos = JSON.stringify(this.formulario.value);
43
46
  this.http.post(this.datosGlobalesService.ObtenerURL() + 'misc/reporteDeSugerencia/' + Datos, {})
47
+ .pipe(takeUntil(this._destroy$))
44
48
  .subscribe({
45
49
  next: (data: any) => {
46
50
  alert('Mensaje enviado');
@@ -51,4 +55,9 @@ export class ReporteDeSugerenciasComponent implements OnInit {
51
55
  })
52
56
  this.Cerrar();
53
57
  }
58
+
59
+ ngOnDestroy(): void {
60
+ this._destroy$.next();
61
+ this._destroy$.complete();
62
+ }
54
63
  }
@@ -1,3 +1,4 @@
1
+ <a #descargaLink style="display:none"></a>
1
2
  <div class="contenedor">
2
3
  <div class="lista">
3
4
  @for(archivo of ListaArchivos;track $index){
@@ -17,20 +18,22 @@
17
18
  }
18
19
  </div>
19
20
  @if(EsEditable) {
20
- <div class="zona-archivo" [class.deshabilitado]="ListaArchivos.length >= CantidadMaximaDeArchivos" (click)="AbrirGestorDeArchivos()" (dragover)="ArrastrarAdentro($event)"
21
- (dragleave)="ArrastrarAfuera($event)" (drop)="Soltar($event)">
21
+ <div class="zona-archivo" [class.deshabilitado]="ListaArchivos.length >= CantidadMaximaDeArchivos"
22
+ (click)="AbrirGestorDeArchivos()" (dragover)="ArrastrarAdentro($event)" (dragleave)="ArrastrarAfuera($event)"
23
+ (drop)="Soltar($event)">
22
24
  @if(ListaArchivos.length >= CantidadMaximaDeArchivos) {
23
- <p class="texto-limite">Límite de {{CantidadMaximaDeArchivos}} archivos alcanzado</p>
25
+ <p class="texto-limite">Límite de {{CantidadMaximaDeArchivos}} archivos alcanzado</p>
24
26
  } @else {
25
- @if(!Archivo){
26
- <p class="texto">Arrastra y suelta un archivo aquí o <span class="texto-clic">haz clic para subir</span></p>
27
- }
28
- @else{
29
- <p>Archivo seleccionado: {{ Archivo.name }}</p>
30
- }
27
+ @if(!Archivo){
28
+ <p class="texto">Arrastra y suelta un archivo aquí o <span class="texto-clic">haz clic para subir</span></p>
29
+ }
30
+ @else{
31
+ <p>Archivo seleccionado: {{ Archivo.name }}</p>
32
+ }
31
33
  }
32
34
  <!-- Input oculto para seleccionar archivos manualmente -->
33
- <input type="file" #EntradaDeArchivo hidden (change)="ArchivoSeleccionado($event)" [accept]="FormatosPermitidos.join(',')">
35
+ <input type="file" #EntradaDeArchivo hidden (change)="ArchivoSeleccionado($event)"
36
+ [accept]="FormatosPermitidos.join(',')">
34
37
  </div>
35
38
  } @else {
36
39
  @if(ListaArchivos.length === 0) {