@jvsoft/mat-form-controls 1.0.0-alpha.13

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 (29) hide show
  1. package/README.md +184 -0
  2. package/base/index.d.ts +5 -0
  3. package/base/jvs-mat-form-control-base.d.ts +51 -0
  4. package/base/public-api.d.ts +1 -0
  5. package/fesm2022/jvsoft-mat-form-controls-base.mjs +145 -0
  6. package/fesm2022/jvsoft-mat-form-controls-base.mjs.map +1 -0
  7. package/fesm2022/jvsoft-mat-form-controls-jvs-autocomplete.mjs +101 -0
  8. package/fesm2022/jvsoft-mat-form-controls-jvs-autocomplete.mjs.map +1 -0
  9. package/fesm2022/jvsoft-mat-form-controls-jvs-file-upload.mjs +624 -0
  10. package/fesm2022/jvsoft-mat-form-controls-jvs-file-upload.mjs.map +1 -0
  11. package/fesm2022/jvsoft-mat-form-controls.mjs +145 -0
  12. package/fesm2022/jvsoft-mat-form-controls.mjs.map +1 -0
  13. package/index.d.ts +5 -0
  14. package/jvs-autocomplete/index.d.ts +5 -0
  15. package/jvs-autocomplete/jvs-autocomplete.component.d.ts +26 -0
  16. package/jvs-autocomplete/jvs-autocomplete.component.scss +58 -0
  17. package/jvs-autocomplete/public-api.d.ts +1 -0
  18. package/jvs-file-upload/README.md +613 -0
  19. package/jvs-file-upload/index.d.ts +5 -0
  20. package/jvs-file-upload/jvs-file-upload-item/jvs-file-upload-item.component.d.ts +29 -0
  21. package/jvs-file-upload/jvs-file-upload-item/jvs-file-upload-item.component.scss +118 -0
  22. package/jvs-file-upload/jvs-file-upload.component.d.ts +140 -0
  23. package/jvs-file-upload/jvs-file-upload.component.scss +163 -0
  24. package/jvs-file-upload/jvs-file-upload.directive.d.ts +42 -0
  25. package/jvs-file-upload/jvs-file-upload.interfaces.d.ts +77 -0
  26. package/jvs-file-upload/public-api.d.ts +4 -0
  27. package/package.json +39 -0
  28. package/public-api.d.ts +1 -0
  29. package/src/lib/mat-form-controls/mat-form-controls.component.css +0 -0
@@ -0,0 +1,613 @@
1
+ # JvsFileUploadComponent
2
+
3
+ Componente de subida de archivos reutilizable para proyectos `@jvsoft`, construido con el estándar más moderno de Angular 19+: **signals**, `input()`, `output()`, `computed()` y control flow declarativo (`@if`, `@for`, `@switch`).
4
+
5
+ Implementa `ControlValueAccessor` para integrarse de forma nativa con formularios reactivos y template-driven. No depende de ningún servicio de negocio: el componente padre inyecta las funciones de subida y eliminación como `input()`, lo que lo hace completamente reutilizable entre proyectos.
6
+
7
+ ---
8
+
9
+ ## Características
10
+
11
+ - ✅ **Angular 19+ Signals**: `input()`, `output()`, `signal()`, `computed()` — sin decoradores legacy.
12
+ - ✅ **Dos modos de operación**: Subida manual (pre-form) y subida automática inmediata (temporal).
13
+ - ✅ **Drag & Drop**: Zona de arrastrar y soltar integrada con la directiva `jvsFileUpload`.
14
+ - ✅ **Validación de archivos**: por extensión, tamaño máximo, parte de nombre obligatoria o nombre exclusivo.
15
+ - ✅ **Barra de progreso individual** por cada archivo (modo temporal).
16
+ - ✅ **Soporte de formularios**: `formControlName`, `formControl`, `ngModel`.
17
+ - ✅ **Inversión de dependencias**: las funciones de upload/remove se pasan como `input()`, sin acoplamiento a servicios concretos.
18
+ - ✅ **Firma electrónica delegada**: emite un `output()` para que el padre gestione la firma.
19
+ - ✅ **Modo readonly**: solo muestra la lista de archivos, oculta la zona de drop.
20
+
21
+ ---
22
+
23
+ ## Instalación y uso
24
+
25
+ ### 1. Importar el componente
26
+
27
+ El componente es standalone y se publica como **secondary entry point**:
28
+
29
+ ```typescript
30
+ import { JvsFileUploadComponent } from '@jvsoft/mat-form-controls/jvs-file-upload';
31
+
32
+ @Component({
33
+ standalone: true,
34
+ imports: [JvsFileUploadComponent, ReactiveFormsModule],
35
+ })
36
+ export class MiFormulario {}
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Modos de operación
42
+
43
+ ### Modo A — Subida manual (`temporal = false`, defecto)
44
+
45
+ Los archivos se seleccionan localmente y **no se suben hasta que el padre llame a `uploadFilesPreForm()`**.
46
+ Ideal para formularios donde se quiere controlar cuándo se envían los datos.
47
+
48
+ ```html
49
+ <jvs-file-upload
50
+ [extensionesPermitidas]="['pdf', 'docx', 'xlsx']"
51
+ [tamanoMaximoMB]="10"
52
+ formControlName="archivos"
53
+ />
54
+ ```
55
+
56
+ ```typescript
57
+ // En el método de guardado del formulario:
58
+ const archivosSubidos = await this.fileUpload.uploadFilesPreForm('documentos/2024');
59
+ // archivosSubidos: JvsArchivoServidor[]
60
+ ```
61
+
62
+ ### Modo B — Subida automática (`temporal = true`)
63
+
64
+ Cada archivo se sube **inmediatamente** al ser seleccionado o arrastrado.
65
+ La barra de progreso es visible por cada ítem. El valor del control se actualiza en tiempo real.
66
+
67
+ ```html
68
+ <jvs-file-upload
69
+ [temporal]="true"
70
+ [extensionesPermitidas]="['pdf']"
71
+ [tamanoMaximoMB]="5"
72
+ [uploadFn]="uploadFn"
73
+ [removeFn]="removeFn"
74
+ (archivoDescarga)="onDescargar($event)"
75
+ (firmarArchivo)="onFirmar($event)"
76
+ formControlName="archivos"
77
+ />
78
+ ```
79
+
80
+ ```typescript
81
+ // Definir la función de subida (provista por el proyecto consumidor)
82
+ readonly uploadFn: JvsUploadFn = (formData, anonimo) =>
83
+ this.filesQueryService.uploadFile(formData, anonimo);
84
+
85
+ // Definir la función de eliminación
86
+ // servFile.key → archivo registrado en BD con key de almacenamiento externo
87
+ // servFile.path → archivo recién subido (solo path disponible) o archivo de BD con path local
88
+ readonly removeFn: JvsRemoveFn = async ({ servFile }) => {
89
+ if (servFile.key) {
90
+ // Archivo registrado en BD: eliminar via mantenimiento (limpia BD + storage)
91
+ await this.queryService.guardarDatos(
92
+ 'mantenimiento#archivos',
93
+ { id: servFile.id },
94
+ 'grl', 'toastr'
95
+ );
96
+ } else if (servFile.path) {
97
+ // Archivo en storage local (recién subido o BD sin key)
98
+ // El backend espera la ruta relativa sin el prefijo "storage/"
99
+ const path = (servFile.path as string).replace('storage/', '');
100
+ await this.filesQueryService.removeFile({ f: path });
101
+ }
102
+ };
103
+ ```
104
+
105
+ ### Modo C — Solo lectura (`readonly = true`)
106
+
107
+ Muestra la lista de archivos existentes sin zona de drop ni controles de edición.
108
+
109
+ ```html
110
+ <jvs-file-upload
111
+ [readonly]="true"
112
+ [temporal]="true"
113
+ formControlName="archivos"
114
+ />
115
+ ```
116
+
117
+ ---
118
+
119
+ ## API del Componente
120
+
121
+ ### Entry Point
122
+
123
+ ```
124
+ @jvsoft/mat-form-controls/jvs-file-upload
125
+ ```
126
+
127
+ ### Selector
128
+
129
+ ```
130
+ jvs-file-upload
131
+ ```
132
+
133
+ ---
134
+
135
+ ### Inputs
136
+
137
+ | Propiedad | Tipo | Defecto | Descripción |
138
+ |:---|:---|:---|:---|
139
+ | `temporal` | `boolean` | `false` | Activa la subida inmediata de archivos al seleccionarlos. |
140
+ | `permitirEliminar` | `boolean` | `true` | Muestra el botón de eliminar en cada ítem. |
141
+ | `readonly` | `boolean` | `false` | Oculta la zona de drop y solo muestra la lista. |
142
+ | `multiple` | `boolean` | `true` | Permite seleccionar más de un archivo. |
143
+ | `extensionesPermitidas` | `string[]` | `[]` | Lista de extensiones válidas (sin punto, minúsculas). Array vacío = todas. |
144
+ | `parteDeNombre` | `string[]` | `[]` | El nombre del archivo debe contener **todos** estos fragmentos. |
145
+ | `parteDeNombreExclusivo` | `string[]` | `[]` | Si el nombre contiene **alguno** de estos fragmentos, el archivo se acepta sin checar extensión. Tiene prioridad sobre `parteDeNombre`. |
146
+ | `tamanoMaximoMB` | `number \| null` | `null` | Tamaño máximo en MB. `null` = sin límite. |
147
+ | `nombreArchivoFijo` | `string \| null` | `null` | Si se provee, se envía como `nombreArchivo` en el `FormData` al servidor. |
148
+ | `carpetaSubida` | `string \| null` | `null` | Carpeta destino en el servidor. Si es `null`, usa `temp/YYYY-MM-DD_HH`. |
149
+ | `diskSubida` | `string \| null` | `null` | Disk de Laravel (`local`, `s3`, etc.). `null` = usar el default del servidor. |
150
+ | `uploadFn` | `JvsUploadFn \| null` | `null` | **Función de subida**. Requerida en modo `temporal` o al llamar `uploadFilesPreForm()`. |
151
+ | `removeFn` | `JvsRemoveFn \| null` | `null` | **Función de eliminación**. Si es `null`, el archivo se elimina solo de la lista local sin llamar al servidor. |
152
+ | `cssContenedorAgregados` | `string` | `''` | Clase CSS adicional para el contenedor de la lista en modo temporal. |
153
+ | `cssContenedorAgregadosLista` | `string` | `''` | Clase CSS adicional para el contenedor de archivos válidos/inválidos en modo no-temporal. |
154
+
155
+ ---
156
+
157
+ ### Outputs
158
+
159
+ | Evento | Emite | Descripción |
160
+ |:---|:---|:---|
161
+ | `resultadoEliminado` | `JvsArchivoServidor[]` | Emite la lista de archivos del servidor restante tras eliminar un elemento. |
162
+ | `archivoDescarga` | `JvsFileEntry` | El usuario hizo click en "Descargar". El padre ejecuta la descarga real. |
163
+ | `firmarArchivo` | `JvsArchivoServidor` | El usuario hizo click en "Firmar". El padre abre el diálogo de firma electrónica. |
164
+
165
+ ---
166
+
167
+ ### Métodos públicos
168
+
169
+ Accede a estos métodos vía `ViewChild`:
170
+
171
+ ```typescript
172
+ @ViewChild(JvsFileUploadComponent) fileUpload!: JvsFileUploadComponent;
173
+ ```
174
+
175
+ | Método | Firma | Descripción |
176
+ |:---|:---|:---|
177
+ | `uploadFilesPreForm()` | `(carpeta?, anonimo?, disk?) => Promise<JvsArchivoServidor[]>` | Sube todos los archivos pendientes. Muestra confirmación si alguno falla. Retorna los archivos subidos. |
178
+ | `reset()` | `() => void` | Limpia toda la lista de archivos y notifica al `FormControl`. |
179
+
180
+ ---
181
+
182
+ ### Propiedades de estado (signals públicos)
183
+
184
+ | Signal | Tipo | Descripción |
185
+ |:---|:---|:---|
186
+ | `fileList` | `Signal<JvsFileEntry[]>` | Lista interna de archivos (locales y del servidor). |
187
+ | `invalidFiles` | `Signal<File[]>` | Archivos rechazados por la última selección. |
188
+ | `isDisabled` | `Signal<boolean>` | Estado disabled del control. |
189
+ | `isEmpty` | `Signal<boolean>` | `true` cuando no hay archivos en la lista. |
190
+
191
+ ---
192
+
193
+ ## Interfaces y Tipos
194
+
195
+ ```typescript
196
+ import {
197
+ JvsArchivoServidor,
198
+ JvsFileEntry,
199
+ JvsUploadFn,
200
+ JvsRemoveFn,
201
+ } from '@jvsoft/mat-form-controls/jvs-file-upload';
202
+ ```
203
+
204
+ ### `JvsArchivoServidor`
205
+
206
+ Representa un archivo ya guardado en el servidor.
207
+
208
+ ```typescript
209
+ interface JvsArchivoServidor {
210
+ id?: number | string; // ID en la BD
211
+ key?: string; // Key en S3/almacenamiento externo
212
+ path?: string; // Path en storage local
213
+ nombre?: string; // Nombre legible del archivo
214
+ extension?: string; // Extensión (sin punto)
215
+ cArchivoData?: string; // JSON string con metadata de generación PDF
216
+ [key: string]: any; // Campos adicionales del servidor
217
+ }
218
+ ```
219
+
220
+ ### `JvsFileEntry`
221
+
222
+ Entrada interna de la lista. Encapsula tanto archivos locales como los ya subidos.
223
+
224
+ ```typescript
225
+ interface JvsFileEntry {
226
+ file?: File | { name: string }; // Objeto File nativo o wrapper mínimo
227
+ desdeServidor: boolean; // true si proviene de writeValue()
228
+ inProgress: boolean; // true mientras se está subiendo
229
+ progress: number; // 0-100
230
+ errorSubida?: boolean; // true si ocurrió un error
231
+ servFile?: JvsArchivoServidor; // Datos del servidor tras subida exitosa
232
+ }
233
+ ```
234
+
235
+ ### `JvsUploadFn`
236
+
237
+ Tipo de la función de subida que el consumidor debe proveer:
238
+
239
+ ```typescript
240
+ type JvsUploadFn = (formData: FormData, anonimo?: boolean) => Observable<HttpEvent<any>>;
241
+ ```
242
+
243
+ ### `JvsRemoveFn`
244
+
245
+ Tipo de la función de eliminación que el consumidor debe proveer:
246
+
247
+ ```typescript
248
+ type JvsRemoveFn = (params: Record<string, any>) => Promise<any>;
249
+ // params.servFile contiene el JvsArchivoServidor a eliminar
250
+ ```
251
+
252
+ ---
253
+
254
+ ## Lógica de validación de archivos
255
+
256
+ La directiva `JvsFileUploadDirective` implementa la siguiente cadena de decisión al recibir archivos (por click o drag & drop):
257
+
258
+ ```
259
+ Para cada archivo recibido:
260
+ 1. ¿Excede tamanoMaximoMB?
261
+ → SÍ: Rechazado. Toast de error. Siguiente archivo.
262
+
263
+ 2. ¿parteDeNombreExclusivo está definido Y el nombre del archivo
264
+ contiene ALGUNO de sus valores?
265
+ → SÍ: Aceptado sin verificar extensión. (Siguiente archivo.)
266
+
267
+ 3. ¿La extensión está en extensionesPermitidas (o la lista está vacía)
268
+ Y el nombre contiene TODOS los valores de parteDeNombre?
269
+ → SÍ: Aceptado.
270
+
271
+ 4. En cualquier otro caso:
272
+ → Rechazado. Toast de error.
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Flujo de datos (Diagrama)
278
+
279
+ ```
280
+ Usuario selecciona / arrastra archivos
281
+
282
+
283
+ JvsFileUploadDirective.filtrarArchivos()
284
+ │ │
285
+ válidos inválidos
286
+ │ │
287
+ │ invalidFiles.set()
288
+
289
+ onFilesChange()
290
+
291
+ ├─ [temporal=false] → fileList.set(entries sin progress)
292
+ │ _notifyChange() → onChange(File[])
293
+
294
+ └─ [temporal=true] → fileList.update([...nuevos])
295
+ uploadFileTemporal(entry) por cada nuevo
296
+
297
+
298
+ uploadFn(FormData).pipe(
299
+ UploadProgress → fileList.update(progress)
300
+ Response → fileList.update(servFile)
301
+ _notifyChange() → onChange(JvsArchivoServidor[])
302
+ )
303
+ ```
304
+
305
+ ---
306
+
307
+ ## Integración con formularios reactivos
308
+
309
+ ### Con `formControlName`
310
+
311
+ ```typescript
312
+ form = this.fb.group({
313
+ titulo: ['', Validators.required],
314
+ archivos: [[]], // Valor inicial: array vacío
315
+ });
316
+ ```
317
+
318
+ ```html
319
+ <form [formGroup]="form" (ngSubmit)="guardar()">
320
+ <jvs-file-upload
321
+ formControlName="archivos"
322
+ [temporal]="true"
323
+ [uploadFn]="uploadFn"
324
+ />
325
+ </form>
326
+ ```
327
+
328
+ **Valor del control:**
329
+ - Modo `temporal = false`: `File[]` — objetos File del browser (solo archivos locales nuevos; los archivos precargados del servidor no se incluyen en el valor pero sí se muestran en la lista).
330
+ - Modo `temporal = true`: `JvsArchivoServidor[]` — objetos retornados por el servidor.
331
+
332
+ ### Validación de formulario
333
+
334
+ El componente implementa la interfaz `Validator` de Angular. Registra errores en el `FormControl` automáticamente:
335
+
336
+ | Error | Cuándo aparece |
337
+ |:---|:---|
338
+ | `uploadEnProgreso: true` | Hay al menos un archivo siendo subido en este momento. |
339
+ | `errorSubida: true` | Al menos un archivo terminó con error de subida. |
340
+
341
+ ```typescript
342
+ // Leer los errores desde el componente padre
343
+ const ctrl = this.form.get('archivos');
344
+
345
+ if (ctrl?.hasError('uploadEnProgreso')) {
346
+ // Subida en curso — no guardar aún
347
+ }
348
+ if (ctrl?.hasError('errorSubida')) {
349
+ // Hubo un fallo — mostrar mensaje al usuario
350
+ }
351
+ ```
352
+
353
+ ```html
354
+ <!-- Mostrar mensajes de error en el template -->
355
+ <jvs-file-upload formControlName="archivos" [temporal]="true" [uploadFn]="uploadFn">
356
+ <span validator>
357
+ @if (form.get('archivos')?.hasError('uploadEnProgreso')) {
358
+ Espere a que terminen de subir los archivos.
359
+ }
360
+ @if (form.get('archivos')?.hasError('errorSubida')) {
361
+ Algunos archivos no se pudieron subir. Elimínalos e inténtalo de nuevo.
362
+ }
363
+ </span>
364
+ </jvs-file-upload>
365
+ ```
366
+
367
+ > Los errores se actualizan automáticamente: Angular re-ejecuta `validate()` cada vez que cambia el estado de progreso o error en la lista.
368
+
369
+ ---
370
+
371
+ ### Cargar archivos existentes (`writeValue`)
372
+
373
+ Cuando el formulario se carga con datos del servidor (ej: editando un registro):
374
+
375
+ ```typescript
376
+ this.form.patchValue({
377
+ archivos: registro.archivos, // JvsArchivoServidor[]
378
+ });
379
+ // El componente reconstruye la lista mostrando los archivos "En servidor".
380
+ // Funciona en ambos modos (temporal = true y temporal = false).
381
+ //
382
+ // En temporal = false: los archivos del servidor se muestran en la lista pero NO se
383
+ // re-suben al llamar uploadFilesPreForm() (ya están en el servidor). El valor que
384
+ // emite onChange solo incluye los File locales nuevos que el usuario agregue.
385
+ ```
386
+
387
+ ---
388
+
389
+ ## Ejemplos completos
390
+
391
+ ### Formulario de registro de documentos
392
+
393
+ ```typescript
394
+ @Component({
395
+ template: `
396
+ <jvs-file-upload
397
+ #fileUpload
398
+ [extensionesPermitidas]="['pdf', 'docx']"
399
+ [tamanoMaximoMB]="10"
400
+ [multiple]="false"
401
+ [uploadFn]="uploadFn"
402
+ [carpetaSubida]="'contratos/2024'"
403
+ (resultadoEliminado)="onEliminado($event)"
404
+ formControlName="contrato"
405
+ />
406
+ `
407
+ })
408
+ export class RegistroContratoComponent {
409
+ @ViewChild('fileUpload') fileUpload!: JvsFileUploadComponent;
410
+
411
+ readonly uploadFn: JvsUploadFn = (fd) =>
412
+ inject(FilesQueryService).uploadFile(fd);
413
+
414
+ async guardar() {
415
+ const archivos = await this.fileUpload.uploadFilesPreForm();
416
+ if (!archivos.length) return; // El usuario canceló o hubo error
417
+
418
+ this.form.patchValue({ contrato: archivos[0] });
419
+ await this.registroService.guardar(this.form.value);
420
+ }
421
+ }
422
+ ```
423
+
424
+ ### Vista de detalle con descarga y firma
425
+
426
+ ```typescript
427
+ @Component({
428
+ template: `
429
+ <jvs-file-upload
430
+ [temporal]="true"
431
+ [readonly]="!puedeEditar"
432
+ [uploadFn]="uploadFn"
433
+ [removeFn]="removeFn"
434
+ (archivoDescarga)="descargar($event)"
435
+ (firmarArchivo)="firmar($event)"
436
+ formControlName="adjuntos"
437
+ />
438
+ `
439
+ })
440
+ export class DetalleExpedienteComponent {
441
+
442
+ descargar(entry: JvsFileEntry) {
443
+ if (entry.servFile?.key) {
444
+ this.filesService.downloadFile({ cArchivoKey: entry.servFile.key });
445
+ }
446
+ }
447
+
448
+ firmar(archivo: JvsArchivoServidor) {
449
+ this.dlgFirmaService.firmarPorTipo(this.tipoFirma, archivo);
450
+ }
451
+ }
452
+ ```
453
+
454
+ ---
455
+
456
+ ## Comparativa con la versión legada (`fc-file-upload`)
457
+
458
+ | Característica | Versión legada | `jvs-file-upload` |
459
+ |:---|:---|:---|
460
+ | Inputs | `@Input()` decorador | `input()` signal |
461
+ | Outputs | `@Output() EventEmitter` | `output()` signal |
462
+ | Estado | `any[]` mutable | `signal<JvsFileEntry[]>()` |
463
+ | Derivados | Lógica en template | `computed()` signals |
464
+ | HTTP | `toPromise()` (deprecado) | `lastValueFrom()` |
465
+ | Control flow | `*ngIf`, `*ngFor` | `@if`, `@for`, `@switch` |
466
+ | Servicios | Inyectados (acoplados) | `input<JvsUploadFn>()` (inversión de dependencia) |
467
+ | Firma | Dependencia directa a `FirmaService` | `output()` — el padre gestiona |
468
+ | `@Output() resultado` | Declarado, nunca emitía | Eliminado |
469
+ | `@Input() contieneEnNombre` | Declarado, sin implementar | Eliminado |
470
+ | `validarExtensionesV1()` | Bug en condición lógica | Eliminado |
471
+ | `console.log` en `mostrarDataArchivo` | Presente | Eliminado |
472
+ | Tipado | `fileList: any[]` | `fileList: signal<JvsFileEntry[]>` |
473
+
474
+ ---
475
+
476
+ ---
477
+
478
+ ## Referencia de API del backend (JVSoft Laravel)
479
+
480
+ El componente consume tres endpoints del módulo `grl` del backend. Los mismos existen en versión autenticada y anónima.
481
+
482
+ ### POST `/grl/archivos/cargar` · `/grl/archivos/anonimo/cargar`
483
+
484
+ Sube un archivo al disco de Laravel.
485
+
486
+ **Request** (`multipart/form-data` o JSON con base64):
487
+
488
+ | Campo | Tipo | Requerido | Descripción |
489
+ |:---|:---|:---|:---|
490
+ | `archivo` | `File` | ✓ (o `base64`) | Archivo multipart |
491
+ | `base64` | `string` | ✓ (o `archivo`) | Contenido en base64. Incluir prefijo `data:<mime>;base64,` o enviar `mime` por separado |
492
+ | `carpeta` | `string` | ✓ | Ruta destino en el disco (ej: `"temp/2024-01-15_14"`, `"tramites/adjuntos"`) |
493
+ | `disk` | `string` | — | Nombre del disco Laravel (`local`, `s3`, `tramite`, etc.). Default: el configurado en `filesystems.default` |
494
+ | `nombreArchivo` | `string` | — | Nombre fijo (sin extensión). Si se omite, se genera: `timestamp-[prefijo-]nombre_original[-sufijo].ext` |
495
+ | `prefijo` | `string` | — | Prefijo añadido al nombre generado automáticamente |
496
+ | `sufijo` | `string` | — | Sufijo añadido al nombre generado automáticamente |
497
+
498
+ **Respuesta** `200 OK`:
499
+ ```json
500
+ "temp/2024-01-15_14/1705326000-documento.pdf"
501
+ ```
502
+ > ⚠️ El backend devuelve un **string plano** (la ruta relativa en el disco), no un objeto JSON.
503
+ > El componente lo normaliza automáticamente a `{ path: "..." }` para uso uniforme.
504
+
505
+ ---
506
+
507
+ ### POST `/grl/archivos/eliminar` · `/grl/archivos/anonimo/eliminar`
508
+
509
+ Elimina un archivo del disco de Laravel por su ruta relativa.
510
+
511
+ **Request** (JSON):
512
+
513
+ | Campo | Tipo | Descripción |
514
+ |:---|:---|:---|
515
+ | `f` | `string` | Ruta relativa del archivo en el disco (sin prefijo `storage/`) |
516
+
517
+ **Respuesta** `200 OK`:
518
+ ```json
519
+ { "msg": "Se eliminó correctamente" }
520
+ ```
521
+
522
+ ---
523
+
524
+ ### GET `/grl/archivos/descargar` · `/grl/archivos/anonimo/descargar`
525
+
526
+ Sirve un archivo almacenado. Acepta dos modos de identificación:
527
+
528
+ **Por `key` (archivos registrados en BD):**
529
+
530
+ | Parámetro | Descripción |
531
+ |:---|:---|
532
+ | `cArchivoKey` | Key del archivo en la BD |
533
+ | `cArchivoCVD` | Alternativa: código de verificación digital |
534
+
535
+ Si el archivo tiene firmas electrónicas, el backend genera el PDF con QR de firma incrustado antes de servirlo.
536
+
537
+ **Por ruta (`f`):**
538
+
539
+ | Parámetro | Descripción |
540
+ |:---|:---|
541
+ | `f` | Ruta relativa en el disco (ej: `"temp/2024-01-15_14/file.pdf"`) |
542
+ | `porTipoAutomatico` | `1` → inline si < 50 MB, descarga si es mayor |
543
+ | `type` | `"view_pdf"` o `"view_img"` → fuerza inline |
544
+ | `disk` | Disco alternativo (opcional) |
545
+ | `name` | Nombre de descarga personalizado (opcional) |
546
+
547
+ **Ejemplo desde el componente padre:**
548
+ ```typescript
549
+ descargar(entry: JvsFileEntry) {
550
+ const sf = entry.servFile;
551
+ if (!sf) return;
552
+
553
+ if (sf.key) {
554
+ // Archivo en BD: el backend busca por key y aplica firmas si corresponde
555
+ this.filesQueryService.downloadFile({ cArchivoKey: sf.key });
556
+ } else if (sf.cArchivoData) {
557
+ // PDF generado dinámicamente con metadata
558
+ const meta = JSON.parse(sf.cArchivoData);
559
+ this.filesQueryService.downloadFile({ cArchivoKey: meta.key ?? sf.key });
560
+ } else if (sf.path) {
561
+ // Archivo recién subido (solo path disponible)
562
+ this.filesQueryService.downloadFile({ f: sf.path, porTipoAutomatico: 1 });
563
+ }
564
+ }
565
+ ```
566
+
567
+ ---
568
+
569
+ ## Storybook
570
+
571
+ Para explorar el componente de forma interactiva:
572
+
573
+ ```bash
574
+ npx nx run mat-form-controls:storybook
575
+ ```
576
+
577
+ ---
578
+
579
+ ## Directiva `JvsFileUploadDirective`
580
+
581
+ La directiva interna que gestiona el drag-and-drop. También puede usarse independientemente:
582
+
583
+ ```typescript
584
+ import { JvsFileUploadDirective } from '@jvsoft/mat-form-controls/jvs-file-upload';
585
+ ```
586
+
587
+ ### Inputs
588
+
589
+ | Input | Tipo | Defecto | Descripción |
590
+ |:---|:---|:---|:---|
591
+ | `controlFile` | `HTMLInputElement` | **requerido** | Referencia al `<input type="file">` para determinar si es multiple. |
592
+ | `extensionesPermitidas` | `string[]` | `[]` | Extensiones aceptadas al soltar archivos. |
593
+ | `parteDeNombre` | `string[]` | `[]` | Fragmentos requeridos en el nombre. |
594
+ | `parteDeNombreExclusivo` | `string[]` | `[]` | Fragmentos exclusivos (aceptan sin checar extensión). |
595
+ | `tamanoMaximoMB` | `number \| null` | `null` | Límite de tamaño en MB. |
596
+ | `isDisabled` | `boolean` | `false` | Deshabilita el drag-and-drop. |
597
+ | `fondoInicial` | `string` | `''` | Color de fondo del elemento en estado normal. |
598
+ | `fondoDragOver` | `string` | `'#e5e7eb'` | Color de fondo al arrastrar sobre el elemento. |
599
+
600
+ ### Outputs
601
+
602
+ | Output | Emite | Descripción |
603
+ |:---|:---|:---|
604
+ | `filesChange` | `File[]` | Archivos válidos al soltar en la zona. |
605
+ | `filesInvalidChange` | `File[]` | Archivos rechazados al soltar en la zona. |
606
+
607
+ ### Método estático
608
+
609
+ ```typescript
610
+ JvsFileUploadDirective.filtrarArchivos(files, params, multiple?): { valid: File[], invalid: File[] }
611
+ ```
612
+
613
+ Útil para validar archivos desde un `input[type=file]` sin necesidad del drop.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ /// <amd-module name="@jvsoft/mat-form-controls/jvs-file-upload" />
5
+ export * from './public-api';
@@ -0,0 +1,29 @@
1
+ import { convertirBytes } from '@jvsoft/utils';
2
+ import { JvsFileEntry, JvsArchivoServidor } from '../jvs-file-upload.interfaces';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * Componente de ítem individual de la lista de archivos en modo `temporal`.
6
+ * Muestra nombre, tamaño, barra de progreso y acciones (descargar, firmar, eliminar).
7
+ */
8
+ export declare class JvsFileUploadItemComponent {
9
+ file: import("@angular/core").InputSignal<JvsFileEntry>;
10
+ permitirEliminar: import("@angular/core").InputSignal<boolean>;
11
+ isDisabled: import("@angular/core").InputSignal<boolean>;
12
+ eliminar: import("@angular/core").OutputEmitterRef<JvsFileEntry>;
13
+ descargar: import("@angular/core").OutputEmitterRef<JvsFileEntry>;
14
+ firmar: import("@angular/core").OutputEmitterRef<JvsArchivoServidor>;
15
+ readonly extension: import("@angular/core").Signal<string>;
16
+ readonly nombreMostrado: import("@angular/core").Signal<string>;
17
+ readonly estadoProgreso: import("@angular/core").Signal<"" | "cargando" | "incompleto" | "completado">;
18
+ readonly modoProgreso: import("@angular/core").Signal<"indeterminate" | "determinate">;
19
+ readonly mostrarBotonDescargar: import("@angular/core").Signal<boolean>;
20
+ readonly mostrarBotonFirmar: import("@angular/core").Signal<boolean>;
21
+ readonly mostrarBotonEliminar: import("@angular/core").Signal<boolean>;
22
+ readonly tamanioArchivo: import("@angular/core").Signal<number>;
23
+ readonly convertirBytes: typeof convertirBytes;
24
+ onEliminar(): void;
25
+ onDescargar(): void;
26
+ onFirmar(): void;
27
+ static ɵfac: i0.ɵɵFactoryDeclaration<JvsFileUploadItemComponent, never>;
28
+ static ɵcmp: i0.ɵɵComponentDeclaration<JvsFileUploadItemComponent, "jvs-file-upload-item", never, { "file": { "alias": "file"; "required": true; "isSignal": true; }; "permitirEliminar": { "alias": "permitirEliminar"; "required": false; "isSignal": true; }; "isDisabled": { "alias": "isDisabled"; "required": false; "isSignal": true; }; }, { "eliminar": "eliminar"; "descargar": "descargar"; "firmar": "firmar"; }, never, never, true, never>;
29
+ }