ngx-dsxlibrary 2.21.71 → 2.21.72

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.
@@ -1,11 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, forwardRef, Component, EventEmitter, effect, Output, viewChild, signal, Input, model, inject, computed, Injectable, isDevMode, InjectionToken, output, HostBinding, ChangeDetectorRef, untracked, Pipe, ViewEncapsulation, HostListener, Directive, NgZone, ElementRef, ChangeDetectionStrategy, NgModule, Injector, ViewChild } from '@angular/core';
2
+ import { input, forwardRef, Component, EventEmitter, effect, Output, viewChild, signal, Input, model, inject, computed, Injectable, isDevMode, InjectionToken, output, HostBinding, ChangeDetectorRef, untracked, ViewEncapsulation, HostListener, Directive, NgZone, ElementRef, ChangeDetectionStrategy, NgModule, Pipe, Injector, ViewChild } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
4
  import { FormsModule, NG_VALUE_ACCESSOR, AbstractControl, FormControl, NG_VALIDATORS, FormBuilder, Validators, ReactiveFormsModule, NgControl, FormGroup } from '@angular/forms';
5
5
  import * as i2 from 'primeng/togglebutton';
6
6
  import { ToggleButtonModule } from 'primeng/togglebutton';
7
7
  import * as i1$1 from '@angular/common';
8
- import { CommonModule, AsyncPipe, DecimalPipe, Location, JsonPipe } from '@angular/common';
8
+ import { CommonModule, AsyncPipe, JsonPipe, DatePipe, DecimalPipe, Location } from '@angular/common';
9
9
  import * as i2$1 from 'primeng/tree';
10
10
  import { TreeModule } from 'primeng/tree';
11
11
  import * as i3 from 'primeng/api';
@@ -60,6 +60,7 @@ import { DrawerModule } from 'primeng/drawer';
60
60
  import { FieldsetModule } from 'primeng/fieldset';
61
61
  import { IconFieldModule } from 'primeng/iconfield';
62
62
  import { InputIconModule } from 'primeng/inputicon';
63
+ import * as i1$8 from 'primeng/inputmask';
63
64
  import { InputMaskModule } from 'primeng/inputmask';
64
65
  import { InputNumberModule, InputNumber } from 'primeng/inputnumber';
65
66
  import { InputTextModule } from 'primeng/inputtext';
@@ -4978,7 +4979,6 @@ class FileComponent {
4978
4979
  existingFileName = input(null, ...(ngDevMode ? [{ debugName: "existingFileName" }] : /* istanbul ignore next */ []));
4979
4980
  overrideFileName = input(null, ...(ngDevMode ? [{ debugName: "overrideFileName" }] : /* istanbul ignore next */ []));
4980
4981
  debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
4981
- required = input(true, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
4982
4982
  accept = input('.xls,.xlsx', ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
4983
4983
  pTooltipOverride = input('Seleccione un archivo', ...(ngDevMode ? [{ debugName: "pTooltipOverride" }] : /* istanbul ignore next */ []));
4984
4984
  tooltipPositionOverride = input('top', ...(ngDevMode ? [{ debugName: "tooltipPositionOverride" }] : /* istanbul ignore next */ []));
@@ -4990,6 +4990,7 @@ class FileComponent {
4990
4990
  // Signals de estado
4991
4991
  isReplacing = signal(false, ...(ngDevMode ? [{ debugName: "isReplacing" }] : /* istanbul ignore next */ []));
4992
4992
  disabled = signal(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4993
+ errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
4993
4994
  isViewActive = signal(false, ...(ngDevMode ? [{ debugName: "isViewActive" }] : /* istanbul ignore next */ []));
4994
4995
  value = null;
4995
4996
  touched = false;
@@ -4997,7 +4998,31 @@ class FileComponent {
4997
4998
  // Dependencias
4998
4999
  cdr = inject(ChangeDetectorRef);
4999
5000
  fileUpload = viewChild('fileUpload', ...(ngDevMode ? [{ debugName: "fileUpload" }] : /* istanbul ignore next */ []));
5000
- // Computed properties (solo se "activan" si la vista está activa)
5001
+ // --- Para el manejo de errores del formulario y debug ---
5002
+ _control = null;
5003
+ // Computed para el control de errores
5004
+ errorControl = computed(() => {
5005
+ return this._control;
5006
+ }, ...(ngDevMode ? [{ debugName: "errorControl" }] : /* istanbul ignore next */ []));
5007
+ // Signal para errores del control
5008
+ controlErrors = signal(null, ...(ngDevMode ? [{ debugName: "controlErrors" }] : /* istanbul ignore next */ []));
5009
+ // Estado del control
5010
+ controlValid = signal(true, ...(ngDevMode ? [{ debugName: "controlValid" }] : /* istanbul ignore next */ []));
5011
+ controlTouched = signal(false, ...(ngDevMode ? [{ debugName: "controlTouched" }] : /* istanbul ignore next */ []));
5012
+ controlDirty = signal(false, ...(ngDevMode ? [{ debugName: "controlDirty" }] : /* istanbul ignore next */ []));
5013
+ // --- Detección de modo desarrollo ---
5014
+ get isDevMode() {
5015
+ return (typeof ngDevMode !== 'undefined' &&
5016
+ ngDevMode !== null &&
5017
+ ngDevMode !== false);
5018
+ }
5019
+ // ✅ Método para detectar si el control es requerido
5020
+ isRequired() {
5021
+ if (!this._control)
5022
+ return false;
5023
+ return this._control.errors?.['required'] ?? false;
5024
+ }
5025
+ // Computed properties
5001
5026
  hasExistingFile = computed(() => {
5002
5027
  if (!this.isViewActive())
5003
5028
  return false;
@@ -5033,14 +5058,27 @@ class FileComponent {
5033
5058
  return true;
5034
5059
  return false;
5035
5060
  }, ...(ngDevMode ? [{ debugName: "isReplaceButtonDisabled" }] : /* istanbul ignore next */ []));
5036
- // --- Constructor: Aquí se crean los effects (contexto de inyección válido) ---
5061
+ allowedFileTypeLabel = computed(() => {
5062
+ if (!this.isViewActive())
5063
+ return 'archivo';
5064
+ const acceptStr = this.accept();
5065
+ if (!acceptStr)
5066
+ return 'archivo';
5067
+ const extensions = acceptStr.split(',').map((ext) => ext.trim());
5068
+ const cleanExtensions = extensions
5069
+ .map((ext) => ext.replace('.', '').toUpperCase())
5070
+ .filter((ext) => ext.length > 0);
5071
+ if (cleanExtensions.length === 0)
5072
+ return 'archivo';
5073
+ if (cleanExtensions.length === 1)
5074
+ return cleanExtensions[0];
5075
+ return cleanExtensions.join(' o ');
5076
+ }, ...(ngDevMode ? [{ debugName: "allowedFileTypeLabel" }] : /* istanbul ignore next */ []));
5077
+ // --- Constructor ---
5037
5078
  constructor() {
5038
- // Efecto 1: Re-evaluar validación cuando cambie el estado relevante
5039
5079
  effect(() => {
5040
- // Solo ejecutar si la vista está activa
5041
5080
  if (!this.isViewActive())
5042
5081
  return;
5043
- // "Tocar" estas signals para que el efecto dependa de ellas
5044
5082
  const hasFile = this.hasExistingFile();
5045
5083
  const replacing = this.isReplacing();
5046
5084
  const fileName = this.existingFileName();
@@ -5051,14 +5089,12 @@ class FileComponent {
5051
5089
  fileName,
5052
5090
  });
5053
5091
  if (this.control) {
5054
- // Usar untracked para no crear bucles infinitos
5055
5092
  untracked(() => {
5056
5093
  this.control?.updateValueAndValidity({ emitEvent: false });
5057
5094
  });
5058
5095
  }
5059
5096
  this.cdr.detectChanges();
5060
5097
  });
5061
- // Efecto 2: Cancelar reemplazo si el componente se deshabilita
5062
5098
  effect(() => {
5063
5099
  if (!this.isViewActive())
5064
5100
  return;
@@ -5072,104 +5108,341 @@ class FileComponent {
5072
5108
  });
5073
5109
  }
5074
5110
  ngAfterViewInit() {
5075
- // Activar el componente después de que la vista esté lista
5076
5111
  setTimeout(() => {
5077
5112
  this.isViewActive.set(true);
5078
- if (this.debug())
5079
- console.log('[FileComponent] View activated');
5113
+ if (this.debug()) {
5114
+ console.log('[FileComponent] View activated');
5115
+ console.log('[FileComponent] 📋 Configuración actual:', {
5116
+ accept: this.accept(),
5117
+ maxFileSize: this.maxFileSize(),
5118
+ debug: this.debug(),
5119
+ isDevMode: this.isDevMode,
5120
+ });
5121
+ }
5080
5122
  });
5081
5123
  }
5082
5124
  ngOnDestroy() {
5083
5125
  this.isViewActive.set(false);
5084
5126
  if (this.debug())
5085
- console.log('[FileComponent] Component destroyed');
5127
+ console.log('[FileComponent] 🗑️ Component destroyed');
5128
+ }
5129
+ // --- Método para validar archivo manualmente ---
5130
+ validateFile(file) {
5131
+ if (this.debug()) {
5132
+ this.log('🔍 validateFile - Iniciando validación manual', {
5133
+ fileName: file.name,
5134
+ fileSize: file.size,
5135
+ fileType: file.type,
5136
+ accept: this.accept(),
5137
+ maxSize: this.maxFileSize(),
5138
+ });
5139
+ }
5140
+ // Validar tipo de archivo
5141
+ const acceptStr = this.accept();
5142
+ if (acceptStr) {
5143
+ const acceptedExtensions = acceptStr
5144
+ .split(',')
5145
+ .map((ext) => ext.trim().toLowerCase());
5146
+ const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
5147
+ if (this.debug()) {
5148
+ this.log('🔍 validateFile - Verificando extensión', {
5149
+ fileExtension,
5150
+ acceptedExtensions,
5151
+ matches: acceptedExtensions.includes(fileExtension),
5152
+ });
5153
+ }
5154
+ if (!acceptedExtensions.includes(fileExtension)) {
5155
+ const detail = this.invalidDetail().replace('{0}', this.allowedFileTypeLabel());
5156
+ const message = `${this.invalidSummary()} ${detail}`;
5157
+ if (this.debug())
5158
+ this.log('❌ validateFile - Tipo de archivo no permitido', {
5159
+ message,
5160
+ });
5161
+ return { valid: false, error: message };
5162
+ }
5163
+ }
5164
+ // Validar tamaño
5165
+ const maxSizeBytes = this.maxFileSize() * 1024 * 1024;
5166
+ if (this.debug()) {
5167
+ this.log('🔍 validateFile - Verificando tamaño', {
5168
+ fileSize: file.size,
5169
+ maxSizeBytes,
5170
+ maxSizeMB: this.maxFileSize(),
5171
+ });
5172
+ }
5173
+ if (file.size > maxSizeBytes) {
5174
+ const detail = this.invalidSizeDetail().replace('{0}', `${this.maxFileSize()} MB`);
5175
+ const message = `${this.invalidSizeSummary()} ${detail}`;
5176
+ if (this.debug())
5177
+ this.log('❌ validateFile - Tamaño excedido', { message });
5178
+ return { valid: false, error: message };
5179
+ }
5180
+ if (this.debug())
5181
+ this.log('✅ validateFile - Archivo válido');
5182
+ return { valid: true };
5183
+ }
5184
+ // --- Métodos para el manejo de errores ---
5185
+ showError() {
5186
+ return !!(this._control && this._control.invalid && this._control.touched);
5187
+ }
5188
+ // Actualizar estado del control
5189
+ updateControlState() {
5190
+ if (this._control) {
5191
+ this.controlValid.set(this._control.valid);
5192
+ this.controlTouched.set(this._control.touched);
5193
+ this.controlDirty.set(this._control.dirty);
5194
+ this.controlErrors.set(this._control.errors);
5195
+ }
5196
+ }
5197
+ // Forzar recarga (para debug)
5198
+ forceReload() {
5199
+ if (this.debug()) {
5200
+ console.log('[FileComponent] 🔄 Force reload');
5201
+ if (this._control) {
5202
+ this._control.updateValueAndValidity();
5203
+ this.updateControlState();
5204
+ }
5205
+ this.cdr.detectChanges();
5206
+ }
5086
5207
  }
5087
- // --- Métodos públicos y ControlValueAccessor ---
5208
+ // --- Métodos públicos ---
5088
5209
  onSelect(event) {
5089
- if (!this.isViewActive())
5210
+ const startTime = performance.now();
5211
+ if (this.debug()) {
5212
+ this.log('📤 onSelect - Evento recibido', {
5213
+ files: event.files,
5214
+ currentFiles: event.currentFiles,
5215
+ isFileUploadEnabled: this.isFileUploadEnabled(),
5216
+ });
5217
+ }
5218
+ if (!this.isViewActive()) {
5219
+ if (this.debug())
5220
+ this.log('⚠️ Componente no activo, ignorando selección');
5090
5221
  return;
5222
+ }
5091
5223
  if (!this.isFileUploadEnabled()) {
5092
- console.warn('File upload is disabled');
5224
+ console.warn('[FileComponent] ⚠️ File upload is disabled');
5093
5225
  this.fileUpload()?.clear();
5094
5226
  return;
5095
5227
  }
5096
5228
  const file = event.files?.[0] ?? null;
5097
- if (this.debug())
5098
- this.log('File selected', { name: file?.name });
5229
+ if (!file) {
5230
+ if (this.debug())
5231
+ this.log('⚠️ No se seleccionó ningún archivo');
5232
+ return;
5233
+ }
5234
+ if (this.debug()) {
5235
+ this.log('📄 Archivo seleccionado', {
5236
+ name: file.name,
5237
+ size: file.size,
5238
+ type: file.type,
5239
+ sizeFormatted: this.formatFileSize(file.size),
5240
+ });
5241
+ }
5242
+ // Validar archivo manualmente
5243
+ const validation = this.validateFile(file);
5244
+ if (!validation.valid) {
5245
+ // Archivo inválido - mostrar error y limpiar
5246
+ this.errorMessage.set(validation.error || 'Archivo inválido');
5247
+ this.fileUpload()?.clear();
5248
+ this.value = null;
5249
+ this.onChange(null);
5250
+ if (this.debug()) {
5251
+ this.log('❌ Archivo inválido - Limpiando selección', {
5252
+ error: validation.error,
5253
+ });
5254
+ }
5255
+ // Auto-ocultar error después de 5 segundos
5256
+ setTimeout(() => {
5257
+ this.errorMessage.set(null);
5258
+ if (this.debug())
5259
+ this.log('⏰ Mensaje de error auto-ocultado después de 5s');
5260
+ }, 5000);
5261
+ // Actualizar estado del control
5262
+ this.updateControlState();
5263
+ return;
5264
+ }
5265
+ // Archivo válido
5266
+ this.errorMessage.set(null);
5099
5267
  this.value = file;
5100
5268
  this.onChange(file);
5101
5269
  this.markAsTouched();
5102
5270
  if (this.isReplacing() && file) {
5103
5271
  this.isReplacing.set(false);
5272
+ if (this.debug())
5273
+ this.log('🔄 Reemplazo completado');
5104
5274
  }
5105
5275
  if (this.control) {
5106
5276
  this.control.updateValueAndValidity();
5107
5277
  }
5278
+ // Actualizar estado del control
5279
+ this.updateControlState();
5280
+ const endTime = performance.now();
5281
+ if (this.debug()) {
5282
+ this.log(`⏱️ onSelect completado en ${(endTime - startTime).toFixed(2)}ms`);
5283
+ }
5108
5284
  }
5109
5285
  clear() {
5286
+ if (this.debug())
5287
+ this.log('🧹 clear() - Limpiando componente');
5110
5288
  if (!this.isViewActive())
5111
5289
  return;
5112
5290
  this.fileUpload()?.clear();
5113
5291
  this.value = null;
5292
+ this.errorMessage.set(null);
5114
5293
  this.onChange(null);
5115
5294
  this.markAsTouched();
5116
5295
  if (this.control)
5117
5296
  this.control.updateValueAndValidity();
5297
+ this.updateControlState();
5298
+ if (this.debug())
5299
+ this.log('✅ Limpieza completada');
5118
5300
  }
5119
- writeValue(value) {
5301
+ clearFile() {
5302
+ if (this.debug())
5303
+ this.log('🧹 clearFile() - Limpiando archivo seleccionado');
5120
5304
  if (!this.isViewActive())
5121
5305
  return;
5306
+ this.fileUpload()?.clear();
5307
+ this.value = null;
5308
+ this.errorMessage.set(null);
5309
+ this.onChange(null);
5310
+ this.markAsTouched();
5311
+ if (this.control)
5312
+ this.control.updateValueAndValidity();
5313
+ this.updateControlState();
5122
5314
  if (this.debug())
5123
- this.log('writeValue', { value: value?.name || null });
5315
+ this.log(' Archivo limpiado');
5316
+ }
5317
+ writeValue(value) {
5318
+ if (!this.isViewActive())
5319
+ return;
5320
+ if (this.debug()) {
5321
+ this.log('✍️ writeValue', {
5322
+ value: value?.name || null,
5323
+ size: value?.size ? this.formatFileSize(value.size) : null,
5324
+ });
5325
+ }
5124
5326
  this.value = value;
5125
5327
  }
5126
5328
  registerOnChange(fn) {
5329
+ if (this.debug())
5330
+ this.log('📝 registerOnChange');
5127
5331
  this.onChange = fn;
5128
5332
  }
5129
5333
  registerOnTouched(fn) {
5334
+ if (this.debug())
5335
+ this.log('📝 registerOnTouched');
5130
5336
  this.onTouched = fn;
5131
5337
  }
5132
5338
  setDisabledState(isDisabled) {
5133
5339
  if (!this.isViewActive())
5134
5340
  return;
5341
+ if (this.debug()) {
5342
+ this.log('🔒 setDisabledState', {
5343
+ isDisabled,
5344
+ previousState: this.disabled(),
5345
+ });
5346
+ }
5135
5347
  this.disabled.set(isDisabled);
5136
5348
  }
5137
5349
  startReplace() {
5350
+ if (this.debug())
5351
+ this.log('🔄 startReplace - Iniciando reemplazo');
5138
5352
  if (!this.isViewActive())
5139
5353
  return;
5140
- if (this.disabled())
5354
+ if (this.disabled()) {
5355
+ if (this.debug())
5356
+ this.log('⛔ Reemplazo cancelado - componente deshabilitado');
5141
5357
  return;
5358
+ }
5142
5359
  this.fileUpload()?.clear();
5143
5360
  this.value = null;
5361
+ this.errorMessage.set(null);
5144
5362
  this.onChange(null);
5145
5363
  this.isReplacing.set(true);
5146
5364
  if (this.debug())
5147
- this.log('startReplace');
5365
+ this.log('✅ Reemplazo iniciado');
5148
5366
  }
5149
5367
  cancelReplace() {
5368
+ if (this.debug())
5369
+ this.log('❌ cancelReplace - Cancelando reemplazo');
5150
5370
  if (!this.isViewActive())
5151
5371
  return;
5152
5372
  this.isReplacing.set(false);
5153
5373
  this.fileUpload()?.clear();
5154
5374
  this.value = null;
5375
+ this.errorMessage.set(null);
5155
5376
  this.onChange(null);
5156
5377
  this.markAsTouched();
5157
5378
  if (this.debug())
5158
- this.log('cancelReplace');
5379
+ this.log('✅ Reemplazo cancelado');
5380
+ }
5381
+ choose(event, chooseCallback) {
5382
+ if (this.debug())
5383
+ this.log('🔍 choose - Abriendo selector de archivos');
5384
+ chooseCallback();
5385
+ }
5386
+ formatFileSize(bytes) {
5387
+ if (!bytes)
5388
+ return '';
5389
+ const sizes = ['B', 'KB', 'MB', 'GB'];
5390
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
5391
+ const value = (bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0);
5392
+ return `${value} ${sizes[i]}`;
5159
5393
  }
5160
5394
  // --- Validación ---
5161
5395
  validate(control) {
5396
+ const startTime = performance.now();
5397
+ this._control = control;
5162
5398
  this.control = control;
5163
- if (!this.isViewActive())
5399
+ if (!this.isViewActive()) {
5400
+ if (this.debug())
5401
+ this.log('⚠️ validate - Componente no activo');
5164
5402
  return null;
5165
- if (!this.required())
5403
+ }
5404
+ if (this.debug()) {
5405
+ this.log('🔍 validate - Ejecutando validación', {
5406
+ isRequired: this.isRequired(),
5407
+ hasExistingFile: this.hasExistingFile(),
5408
+ isReplacing: this.isReplacing(),
5409
+ hasValue: !!this.value,
5410
+ valueName: this.value?.name || null,
5411
+ errorMessage: this.errorMessage(),
5412
+ });
5413
+ }
5414
+ // Actualizar estado del control
5415
+ this.updateControlState();
5416
+ // Si hay un mensaje de error, consideramos que la validación falla
5417
+ if (this.errorMessage()) {
5418
+ if (this.debug())
5419
+ this.log('❌ validate - Error: Hay un mensaje de error activo');
5420
+ return { invalidFile: true };
5421
+ }
5422
+ if (!this.isRequired()) {
5423
+ if (this.debug())
5424
+ this.log('✅ validate - No requerido, pasa validación');
5166
5425
  return null;
5167
- if (this.hasExistingFile())
5426
+ }
5427
+ if (this.hasExistingFile()) {
5428
+ if (this.debug())
5429
+ this.log('✅ validate - Archivo existente, pasa validación');
5168
5430
  return null;
5169
- if (this.isReplacing() && !this.value)
5431
+ }
5432
+ if (this.isReplacing() && !this.value) {
5433
+ if (this.debug())
5434
+ this.log('❌ validate - Error: Reemplazando pero sin archivo');
5170
5435
  return { required: true };
5171
- if (!this.hasExistingFile() && !this.value)
5436
+ }
5437
+ if (!this.hasExistingFile() && !this.value) {
5438
+ if (this.debug())
5439
+ this.log('❌ validate - Error: Sin archivo seleccionado');
5172
5440
  return { required: true };
5441
+ }
5442
+ const endTime = performance.now();
5443
+ if (this.debug()) {
5444
+ this.log(`✅ validate - Validación exitosa en ${(endTime - startTime).toFixed(2)}ms`);
5445
+ }
5173
5446
  return null;
5174
5447
  }
5175
5448
  // --- Privados ---
@@ -5179,13 +5452,15 @@ class FileComponent {
5179
5452
  if (!this.touched) {
5180
5453
  this.touched = true;
5181
5454
  this.onTouched();
5455
+ if (this.debug())
5456
+ this.log('👆 markAsTouched - Componente marcado como touched');
5182
5457
  }
5183
5458
  }
5184
5459
  log(method, data) {
5185
5460
  console.log(`[FileComponent] ${method}`, data || '');
5186
5461
  }
5187
5462
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5188
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: FileComponent, isStandalone: true, selector: "dsx-file-upload", inputs: { existingFile: { classPropertyName: "existingFile", publicName: "existingFile", isSignal: true, isRequired: false, transformFunction: null }, existingFileName: { classPropertyName: "existingFileName", publicName: "existingFileName", isSignal: true, isRequired: false, transformFunction: null }, overrideFileName: { classPropertyName: "overrideFileName", publicName: "overrideFileName", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, pTooltipOverride: { classPropertyName: "pTooltipOverride", publicName: "pTooltipOverride", isSignal: true, isRequired: false, transformFunction: null }, tooltipPositionOverride: { classPropertyName: "tooltipPositionOverride", publicName: "tooltipPositionOverride", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, invalidSummary: { classPropertyName: "invalidSummary", publicName: "invalidSummary", isSignal: true, isRequired: false, transformFunction: null }, invalidDetail: { classPropertyName: "invalidDetail", publicName: "invalidDetail", isSignal: true, isRequired: false, transformFunction: null }, invalidSizeSummary: { classPropertyName: "invalidSizeSummary", publicName: "invalidSizeSummary", isSignal: true, isRequired: false, transformFunction: null }, invalidSizeDetail: { classPropertyName: "invalidSizeDetail", publicName: "invalidSizeDetail", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
5463
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: FileComponent, isStandalone: true, selector: "dsx-file-upload", inputs: { existingFile: { classPropertyName: "existingFile", publicName: "existingFile", isSignal: true, isRequired: false, transformFunction: null }, existingFileName: { classPropertyName: "existingFileName", publicName: "existingFileName", isSignal: true, isRequired: false, transformFunction: null }, overrideFileName: { classPropertyName: "overrideFileName", publicName: "overrideFileName", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, pTooltipOverride: { classPropertyName: "pTooltipOverride", publicName: "pTooltipOverride", isSignal: true, isRequired: false, transformFunction: null }, tooltipPositionOverride: { classPropertyName: "tooltipPositionOverride", publicName: "tooltipPositionOverride", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, invalidSummary: { classPropertyName: "invalidSummary", publicName: "invalidSummary", isSignal: true, isRequired: false, transformFunction: null }, invalidDetail: { classPropertyName: "invalidDetail", publicName: "invalidDetail", isSignal: true, isRequired: false, transformFunction: null }, invalidSizeSummary: { classPropertyName: "invalidSizeSummary", publicName: "invalidSizeSummary", isSignal: true, isRequired: false, transformFunction: null }, invalidSizeDetail: { classPropertyName: "invalidSizeDetail", publicName: "invalidSizeDetail", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
5189
5464
  {
5190
5465
  provide: NG_VALUE_ACCESSOR,
5191
5466
  useExisting: forwardRef(() => FileComponent),
@@ -5196,11 +5471,11 @@ class FileComponent {
5196
5471
  useExisting: forwardRef(() => FileComponent),
5197
5472
  multi: true,
5198
5473
  },
5199
- ], viewQueries: [{ propertyName: "fileUpload", first: true, predicate: ["fileUpload"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (showExistingFile() && !existingFile()) {\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"file-name\">\r\n \uD83D\uDCC4 {{ overrideFileName() ?? existingFileName() }}\r\n </span>\r\n\r\n <p-button\r\n icon=\"pi pi-cloud-upload\"\r\n [rounded]=\"true\"\r\n severity=\"info\"\r\n (click)=\"startReplace()\"\r\n [disabled]=\"isReplaceButtonDisabled()\"\r\n pTooltip=\"Reemplazar archivo\"\r\n tooltipPosition=\"top\"\r\n />\r\n </div>\r\n} @else {\r\n <div class=\"flex items-center gap-2\">\r\n <p-fileUpload\r\n #fileUpload\r\n mode=\"basic\"\r\n [accept]=\"accept()\"\r\n [maxFileSize]=\"maxFileSize() * 1024 * 1024\"\r\n [invalidFileTypeMessageSummary]=\"invalidSummary()\"\r\n [invalidFileTypeMessageDetail]=\"invalidDetail()\"\r\n [invalidFileSizeMessageSummary]=\"invalidSizeSummary()\"\r\n [invalidFileSizeMessageDetail]=\"invalidSizeDetail()\"\r\n (onSelect)=\"onSelect($event)\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n [pTooltip]=\"pTooltipOverride()\"\r\n [tooltipPosition]=\"tooltipPositionOverride()\"\r\n />\r\n\r\n @if (isReplacing() || existingFile()) {\r\n <p-button\r\n icon=\"pi pi-times\"\r\n [rounded]=\"true\"\r\n severity=\"secondary\"\r\n (click)=\"cancelReplace()\"\r\n [disabled]=\"disabled()\"\r\n pTooltip=\"Cancelar reemplazo\"\r\n tooltipPosition=\"top\"\r\n />\r\n }\r\n </div>\r\n}\r\n", styles: [".file-name{display:inline-block;font-size:clamp(.85rem,1vw + .5rem,1.4rem);font-weight:500;color:#2c3e50;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}\n"], dependencies: [{ kind: "component", type: FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }] });
5474
+ ], viewQueries: [{ propertyName: "fileUpload", first: true, predicate: ["fileUpload"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (showExistingFile() && !existingFile()) {\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"file-name\">\r\n \uD83D\uDCC4 {{ overrideFileName() ?? existingFileName() }}\r\n </span>\r\n\r\n <p-button\r\n icon=\"pi pi-cloud-upload\"\r\n [rounded]=\"true\"\r\n severity=\"info\"\r\n (click)=\"startReplace()\"\r\n [disabled]=\"isReplaceButtonDisabled()\"\r\n pTooltip=\"Reemplazar archivo\"\r\n tooltipPosition=\"top\"\r\n />\r\n </div>\r\n} @else {\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"flex items-center gap-2\">\r\n <p-fileUpload\r\n fluid\r\n #fileUpload\r\n chooseIcon=\"fa-solid fa-file-arrow-up\"\r\n [accept]=\"accept()\"\r\n [maxFileSize]=\"maxFileSize() * 1024 * 1024\"\r\n [invalidFileTypeMessageSummary]=\"invalidSummary()\"\r\n [invalidFileTypeMessageDetail]=\"invalidDetail()\"\r\n [invalidFileSizeMessageSummary]=\"invalidSizeSummary()\"\r\n [invalidFileSizeMessageDetail]=\"invalidSizeDetail()\"\r\n (onSelect)=\"onSelect($event)\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n [pTooltip]=\"`${pTooltipOverride()} ${allowedFileTypeLabel()}`\"\r\n [tooltipPosition]=\"tooltipPositionOverride()\"\r\n >\r\n <ng-template\r\n #header\r\n let-files\r\n let-chooseCallback=\"chooseCallback\"\r\n let-clearCallback=\"clearCallback\"\r\n >\r\n <!-- Mostrar el bot\u00F3n de upload SOLO si NO hay archivo seleccionado -->\r\n @if (!files || files.length === 0) {\r\n <p-button\r\n (onClick)=\"choose($event, chooseCallback)\"\r\n icon=\"fa-solid fa-cloud-arrow-up\"\r\n [rounded]=\"true\"\r\n [outlined]=\"true\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n />\r\n }\r\n\r\n <span class=\"file-upload-placeholder\">\r\n <i class=\"fa-regular fa-file-lines placeholder-icon\"></i>\r\n <span class=\"placeholder-text\">\r\n @if (files && files.length > 0) {\r\n <span class=\"file-selected\">\r\n <i class=\"fa-regular fa-file-pdf file-icon\"></i>\r\n {{ files[0]?.name }}\r\n <span class=\"file-size\"\r\n >({{ formatFileSize(files[0]?.size) }})</span\r\n >\r\n </span>\r\n } @else if (allowedFileTypeLabel() !== \"archivo\") {\r\n <span class=\"empty-warning\">Ning\u00FAn</span>\r\n <span class=\"file-type empty-warning-type\">{{\r\n allowedFileTypeLabel()\r\n }}</span>\r\n <span class=\"empty-warning\">seleccionado</span>\r\n } @else {\r\n <span class=\"empty-warning\">Ning\u00FAn archivo seleccionado</span>\r\n }\r\n </span>\r\n </span>\r\n\r\n <!-- Mostrar bot\u00F3n de limpiar SOLO si hay archivo seleccionado -->\r\n @if (files && files.length > 0) {\r\n <p-button\r\n icon=\"fa-solid fa-xmark\"\r\n [rounded]=\"true\"\r\n [outlined]=\"true\"\r\n (onClick)=\"clearFile()\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n styleClass=\"clear-button\"\r\n />\r\n }\r\n </ng-template>\r\n <!-- Ocultar la vista previa por defecto -->\r\n <ng-template #content></ng-template>\r\n <ng-template #file></ng-template>\r\n </p-fileUpload>\r\n\r\n @if (isReplacing() || existingFile()) {\r\n <p-button\r\n icon=\"pi pi-times\"\r\n [rounded]=\"true\"\r\n severity=\"secondary\"\r\n (click)=\"cancelReplace()\"\r\n [disabled]=\"disabled()\"\r\n pTooltip=\"Cancelar reemplazo\"\r\n tooltipPosition=\"top\"\r\n />\r\n }\r\n </div>\r\n\r\n <!-- Mensajes de error (validaci\u00F3n de PrimeNG) -->\r\n @if (errorMessage()) {\r\n <div class=\"file-error-message\">\r\n <i class=\"pi pi-exclamation-circle\"></i>\r\n {{ errorMessage() }}\r\n </div>\r\n }\r\n\r\n <!-- Mensaje de error del formulario (dsx-message-error) -->\r\n @if (showError() && errorControl()) {\r\n <dsx-message-error [control]=\"errorControl()\" />\r\n }\r\n </div>\r\n}\r\n\r\n<!-- Panel de Debug -->\r\n@if (debug() && isDevMode) {\r\n <div class=\"debug-panel\">\r\n <div class=\"debug-header\">\r\n <div class=\"debug-info\">\r\n <strong>\uD83D\uDD0D Debug File Upload:</strong>\r\n <span class=\"debug-item\"\r\n >Archivo: {{ value ? value.name : \"Ninguno\" }}</span\r\n >\r\n <span class=\"debug-item\"\r\n >Tama\u00F1o: {{ value ? formatFileSize(value.size) : \"-\" }}</span\r\n >\r\n <span class=\"debug-item\">Tipo: {{ value ? value.type : \"-\" }}</span>\r\n <span\r\n class=\"debug-item\"\r\n [style.color]=\"controlValid() ? '#10b981' : '#ef4444'\"\r\n >\r\n {{ controlValid() ? \"\u2705 V\u00E1lido\" : \"\u274C Inv\u00E1lido\" }}\r\n </span>\r\n <!-- Cambiar esta l\u00EDnea en el panel de debug -->\r\n <span class=\"debug-item\"\r\n >Required: {{ isRequired() ? \"\u2705\" : \"\u274C\" }}</span\r\n >\r\n <span class=\"debug-item\"\r\n >Reemplazando: {{ isReplacing() ? \"\u2705\" : \"\u274C\" }}</span\r\n >\r\n </div>\r\n <div class=\"debug-actions\">\r\n <button (click)=\"forceReload()\" class=\"debug-btn\" type=\"button\">\r\n \uD83D\uDD04 Recargar\r\n </button>\r\n <button (click)=\"clearFile()\" class=\"debug-btn\" type=\"button\">\r\n \uD83E\uDDF9 Limpiar\r\n </button>\r\n </div>\r\n </div>\r\n @if (errorMessage()) {\r\n <div class=\"debug-error\">\r\n <strong>\u26A0\uFE0F Error activo:</strong> {{ errorMessage() }}\r\n </div>\r\n }\r\n @if (controlErrors()) {\r\n <div class=\"debug-errors\">\r\n <strong>\u274C Errores del formulario:</strong>\r\n <pre>{{ controlErrors() | json }}</pre>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".file-name{display:inline-block;font-size:clamp(.85rem,1vw + .5rem,1.4rem);font-weight:500;color:#2c3e50;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}::ng-deep .p-fileupload .p-fileupload-header{display:flex!important;align-items:center!important;gap:.75rem!important;padding:0!important;border:none!important;background:transparent!important}::ng-deep .p-fileupload .p-fileupload-header .p-button{background:transparent!important;border:2px dashed #d1d5db!important;border-radius:8px!important;padding:.5rem 1rem!important;margin:.5rem!important;min-height:40px!important;min-width:40px!important;transition:all .25s ease!important;color:#6b7280!important}::ng-deep .p-fileupload .p-fileupload-header .p-button:not(:disabled):hover{border-color:#3b82f6!important;background:#eff6ff!important;box-shadow:0 0 0 3px #3b82f61a!important;color:#3b82f6!important}::ng-deep .p-fileupload .p-fileupload-header .p-button:disabled{opacity:.5!important;cursor:not-allowed!important;border-color:#e5e7eb!important;color:#9ca3af!important}::ng-deep .p-fileupload .p-fileupload-header .p-button .p-button-icon{font-size:1.2rem!important;transition:color .25s ease!important}.file-upload-placeholder{display:flex;align-items:center;gap:.75rem;pointer-events:none;white-space:nowrap;padding:0 .5rem}.placeholder-icon{font-size:1.35rem;color:#94a3b8;transition:all .3s ease}.placeholder-text{color:#94a3b8;font-size:.875rem;font-weight:400;transition:all .3s ease;letter-spacing:.01em}.placeholder-text .file-type{color:#64748b;font-size:.8rem;font-weight:600;transition:all .3s ease;letter-spacing:.03em;text-transform:uppercase;background:#f1f5f9;padding:.1rem .4rem;border-radius:4px;margin:0 .1rem}.placeholder-text .empty-warning{color:#d97706;font-weight:500;transition:color .3s ease}.placeholder-text .file-type.empty-warning-type{color:#b45309;background:#fef3c7;font-weight:600}.file-selected{display:flex;align-items:center;gap:.5rem;color:#1f2937;font-weight:500;font-size:.85rem}.file-selected .file-icon{font-size:1.2rem;color:#bb1589}.file-selected .file-size{color:#9ca3af;font-weight:400;font-size:.75rem;margin-left:.25rem}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-icon{color:#3b82f6}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-text{color:#1f2937}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .file-type{color:#2563eb;background:#dbeafe}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-text .empty-warning{color:#2563eb}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .file-type.empty-warning-type{color:#2563eb;background:#dbeafe}::ng-deep .p-fileupload .p-fileupload-buttonbar{display:none!important}::ng-deep .p-fileupload .p-fileupload-content{padding:0!important;border:none!important;background:transparent!important}::ng-deep .p-button.clear-button{background:transparent!important;border:none!important;border-radius:8px!important;padding:.5rem!important;min-width:40px!important;height:40px!important;transition:all .25s ease!important;display:flex!important;align-items:center!important;justify-content:center!important;box-shadow:none!important}::ng-deep .p-fileupload .p-fileupload-header .p-button.clear-button:not(:disabled):hover{background:none!important;border:none!important;box-shadow:none!important;transform:scale(1.05)!important}::ng-deep .p-button.clear-button .p-button-icon{color:#6b7280!important;font-size:1.1rem!important;transition:all .25s ease!important}::ng-deep .p-button.clear-button:not(:disabled):hover .p-button-icon{color:#ef4444!important}::ng-deep .p-button.clear-button:disabled{opacity:.5!important;cursor:not-allowed!important}.file-error-message{display:flex;align-items:center;gap:.5rem;color:#dc2626;font-size:.875rem;font-weight:400;padding:.25rem .75rem;background:#fef2f2;border-radius:6px;border:1px solid #fecaca;animation:slideDown .3s ease}.file-error-message i{font-size:1rem;color:#dc2626;flex-shrink:0}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.debug-panel{margin-top:8px;font-size:11px;color:#374151;background:#f8fafc;padding:8px 12px;border-radius:6px;font-family:monospace;border:1px solid #e2e8f0;width:100%}.debug-header{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap}.debug-info{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.debug-item{font-size:11px;color:#475569}.debug-item strong{color:#1e293b}.debug-actions{display:flex;gap:6px}.debug-btn{font-size:10px;padding:2px 10px;border-radius:4px;border:1px solid #cbd5e1;background:#fff;cursor:pointer;transition:all .2s ease;color:#475569;font-family:monospace}.debug-btn:hover{background:#f1f5f9;border-color:#94a3b8}.debug-error,.debug-errors{margin-top:6px;padding:4px 8px;background:#fef2f2;border-radius:4px;color:#dc2626;font-size:11px;border:1px solid #fecaca}.debug-errors pre{margin:4px 0 0;font-size:10px;color:#991b1b;white-space:pre-wrap;word-break:break-all}@media(max-width:640px){.placeholder-icon{font-size:1.1rem!important}.placeholder-text{font-size:.75rem!important}.placeholder-text .file-type{font-size:.7rem!important;padding:.05rem .3rem!important}.file-selected{font-size:.75rem!important}.file-selected .file-icon{font-size:1rem!important}.file-selected .file-size{font-size:.65rem!important}::ng-deep .p-fileupload .p-fileupload-header .p-button{padding:.4rem .75rem!important;min-height:34px!important;min-width:34px!important}::ng-deep .p-button.clear-button{min-width:34px!important;height:34px!important}.file-error-message{font-size:.75rem!important;padding:.2rem .5rem!important}.debug-panel{font-size:10px;padding:6px 8px}.debug-item{font-size:10px}.debug-btn{font-size:9px;padding:2px 6px}}\n"], dependencies: [{ kind: "component", type: FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: AppMessageErrorComponent, selector: "dsx-message-error", inputs: ["control", "form", "debugMode"] }, { kind: "pipe", type: JsonPipe, name: "json" }] });
5200
5475
  }
5201
5476
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FileComponent, decorators: [{
5202
5477
  type: Component,
5203
- args: [{ selector: 'dsx-file-upload', imports: [FileUpload, Tooltip, Button], providers: [
5478
+ args: [{ selector: 'dsx-file-upload', imports: [FileUpload, Tooltip, Button, JsonPipe, AppMessageErrorComponent], providers: [
5204
5479
  {
5205
5480
  provide: NG_VALUE_ACCESSOR,
5206
5481
  useExisting: forwardRef(() => FileComponent),
@@ -5211,119 +5486,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
5211
5486
  useExisting: forwardRef(() => FileComponent),
5212
5487
  multi: true,
5213
5488
  },
5214
- ], template: "@if (showExistingFile() && !existingFile()) {\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"file-name\">\r\n \uD83D\uDCC4 {{ overrideFileName() ?? existingFileName() }}\r\n </span>\r\n\r\n <p-button\r\n icon=\"pi pi-cloud-upload\"\r\n [rounded]=\"true\"\r\n severity=\"info\"\r\n (click)=\"startReplace()\"\r\n [disabled]=\"isReplaceButtonDisabled()\"\r\n pTooltip=\"Reemplazar archivo\"\r\n tooltipPosition=\"top\"\r\n />\r\n </div>\r\n} @else {\r\n <div class=\"flex items-center gap-2\">\r\n <p-fileUpload\r\n #fileUpload\r\n mode=\"basic\"\r\n [accept]=\"accept()\"\r\n [maxFileSize]=\"maxFileSize() * 1024 * 1024\"\r\n [invalidFileTypeMessageSummary]=\"invalidSummary()\"\r\n [invalidFileTypeMessageDetail]=\"invalidDetail()\"\r\n [invalidFileSizeMessageSummary]=\"invalidSizeSummary()\"\r\n [invalidFileSizeMessageDetail]=\"invalidSizeDetail()\"\r\n (onSelect)=\"onSelect($event)\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n [pTooltip]=\"pTooltipOverride()\"\r\n [tooltipPosition]=\"tooltipPositionOverride()\"\r\n />\r\n\r\n @if (isReplacing() || existingFile()) {\r\n <p-button\r\n icon=\"pi pi-times\"\r\n [rounded]=\"true\"\r\n severity=\"secondary\"\r\n (click)=\"cancelReplace()\"\r\n [disabled]=\"disabled()\"\r\n pTooltip=\"Cancelar reemplazo\"\r\n tooltipPosition=\"top\"\r\n />\r\n }\r\n </div>\r\n}\r\n", styles: [".file-name{display:inline-block;font-size:clamp(.85rem,1vw + .5rem,1.4rem);font-weight:500;color:#2c3e50;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}\n"] }]
5215
- }], ctorParameters: () => [], propDecorators: { existingFile: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFile", required: false }] }], existingFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFileName", required: false }] }], overrideFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "overrideFileName", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], pTooltipOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "pTooltipOverride", required: false }] }], tooltipPositionOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipPositionOverride", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], invalidSummary: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidSummary", required: false }] }], invalidDetail: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidDetail", required: false }] }], invalidSizeSummary: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidSizeSummary", required: false }] }], invalidSizeDetail: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidSizeDetail", required: false }] }], fileUpload: [{ type: i0.ViewChild, args: ['fileUpload', { isSignal: true }] }] } });
5216
-
5217
- class JsonHighlightPipe {
5218
- /**
5219
- * Stringifica el JSON manteniendo arrays en una línea para una vista más compacta
5220
- */
5221
- compactStringify(obj, indent = 0) {
5222
- const spaces = ' '.repeat(indent);
5223
- if (obj === null) {
5224
- return 'null';
5225
- }
5226
- if (typeof obj !== 'object') {
5227
- if (typeof obj === 'string') {
5228
- return JSON.stringify(obj);
5229
- }
5230
- return String(obj);
5231
- }
5232
- if (obj instanceof Date) {
5233
- return JSON.stringify(obj.toISOString());
5234
- }
5235
- if (Array.isArray(obj)) {
5236
- // Mantener arrays en una línea
5237
- const items = obj.map((item) => {
5238
- if (item instanceof Date) {
5239
- return JSON.stringify(item.toISOString());
5240
- }
5241
- if (typeof item === 'object' && item !== null) {
5242
- return this.compactStringify(item, indent);
5243
- }
5244
- return typeof item === 'string'
5245
- ? JSON.stringify(item)
5246
- : item === null
5247
- ? 'null'
5248
- : String(item);
5249
- });
5250
- return `[${items.join(', ')}]`;
5251
- }
5252
- // Para objetos, mantener la indentación
5253
- const keys = Object.keys(obj);
5254
- if (keys.length === 0) {
5255
- return '{}';
5256
- }
5257
- const innerIndent = indent + 2;
5258
- const innerSpaces = ' '.repeat(innerIndent);
5259
- const lines = keys.map((key) => {
5260
- const value = obj[key];
5261
- const stringified = value instanceof Date
5262
- ? JSON.stringify(value.toISOString())
5263
- : typeof value === 'object' && value !== null
5264
- ? this.compactStringify(value, innerIndent)
5265
- : typeof value === 'string'
5266
- ? JSON.stringify(value)
5267
- : value === null
5268
- ? 'null'
5269
- : String(value);
5270
- return `${innerSpaces}"${key}": ${stringified}`;
5271
- });
5272
- return `{\n${lines.join(',\n')}\n${spaces}}`;
5273
- }
5274
- instanceId = Math.random().toString(36).slice(2, 8);
5275
- executions = 0;
5276
- transform(value) {
5277
- if (isDevMode()) {
5278
- this.executions++;
5279
- if (this.executions % 10 === 0) {
5280
- console.log(`[jsonHighlight:${this.instanceId}] ${this.executions}`);
5281
- }
5282
- }
5283
- if (!value) {
5284
- return '';
5285
- }
5286
- const json = this.compactStringify(value);
5287
- // Detecta fechas ISO: "YYYY-MM-DDTHH:MM:SS.mmmZ"
5288
- const ISO_DATE_RE = /^"(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}\.\d{3}Z)"$/;
5289
- // Aplica estilo y colores para diferenciar claves, valores, booleanos, números, etc.
5290
- return json
5291
- .replace(/&/g, '&amp;')
5292
- .replace(/</g, '&lt;')
5293
- .replace(/>/g, '&gt;')
5294
- .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?)/g, (match) => {
5295
- // Si es una clave (tiene dos puntos al final)
5296
- if (/:$/.test(match)) {
5297
- return `<span class="json-key">${match}</span>`;
5298
- }
5299
- // Detectar fechas ISO (YYYY-MM-DDTHH:MM:SS.mmmZ)
5300
- const isoMatch = match.match(ISO_DATE_RE);
5301
- if (isoMatch) {
5302
- return `<span class="json-string json-date">"<span class="json-date-part">${isoMatch[1]}</span><span class="json-date-sep">T</span><span class="json-date-time">${isoMatch[2]}</span>"</span>`;
5303
- }
5304
- // Si es un string (no tiene dos puntos)
5305
- return `<span class="json-string">${match}</span>`;
5306
- })
5307
- .replace(/\b(true|false|null)\b/g, (match) => {
5308
- if (match === 'null') {
5309
- return '<span class="json-null">null</span>';
5310
- }
5311
- return `<span class="json-boolean">${match}</span>`;
5312
- })
5313
- .replace(/\b(\d+\.?\d*)\b/g, '<span class="json-number">$1</span>')
5314
- .replace(/[{}\[\]]/g, '<span class="json-bracket">$&</span>')
5315
- .replace(/,/g, '<span class="json-comma">$&</span>')
5316
- .replace(/:/g, '<span class="json-colon">$&</span>');
5317
- }
5318
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
5319
- static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, isStandalone: true, name: "jsonHighlight" });
5320
- }
5321
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, decorators: [{
5322
- type: Pipe,
5323
- args: [{
5324
- name: 'jsonHighlight',
5325
- }]
5326
- }] });
5489
+ ], template: "@if (showExistingFile() && !existingFile()) {\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"file-name\">\r\n \uD83D\uDCC4 {{ overrideFileName() ?? existingFileName() }}\r\n </span>\r\n\r\n <p-button\r\n icon=\"pi pi-cloud-upload\"\r\n [rounded]=\"true\"\r\n severity=\"info\"\r\n (click)=\"startReplace()\"\r\n [disabled]=\"isReplaceButtonDisabled()\"\r\n pTooltip=\"Reemplazar archivo\"\r\n tooltipPosition=\"top\"\r\n />\r\n </div>\r\n} @else {\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"flex items-center gap-2\">\r\n <p-fileUpload\r\n fluid\r\n #fileUpload\r\n chooseIcon=\"fa-solid fa-file-arrow-up\"\r\n [accept]=\"accept()\"\r\n [maxFileSize]=\"maxFileSize() * 1024 * 1024\"\r\n [invalidFileTypeMessageSummary]=\"invalidSummary()\"\r\n [invalidFileTypeMessageDetail]=\"invalidDetail()\"\r\n [invalidFileSizeMessageSummary]=\"invalidSizeSummary()\"\r\n [invalidFileSizeMessageDetail]=\"invalidSizeDetail()\"\r\n (onSelect)=\"onSelect($event)\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n [pTooltip]=\"`${pTooltipOverride()} ${allowedFileTypeLabel()}`\"\r\n [tooltipPosition]=\"tooltipPositionOverride()\"\r\n >\r\n <ng-template\r\n #header\r\n let-files\r\n let-chooseCallback=\"chooseCallback\"\r\n let-clearCallback=\"clearCallback\"\r\n >\r\n <!-- Mostrar el bot\u00F3n de upload SOLO si NO hay archivo seleccionado -->\r\n @if (!files || files.length === 0) {\r\n <p-button\r\n (onClick)=\"choose($event, chooseCallback)\"\r\n icon=\"fa-solid fa-cloud-arrow-up\"\r\n [rounded]=\"true\"\r\n [outlined]=\"true\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n />\r\n }\r\n\r\n <span class=\"file-upload-placeholder\">\r\n <i class=\"fa-regular fa-file-lines placeholder-icon\"></i>\r\n <span class=\"placeholder-text\">\r\n @if (files && files.length > 0) {\r\n <span class=\"file-selected\">\r\n <i class=\"fa-regular fa-file-pdf file-icon\"></i>\r\n {{ files[0]?.name }}\r\n <span class=\"file-size\"\r\n >({{ formatFileSize(files[0]?.size) }})</span\r\n >\r\n </span>\r\n } @else if (allowedFileTypeLabel() !== \"archivo\") {\r\n <span class=\"empty-warning\">Ning\u00FAn</span>\r\n <span class=\"file-type empty-warning-type\">{{\r\n allowedFileTypeLabel()\r\n }}</span>\r\n <span class=\"empty-warning\">seleccionado</span>\r\n } @else {\r\n <span class=\"empty-warning\">Ning\u00FAn archivo seleccionado</span>\r\n }\r\n </span>\r\n </span>\r\n\r\n <!-- Mostrar bot\u00F3n de limpiar SOLO si hay archivo seleccionado -->\r\n @if (files && files.length > 0) {\r\n <p-button\r\n icon=\"fa-solid fa-xmark\"\r\n [rounded]=\"true\"\r\n [outlined]=\"true\"\r\n (onClick)=\"clearFile()\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n styleClass=\"clear-button\"\r\n />\r\n }\r\n </ng-template>\r\n <!-- Ocultar la vista previa por defecto -->\r\n <ng-template #content></ng-template>\r\n <ng-template #file></ng-template>\r\n </p-fileUpload>\r\n\r\n @if (isReplacing() || existingFile()) {\r\n <p-button\r\n icon=\"pi pi-times\"\r\n [rounded]=\"true\"\r\n severity=\"secondary\"\r\n (click)=\"cancelReplace()\"\r\n [disabled]=\"disabled()\"\r\n pTooltip=\"Cancelar reemplazo\"\r\n tooltipPosition=\"top\"\r\n />\r\n }\r\n </div>\r\n\r\n <!-- Mensajes de error (validaci\u00F3n de PrimeNG) -->\r\n @if (errorMessage()) {\r\n <div class=\"file-error-message\">\r\n <i class=\"pi pi-exclamation-circle\"></i>\r\n {{ errorMessage() }}\r\n </div>\r\n }\r\n\r\n <!-- Mensaje de error del formulario (dsx-message-error) -->\r\n @if (showError() && errorControl()) {\r\n <dsx-message-error [control]=\"errorControl()\" />\r\n }\r\n </div>\r\n}\r\n\r\n<!-- Panel de Debug -->\r\n@if (debug() && isDevMode) {\r\n <div class=\"debug-panel\">\r\n <div class=\"debug-header\">\r\n <div class=\"debug-info\">\r\n <strong>\uD83D\uDD0D Debug File Upload:</strong>\r\n <span class=\"debug-item\"\r\n >Archivo: {{ value ? value.name : \"Ninguno\" }}</span\r\n >\r\n <span class=\"debug-item\"\r\n >Tama\u00F1o: {{ value ? formatFileSize(value.size) : \"-\" }}</span\r\n >\r\n <span class=\"debug-item\">Tipo: {{ value ? value.type : \"-\" }}</span>\r\n <span\r\n class=\"debug-item\"\r\n [style.color]=\"controlValid() ? '#10b981' : '#ef4444'\"\r\n >\r\n {{ controlValid() ? \"\u2705 V\u00E1lido\" : \"\u274C Inv\u00E1lido\" }}\r\n </span>\r\n <!-- Cambiar esta l\u00EDnea en el panel de debug -->\r\n <span class=\"debug-item\"\r\n >Required: {{ isRequired() ? \"\u2705\" : \"\u274C\" }}</span\r\n >\r\n <span class=\"debug-item\"\r\n >Reemplazando: {{ isReplacing() ? \"\u2705\" : \"\u274C\" }}</span\r\n >\r\n </div>\r\n <div class=\"debug-actions\">\r\n <button (click)=\"forceReload()\" class=\"debug-btn\" type=\"button\">\r\n \uD83D\uDD04 Recargar\r\n </button>\r\n <button (click)=\"clearFile()\" class=\"debug-btn\" type=\"button\">\r\n \uD83E\uDDF9 Limpiar\r\n </button>\r\n </div>\r\n </div>\r\n @if (errorMessage()) {\r\n <div class=\"debug-error\">\r\n <strong>\u26A0\uFE0F Error activo:</strong> {{ errorMessage() }}\r\n </div>\r\n }\r\n @if (controlErrors()) {\r\n <div class=\"debug-errors\">\r\n <strong>\u274C Errores del formulario:</strong>\r\n <pre>{{ controlErrors() | json }}</pre>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".file-name{display:inline-block;font-size:clamp(.85rem,1vw + .5rem,1.4rem);font-weight:500;color:#2c3e50;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}::ng-deep .p-fileupload .p-fileupload-header{display:flex!important;align-items:center!important;gap:.75rem!important;padding:0!important;border:none!important;background:transparent!important}::ng-deep .p-fileupload .p-fileupload-header .p-button{background:transparent!important;border:2px dashed #d1d5db!important;border-radius:8px!important;padding:.5rem 1rem!important;margin:.5rem!important;min-height:40px!important;min-width:40px!important;transition:all .25s ease!important;color:#6b7280!important}::ng-deep .p-fileupload .p-fileupload-header .p-button:not(:disabled):hover{border-color:#3b82f6!important;background:#eff6ff!important;box-shadow:0 0 0 3px #3b82f61a!important;color:#3b82f6!important}::ng-deep .p-fileupload .p-fileupload-header .p-button:disabled{opacity:.5!important;cursor:not-allowed!important;border-color:#e5e7eb!important;color:#9ca3af!important}::ng-deep .p-fileupload .p-fileupload-header .p-button .p-button-icon{font-size:1.2rem!important;transition:color .25s ease!important}.file-upload-placeholder{display:flex;align-items:center;gap:.75rem;pointer-events:none;white-space:nowrap;padding:0 .5rem}.placeholder-icon{font-size:1.35rem;color:#94a3b8;transition:all .3s ease}.placeholder-text{color:#94a3b8;font-size:.875rem;font-weight:400;transition:all .3s ease;letter-spacing:.01em}.placeholder-text .file-type{color:#64748b;font-size:.8rem;font-weight:600;transition:all .3s ease;letter-spacing:.03em;text-transform:uppercase;background:#f1f5f9;padding:.1rem .4rem;border-radius:4px;margin:0 .1rem}.placeholder-text .empty-warning{color:#d97706;font-weight:500;transition:color .3s ease}.placeholder-text .file-type.empty-warning-type{color:#b45309;background:#fef3c7;font-weight:600}.file-selected{display:flex;align-items:center;gap:.5rem;color:#1f2937;font-weight:500;font-size:.85rem}.file-selected .file-icon{font-size:1.2rem;color:#bb1589}.file-selected .file-size{color:#9ca3af;font-weight:400;font-size:.75rem;margin-left:.25rem}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-icon{color:#3b82f6}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-text{color:#1f2937}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .file-type{color:#2563eb;background:#dbeafe}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-text .empty-warning{color:#2563eb}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .file-type.empty-warning-type{color:#2563eb;background:#dbeafe}::ng-deep .p-fileupload .p-fileupload-buttonbar{display:none!important}::ng-deep .p-fileupload .p-fileupload-content{padding:0!important;border:none!important;background:transparent!important}::ng-deep .p-button.clear-button{background:transparent!important;border:none!important;border-radius:8px!important;padding:.5rem!important;min-width:40px!important;height:40px!important;transition:all .25s ease!important;display:flex!important;align-items:center!important;justify-content:center!important;box-shadow:none!important}::ng-deep .p-fileupload .p-fileupload-header .p-button.clear-button:not(:disabled):hover{background:none!important;border:none!important;box-shadow:none!important;transform:scale(1.05)!important}::ng-deep .p-button.clear-button .p-button-icon{color:#6b7280!important;font-size:1.1rem!important;transition:all .25s ease!important}::ng-deep .p-button.clear-button:not(:disabled):hover .p-button-icon{color:#ef4444!important}::ng-deep .p-button.clear-button:disabled{opacity:.5!important;cursor:not-allowed!important}.file-error-message{display:flex;align-items:center;gap:.5rem;color:#dc2626;font-size:.875rem;font-weight:400;padding:.25rem .75rem;background:#fef2f2;border-radius:6px;border:1px solid #fecaca;animation:slideDown .3s ease}.file-error-message i{font-size:1rem;color:#dc2626;flex-shrink:0}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.debug-panel{margin-top:8px;font-size:11px;color:#374151;background:#f8fafc;padding:8px 12px;border-radius:6px;font-family:monospace;border:1px solid #e2e8f0;width:100%}.debug-header{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap}.debug-info{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.debug-item{font-size:11px;color:#475569}.debug-item strong{color:#1e293b}.debug-actions{display:flex;gap:6px}.debug-btn{font-size:10px;padding:2px 10px;border-radius:4px;border:1px solid #cbd5e1;background:#fff;cursor:pointer;transition:all .2s ease;color:#475569;font-family:monospace}.debug-btn:hover{background:#f1f5f9;border-color:#94a3b8}.debug-error,.debug-errors{margin-top:6px;padding:4px 8px;background:#fef2f2;border-radius:4px;color:#dc2626;font-size:11px;border:1px solid #fecaca}.debug-errors pre{margin:4px 0 0;font-size:10px;color:#991b1b;white-space:pre-wrap;word-break:break-all}@media(max-width:640px){.placeholder-icon{font-size:1.1rem!important}.placeholder-text{font-size:.75rem!important}.placeholder-text .file-type{font-size:.7rem!important;padding:.05rem .3rem!important}.file-selected{font-size:.75rem!important}.file-selected .file-icon{font-size:1rem!important}.file-selected .file-size{font-size:.65rem!important}::ng-deep .p-fileupload .p-fileupload-header .p-button{padding:.4rem .75rem!important;min-height:34px!important;min-width:34px!important}::ng-deep .p-button.clear-button{min-width:34px!important;height:34px!important}.file-error-message{font-size:.75rem!important;padding:.2rem .5rem!important}.debug-panel{font-size:10px;padding:6px 8px}.debug-item{font-size:10px}.debug-btn{font-size:9px;padding:2px 6px}}\n"] }]
5490
+ }], ctorParameters: () => [], propDecorators: { existingFile: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFile", required: false }] }], existingFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFileName", required: false }] }], overrideFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "overrideFileName", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], pTooltipOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "pTooltipOverride", required: false }] }], tooltipPositionOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipPositionOverride", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], invalidSummary: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidSummary", required: false }] }], invalidDetail: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidDetail", required: false }] }], invalidSizeSummary: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidSizeSummary", required: false }] }], invalidSizeDetail: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidSizeDetail", required: false }] }], fileUpload: [{ type: i0.ViewChild, args: ['fileUpload', { isSignal: true }] }] } });
5327
5491
 
5328
5492
  function uid() {
5329
5493
  return Math.random().toString(36).substring(2, 8);
@@ -5331,67 +5495,245 @@ function uid() {
5331
5495
  class JsonValuesDebujComponent {
5332
5496
  form = input(null, ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
5333
5497
  debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
5498
+ debounceTime = input(300, ...(ngDevMode ? [{ debugName: "debounceTime" }] : /* istanbul ignore next */ [])); // ✅ Configurable
5334
5499
  debugValue = signal(null, ...(ngDevMode ? [{ debugName: "debugValue" }] : /* istanbul ignore next */ []));
5500
+ serializedValue = signal('', ...(ngDevMode ? [{ debugName: "serializedValue" }] : /* istanbul ignore next */ []));
5501
+ fileInfo = signal(null, ...(ngDevMode ? [{ debugName: "fileInfo" }] : /* istanbul ignore next */ []));
5502
+ isDevModeSignal = computed(() => isDevMode(), ...(ngDevMode ? [{ debugName: "isDevModeSignal" }] : /* istanbul ignore next */ []));
5503
+ isDebugEnabledSignal = computed(() => {
5504
+ const isDev = this.isDevModeSignal();
5505
+ const debug = this.debug();
5506
+ const result = isDev && debug;
5507
+ if (this._lastDebugState !== result) {
5508
+ this._lastDebugState = result;
5509
+ if (result) {
5510
+ console.log(`[JsonDebug:${this.id}] 📝 Logs activados`, {
5511
+ isDevMode: isDev,
5512
+ debug: debug,
5513
+ });
5514
+ }
5515
+ }
5516
+ return result;
5517
+ }, ...(ngDevMode ? [{ debugName: "isDebugEnabledSignal" }] : /* istanbul ignore next */ []));
5518
+ _lastDebugState = null;
5519
+ _lastVisualState = null;
5335
5520
  id = uid();
5336
5521
  subscription;
5337
5522
  changesCount = 0;
5338
- // Indica si debemos mostrar el componente visualmente
5339
- get shouldShowVisual() {
5340
- // Solo mostrar visualmente en modo desarrollo
5341
- return isDevMode();
5342
- }
5343
- // Indica si debemos mostrar logs en consola
5344
- get shouldShowLogs() {
5345
- // Solo logs en desarrollo Y cuando debug está explícitamente true
5346
- return isDevMode() && this.debug() === true;
5347
- }
5523
+ isInitialized = false;
5524
+ lastLogTime = 0;
5525
+ shouldShowVisual = computed(() => {
5526
+ const show = this.isDevModeSignal();
5527
+ if (this._lastVisualState !== show) {
5528
+ this._lastVisualState = show;
5529
+ if (this.shouldShowLogs()) {
5530
+ console.log(`[JsonDebug:${this.id}] 👁️ Visual: ${show}`, {
5531
+ isDevMode: isDevMode(),
5532
+ reason: show ? 'Modo desarrollo activo' : 'Modo producción',
5533
+ });
5534
+ }
5535
+ }
5536
+ return show;
5537
+ }, ...(ngDevMode ? [{ debugName: "shouldShowVisual" }] : /* istanbul ignore next */ []));
5538
+ shouldShowLogs = computed(() => {
5539
+ const isDev = this.isDevModeSignal();
5540
+ const debug = this.debug();
5541
+ const show = isDev && debug;
5542
+ if (this._lastDebugState !== show) {
5543
+ this._lastDebugState = show;
5544
+ if (show) {
5545
+ console.log(`[JsonDebug:${this.id}] 📝 Logs activados`, {
5546
+ isDevMode: isDev,
5547
+ debug: debug,
5548
+ });
5549
+ }
5550
+ }
5551
+ return show;
5552
+ }, ...(ngDevMode ? [{ debugName: "shouldShowLogs" }] : /* istanbul ignore next */ []));
5348
5553
  constructor() {
5349
- if (this.shouldShowLogs) {
5350
- console.log('[JsonDebug] CONSTRUCTOR', this.id);
5554
+ if (this.shouldShowLogs()) {
5555
+ console.log(`[JsonDebug:${this.id}] 🏗️ Constructor`, {
5556
+ id: this.id,
5557
+ isDevMode: isDevMode(),
5558
+ debug: this.debug(),
5559
+ debounceTime: this.debounceTime(),
5560
+ });
5351
5561
  }
5352
5562
  effect((onCleanup) => {
5353
5563
  const form = this.form();
5354
- if (!form)
5564
+ if (!form) {
5565
+ if (this.shouldShowLogs() && this.isInitialized) {
5566
+ console.log(`[JsonDebug:${this.id}] ⚠️ Formulario eliminado`);
5567
+ }
5568
+ if (this.subscription) {
5569
+ this.subscription.unsubscribe();
5570
+ this.subscription = undefined;
5571
+ }
5572
+ this.isInitialized = false;
5355
5573
  return;
5356
- if (this.shouldShowLogs) {
5357
- console.log(`[json-debug:${this.id}] EFFECT INIT`, {
5574
+ }
5575
+ if (this.isInitialized && this.subscription) {
5576
+ if (this.shouldShowLogs()) {
5577
+ console.log(`[JsonDebug:${this.id}] 🔄 Formulario ya inicializado, saltando...`);
5578
+ }
5579
+ return;
5580
+ }
5581
+ if (this.shouldShowLogs()) {
5582
+ console.log(`[JsonDebug:${this.id}] 🔄 Effect iniciado`, {
5358
5583
  controls: Object.keys(form.controls).length,
5584
+ timestamp: new Date().toISOString(),
5585
+ debounceTime: this.debounceTime(),
5359
5586
  });
5360
5587
  }
5361
- this.subscription?.unsubscribe();
5362
- this.debugValue.set(form.getRawValue());
5363
- this.subscription = form.valueChanges.subscribe(() => {
5364
- this.debugValue.set(form.getRawValue());
5365
- if (this.shouldShowLogs) {
5366
- this.changesCount++;
5367
- console.log(`[json-debug:${this.id}] CHANGE #${this.changesCount}`, {
5368
- formValue: form.getRawValue(),
5588
+ if (this.subscription) {
5589
+ this.subscription.unsubscribe();
5590
+ this.subscription = undefined;
5591
+ }
5592
+ const rawValue = form.getRawValue();
5593
+ this.debugValue.set(rawValue);
5594
+ this.updateSerializedValue(rawValue);
5595
+ this.updateFileInfo(rawValue);
5596
+ if (this.shouldShowLogs()) {
5597
+ console.log(`[JsonDebug:${this.id}] 📊 Procesamiento inicial`, {
5598
+ hasFile: !!rawValue?.file,
5599
+ fileType: rawValue?.file ? typeof rawValue.file : 'N/A',
5600
+ serializedLength: this.serializedValue().length,
5601
+ });
5602
+ }
5603
+ // ✅ Aplicar debounce y distinctUntilChanged para reducir logs
5604
+ this.subscription = form.valueChanges
5605
+ .pipe(debounceTime(this.debounceTime()), // ✅ Esperar 300ms antes de emitir
5606
+ distinctUntilChanged((prev, curr) => {
5607
+ // ✅ Comparar si los valores son iguales
5608
+ return JSON.stringify(prev) === JSON.stringify(curr);
5609
+ }))
5610
+ .subscribe(() => {
5611
+ const startTime = performance.now();
5612
+ const rawValue = form.getRawValue();
5613
+ this.debugValue.set(rawValue);
5614
+ this.updateSerializedValue(rawValue);
5615
+ this.updateFileInfo(rawValue);
5616
+ this.changesCount++;
5617
+ const endTime = performance.now();
5618
+ if (this.shouldShowLogs()) {
5619
+ const elapsed = endTime - startTime;
5620
+ console.log(`[JsonDebug:${this.id}] 🔄 Cambio #${this.changesCount}`, {
5621
+ tiempoMs: elapsed.toFixed(2),
5622
+ hasFile: !!rawValue?.file,
5623
+ serializedLength: this.serializedValue().length,
5624
+ warning: elapsed > 10 ? '⚠️ Cambio lento' : '✅ OK',
5369
5625
  });
5626
+ if (this.changesCount % 10 === 0) {
5627
+ console.log(`[JsonDebug:${this.id}] 📊 Estadísticas`, {
5628
+ cambios: this.changesCount,
5629
+ promedioMs: (elapsed / this.changesCount).toFixed(2),
5630
+ ultimoMs: elapsed.toFixed(2),
5631
+ });
5632
+ }
5370
5633
  }
5371
5634
  });
5635
+ this.isInitialized = true;
5372
5636
  onCleanup(() => {
5373
- this.subscription?.unsubscribe();
5637
+ if (this.shouldShowLogs()) {
5638
+ console.log(`[JsonDebug:${this.id}] 🧹 Limpieza effect`, {
5639
+ cambiosProcesados: this.changesCount,
5640
+ isInitialized: this.isInitialized,
5641
+ });
5642
+ }
5374
5643
  });
5375
5644
  });
5376
5645
  }
5377
- ngOnDestroy() {
5378
- if (this.shouldShowLogs) {
5379
- console.log('[JsonDebug] DESTROY', this.id);
5646
+ updateSerializedValue(value) {
5647
+ if (!value) {
5648
+ this.serializedValue.set('');
5649
+ return;
5380
5650
  }
5381
- this.subscription?.unsubscribe();
5382
- if (this.shouldShowLogs) {
5383
- console.log(`[json-debug:${this.id}] DESTROY SUMMARY`, {
5651
+ try {
5652
+ const copy = { ...value };
5653
+ if (copy.file && typeof copy.file === 'object') {
5654
+ const fileProps = {};
5655
+ if ('name' in copy.file && copy.file.name !== undefined) {
5656
+ fileProps.name = copy.file.name;
5657
+ }
5658
+ if ('size' in copy.file && copy.file.size !== undefined) {
5659
+ fileProps.size = copy.file.size;
5660
+ }
5661
+ if ('type' in copy.file && copy.file.type !== undefined) {
5662
+ fileProps.type = copy.file.type;
5663
+ }
5664
+ if ('lastModified' in copy.file &&
5665
+ copy.file.lastModified !== undefined) {
5666
+ fileProps.lastModified = copy.file.lastModified;
5667
+ }
5668
+ const otherProps = Object.keys(copy.file).filter((key) => !['name', 'size', 'type', 'lastModified'].includes(key));
5669
+ otherProps.forEach((key) => {
5670
+ fileProps[key] = copy.file[key];
5671
+ });
5672
+ copy.file = fileProps;
5673
+ }
5674
+ this.serializedValue.set(JSON.stringify(copy, null, 2));
5675
+ }
5676
+ catch (error) {
5677
+ this.serializedValue.set('Error al serializar: ' + error);
5678
+ if (this.shouldShowLogs()) {
5679
+ console.error('[JsonDebug] Error serializing value:', error);
5680
+ }
5681
+ }
5682
+ }
5683
+ updateFileInfo(value) {
5684
+ if (!value || !value.file) {
5685
+ this.fileInfo.set(null);
5686
+ return;
5687
+ }
5688
+ const file = value.file;
5689
+ const info = {
5690
+ exists: true,
5691
+ isFile: file instanceof File,
5692
+ isBlob: file instanceof Blob,
5693
+ hasName: 'name' in file,
5694
+ hasSize: 'size' in file,
5695
+ hasType: 'type' in file,
5696
+ hasLastModified: 'lastModified' in file,
5697
+ };
5698
+ if ('name' in file && file.name !== undefined) {
5699
+ info.name = file.name;
5700
+ }
5701
+ if ('size' in file && file.size !== undefined) {
5702
+ info.size = file.size;
5703
+ }
5704
+ if ('type' in file && file.type !== undefined) {
5705
+ info.mimeType = file.type;
5706
+ }
5707
+ if ('lastModified' in file && file.lastModified !== undefined) {
5708
+ info.lastModified = file.lastModified;
5709
+ }
5710
+ this.fileInfo.set(info);
5711
+ }
5712
+ hasFile() {
5713
+ return !!this.fileInfo();
5714
+ }
5715
+ getFileInfo() {
5716
+ return this.fileInfo();
5717
+ }
5718
+ ngOnDestroy() {
5719
+ if (this.shouldShowLogs()) {
5720
+ console.log(`[JsonDebug:${this.id}] 💀 Destroy`, {
5384
5721
  totalChanges: this.changesCount,
5722
+ tiempoVida: `${(performance.now() / 1000).toFixed(2)}s`,
5385
5723
  });
5386
5724
  }
5725
+ if (this.subscription) {
5726
+ this.subscription.unsubscribe();
5727
+ this.subscription = undefined;
5728
+ }
5387
5729
  }
5388
5730
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonValuesDebujComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5389
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JsonValuesDebujComponent, isStandalone: true, selector: "app-json-values-debuj", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (form() && shouldShowVisual) {\r\n <div class=\"custom-container\">\r\n <pre class=\"custom-pre\" [innerHTML]=\"debugValue() | jsonHighlight\"></pre>\r\n </div>\r\n}\r\n", styles: [".custom-container{width:100%;overflow:auto;max-height:700px;background:#1e1e1e;border:1px solid #333;border-radius:8px;box-shadow:0 4px 12px #0000004d;padding:1.25rem;margin-bottom:1.25rem}.custom-pre{margin:0;white-space:pre-wrap;word-break:break-word;word-wrap:break-word;font-family:Segoe UI,Fira Code,JetBrains Mono,Consolas,Monaco,Courier New,monospace;font-size:15px;line-height:1.65;color:#d4d4d4;font-weight:400}.json-key{color:#9cdcfe;font-weight:600;font-size:15px}.json-string{color:#ce9178;font-size:15px}.json-number{color:#b5cea8;font-weight:600;font-size:15px}.json-boolean{color:#569cd6;font-weight:700;font-size:15px}.json-null{color:gray;font-style:italic;font-size:15px}.json-bracket{color:gold;font-weight:700;font-size:16px}.json-colon{color:#d4d4d4;font-weight:500}.json-comma{color:gray;font-size:14px}.json-date-part{color:#4fc1ff;font-weight:600;font-size:15px}.json-date-sep{color:gray}.json-date-time{color:#c586c0;font-size:15px}.custom-container::-webkit-scrollbar{width:12px;height:12px}.custom-container::-webkit-scrollbar-track{background:#252526;border-radius:6px}.custom-container::-webkit-scrollbar-thumb{background:#555;border-radius:6px;transition:background .2s ease}.custom-container::-webkit-scrollbar-thumb:hover{background:#777}.custom-container::-webkit-scrollbar-corner{background:#1e1e1e}.custom-container{font-smooth:antialiased;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.json-null,.json-undefined{color:#f48771;font-style:italic;opacity:.8}.json-empty{color:gray;font-style:italic}.json-comma+br{margin-bottom:2px}\n"], dependencies: [{ kind: "pipe", type: JsonHighlightPipe, name: "jsonHighlight" }], encapsulation: i0.ViewEncapsulation.None });
5731
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JsonValuesDebujComponent, isStandalone: true, selector: "app-json-values-debuj", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!-- json-values-debuj.component.html -->\r\n@if (shouldShowVisual()) {\r\n <div class=\"json-debug-container\">\r\n <div class=\"json-debug-header\">\r\n <span class=\"json-debug-id\">\uD83D\uDD0D Debug #{{ id }}</span>\r\n <span class=\"json-debug-info\">\r\n @if (debugValue()) {\r\n <span class=\"badge badge-success\">\u2713 Formulario activo</span>\r\n } @else {\r\n <span class=\"badge badge-danger\">\u2717 Sin formulario</span>\r\n }\r\n </span>\r\n </div>\r\n\r\n @if (debugValue()) {\r\n <div class=\"json-debug-content\">\r\n <!-- Informaci\u00F3n del archivo (si existe) -->\r\n @if (hasFile()) {\r\n <div class=\"file-info-box\">\r\n <div class=\"file-info-header\">\uD83D\uDCC1 Archivo seleccionado</div>\r\n <div class=\"file-info-grid\">\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Nombre:</span>\r\n <span class=\"value\">{{ getFileInfo()?.name || \"N/A\" }}</span>\r\n </div>\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Tama\u00F1o:</span>\r\n <span class=\"value\"\r\n >{{ getFileInfo()?.size | number }} bytes</span\r\n >\r\n </div>\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Tipo:</span>\r\n <span class=\"value\">{{\r\n getFileInfo()?.mimeType || \"N/A\"\r\n }}</span>\r\n </div>\r\n @if (getFileInfo()?.lastModified) {\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Modificado:</span>\r\n <span class=\"value\">{{\r\n getFileInfo()?.lastModified | date: \"medium\"\r\n }}</span>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- JSON -->\r\n <div class=\"json-display\">\r\n <pre class=\"json-colored\">{{ serializedValue() }}</pre>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".json-debug-container{margin:10px 0;border:1px solid #dee2e6;border-radius:8px;background-color:#f8f9fa;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;box-shadow:0 2px 8px #0000001a}.json-debug-header{display:flex;justify-content:space-between;align-items:center;padding:12px 20px;background:linear-gradient(135deg,#667eea,#764ba2);border-radius:8px 8px 0 0;color:#fff}.json-debug-id{font-weight:700;font-size:14px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.2)}.badge{padding:4px 14px;border-radius:20px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.badge-success{background-color:#10b981;color:#fff;box-shadow:0 2px 4px #10b9814d}.badge-danger{background-color:#ef4444;color:#fff;box-shadow:0 2px 4px #ef44444d}.json-debug-content{padding:16px 20px}.file-info-box{background:linear-gradient(135deg,#f0f9ff,#e0f2fe);border:1px solid #bae6fd;border-radius:6px;padding:12px 16px;margin-bottom:16px}.file-info-header{font-weight:600;color:#0369a1;font-size:13px;margin-bottom:8px}.file-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:6px}.file-info-item{padding:4px 8px;background-color:#fff;border-radius:4px;border:1px solid #e5e7eb}.file-info-item .label{font-weight:600;color:#6b7280;margin-right:8px;font-size:12px}.file-info-item .value{color:#0369a1;font-family:Consolas,Monaco,Courier New,monospace;font-size:12px}.json-display{margin-top:0}.json-colored{margin:0;padding:16px 20px;background-color:#1e1e1e;border-radius:6px;overflow:auto;font-size:13px;line-height:1.7;color:#d4d4d4;min-height:50px;max-height:500px;font-family:Consolas,Monaco,Courier New,monospace;white-space:pre}.json-colored::-webkit-scrollbar{width:10px;height:10px}.json-colored::-webkit-scrollbar-track{background:#2d2d2d;border-radius:4px}.json-colored::-webkit-scrollbar-thumb{background:#666;border-radius:4px}.json-colored::-webkit-scrollbar-thumb:hover{background:#888}@media(max-width:768px){.json-debug-header{flex-direction:column;gap:8px;padding:10px 16px}.json-debug-content{padding:12px 16px}.file-info-grid{grid-template-columns:1fr}.json-colored{font-size:11px;padding:12px 14px;max-height:300px}}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: DecimalPipe, name: "number" }], encapsulation: i0.ViewEncapsulation.None });
5390
5732
  }
5391
5733
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonValuesDebujComponent, decorators: [{
5392
5734
  type: Component,
5393
- args: [{ selector: 'app-json-values-debuj', standalone: true, imports: [JsonHighlightPipe], encapsulation: ViewEncapsulation.None, template: "@if (form() && shouldShowVisual) {\r\n <div class=\"custom-container\">\r\n <pre class=\"custom-pre\" [innerHTML]=\"debugValue() | jsonHighlight\"></pre>\r\n </div>\r\n}\r\n", styles: [".custom-container{width:100%;overflow:auto;max-height:700px;background:#1e1e1e;border:1px solid #333;border-radius:8px;box-shadow:0 4px 12px #0000004d;padding:1.25rem;margin-bottom:1.25rem}.custom-pre{margin:0;white-space:pre-wrap;word-break:break-word;word-wrap:break-word;font-family:Segoe UI,Fira Code,JetBrains Mono,Consolas,Monaco,Courier New,monospace;font-size:15px;line-height:1.65;color:#d4d4d4;font-weight:400}.json-key{color:#9cdcfe;font-weight:600;font-size:15px}.json-string{color:#ce9178;font-size:15px}.json-number{color:#b5cea8;font-weight:600;font-size:15px}.json-boolean{color:#569cd6;font-weight:700;font-size:15px}.json-null{color:gray;font-style:italic;font-size:15px}.json-bracket{color:gold;font-weight:700;font-size:16px}.json-colon{color:#d4d4d4;font-weight:500}.json-comma{color:gray;font-size:14px}.json-date-part{color:#4fc1ff;font-weight:600;font-size:15px}.json-date-sep{color:gray}.json-date-time{color:#c586c0;font-size:15px}.custom-container::-webkit-scrollbar{width:12px;height:12px}.custom-container::-webkit-scrollbar-track{background:#252526;border-radius:6px}.custom-container::-webkit-scrollbar-thumb{background:#555;border-radius:6px;transition:background .2s ease}.custom-container::-webkit-scrollbar-thumb:hover{background:#777}.custom-container::-webkit-scrollbar-corner{background:#1e1e1e}.custom-container{font-smooth:antialiased;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.json-null,.json-undefined{color:#f48771;font-style:italic;opacity:.8}.json-empty{color:gray;font-style:italic}.json-comma+br{margin-bottom:2px}\n"] }]
5394
- }], ctorParameters: () => [], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }] } });
5735
+ args: [{ selector: 'app-json-values-debuj', standalone: true, imports: [DatePipe, DecimalPipe], encapsulation: ViewEncapsulation.None, template: "<!-- json-values-debuj.component.html -->\r\n@if (shouldShowVisual()) {\r\n <div class=\"json-debug-container\">\r\n <div class=\"json-debug-header\">\r\n <span class=\"json-debug-id\">\uD83D\uDD0D Debug #{{ id }}</span>\r\n <span class=\"json-debug-info\">\r\n @if (debugValue()) {\r\n <span class=\"badge badge-success\">\u2713 Formulario activo</span>\r\n } @else {\r\n <span class=\"badge badge-danger\">\u2717 Sin formulario</span>\r\n }\r\n </span>\r\n </div>\r\n\r\n @if (debugValue()) {\r\n <div class=\"json-debug-content\">\r\n <!-- Informaci\u00F3n del archivo (si existe) -->\r\n @if (hasFile()) {\r\n <div class=\"file-info-box\">\r\n <div class=\"file-info-header\">\uD83D\uDCC1 Archivo seleccionado</div>\r\n <div class=\"file-info-grid\">\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Nombre:</span>\r\n <span class=\"value\">{{ getFileInfo()?.name || \"N/A\" }}</span>\r\n </div>\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Tama\u00F1o:</span>\r\n <span class=\"value\"\r\n >{{ getFileInfo()?.size | number }} bytes</span\r\n >\r\n </div>\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Tipo:</span>\r\n <span class=\"value\">{{\r\n getFileInfo()?.mimeType || \"N/A\"\r\n }}</span>\r\n </div>\r\n @if (getFileInfo()?.lastModified) {\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Modificado:</span>\r\n <span class=\"value\">{{\r\n getFileInfo()?.lastModified | date: \"medium\"\r\n }}</span>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- JSON -->\r\n <div class=\"json-display\">\r\n <pre class=\"json-colored\">{{ serializedValue() }}</pre>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".json-debug-container{margin:10px 0;border:1px solid #dee2e6;border-radius:8px;background-color:#f8f9fa;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;box-shadow:0 2px 8px #0000001a}.json-debug-header{display:flex;justify-content:space-between;align-items:center;padding:12px 20px;background:linear-gradient(135deg,#667eea,#764ba2);border-radius:8px 8px 0 0;color:#fff}.json-debug-id{font-weight:700;font-size:14px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.2)}.badge{padding:4px 14px;border-radius:20px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.badge-success{background-color:#10b981;color:#fff;box-shadow:0 2px 4px #10b9814d}.badge-danger{background-color:#ef4444;color:#fff;box-shadow:0 2px 4px #ef44444d}.json-debug-content{padding:16px 20px}.file-info-box{background:linear-gradient(135deg,#f0f9ff,#e0f2fe);border:1px solid #bae6fd;border-radius:6px;padding:12px 16px;margin-bottom:16px}.file-info-header{font-weight:600;color:#0369a1;font-size:13px;margin-bottom:8px}.file-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:6px}.file-info-item{padding:4px 8px;background-color:#fff;border-radius:4px;border:1px solid #e5e7eb}.file-info-item .label{font-weight:600;color:#6b7280;margin-right:8px;font-size:12px}.file-info-item .value{color:#0369a1;font-family:Consolas,Monaco,Courier New,monospace;font-size:12px}.json-display{margin-top:0}.json-colored{margin:0;padding:16px 20px;background-color:#1e1e1e;border-radius:6px;overflow:auto;font-size:13px;line-height:1.7;color:#d4d4d4;min-height:50px;max-height:500px;font-family:Consolas,Monaco,Courier New,monospace;white-space:pre}.json-colored::-webkit-scrollbar{width:10px;height:10px}.json-colored::-webkit-scrollbar-track{background:#2d2d2d;border-radius:4px}.json-colored::-webkit-scrollbar-thumb{background:#666;border-radius:4px}.json-colored::-webkit-scrollbar-thumb:hover{background:#888}@media(max-width:768px){.json-debug-header{flex-direction:column;gap:8px;padding:10px 16px}.json-debug-content{padding:12px 16px}.file-info-grid{grid-template-columns:1fr}.json-colored{font-size:11px;padding:12px 14px;max-height:300px}}\n"] }]
5736
+ }], ctorParameters: () => [], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }] } });
5395
5737
 
5396
5738
  class IcoLabel {
5397
5739
  ico = input.required(...(ngDevMode ? [{ debugName: "ico" }] : /* istanbul ignore next */ []));
@@ -8272,11 +8614,122 @@ class JoinByPipe {
8272
8614
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JoinByPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
8273
8615
  static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: JoinByPipe, isStandalone: true, name: "joinBy" });
8274
8616
  }
8275
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JoinByPipe, decorators: [{
8617
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JoinByPipe, decorators: [{
8618
+ type: Pipe,
8619
+ args: [{
8620
+ name: 'joinBy',
8621
+ standalone: true,
8622
+ }]
8623
+ }] });
8624
+
8625
+ class JsonHighlightPipe {
8626
+ /**
8627
+ * Stringifica el JSON manteniendo arrays en una línea para una vista más compacta
8628
+ */
8629
+ compactStringify(obj, indent = 0) {
8630
+ const spaces = ' '.repeat(indent);
8631
+ if (obj === null) {
8632
+ return 'null';
8633
+ }
8634
+ if (typeof obj !== 'object') {
8635
+ if (typeof obj === 'string') {
8636
+ return JSON.stringify(obj);
8637
+ }
8638
+ return String(obj);
8639
+ }
8640
+ if (obj instanceof Date) {
8641
+ return JSON.stringify(obj.toISOString());
8642
+ }
8643
+ if (Array.isArray(obj)) {
8644
+ // Mantener arrays en una línea
8645
+ const items = obj.map((item) => {
8646
+ if (item instanceof Date) {
8647
+ return JSON.stringify(item.toISOString());
8648
+ }
8649
+ if (typeof item === 'object' && item !== null) {
8650
+ return this.compactStringify(item, indent);
8651
+ }
8652
+ return typeof item === 'string'
8653
+ ? JSON.stringify(item)
8654
+ : item === null
8655
+ ? 'null'
8656
+ : String(item);
8657
+ });
8658
+ return `[${items.join(', ')}]`;
8659
+ }
8660
+ // Para objetos, mantener la indentación
8661
+ const keys = Object.keys(obj);
8662
+ if (keys.length === 0) {
8663
+ return '{}';
8664
+ }
8665
+ const innerIndent = indent + 2;
8666
+ const innerSpaces = ' '.repeat(innerIndent);
8667
+ const lines = keys.map((key) => {
8668
+ const value = obj[key];
8669
+ const stringified = value instanceof Date
8670
+ ? JSON.stringify(value.toISOString())
8671
+ : typeof value === 'object' && value !== null
8672
+ ? this.compactStringify(value, innerIndent)
8673
+ : typeof value === 'string'
8674
+ ? JSON.stringify(value)
8675
+ : value === null
8676
+ ? 'null'
8677
+ : String(value);
8678
+ return `${innerSpaces}"${key}": ${stringified}`;
8679
+ });
8680
+ return `{\n${lines.join(',\n')}\n${spaces}}`;
8681
+ }
8682
+ instanceId = Math.random().toString(36).slice(2, 8);
8683
+ executions = 0;
8684
+ transform(value) {
8685
+ if (isDevMode()) {
8686
+ this.executions++;
8687
+ if (this.executions % 10 === 0) {
8688
+ console.log(`[jsonHighlight:${this.instanceId}] ${this.executions}`);
8689
+ }
8690
+ }
8691
+ if (!value) {
8692
+ return '';
8693
+ }
8694
+ const json = this.compactStringify(value);
8695
+ // Detecta fechas ISO: "YYYY-MM-DDTHH:MM:SS.mmmZ"
8696
+ const ISO_DATE_RE = /^"(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}\.\d{3}Z)"$/;
8697
+ // Aplica estilo y colores para diferenciar claves, valores, booleanos, números, etc.
8698
+ return json
8699
+ .replace(/&/g, '&amp;')
8700
+ .replace(/</g, '&lt;')
8701
+ .replace(/>/g, '&gt;')
8702
+ .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?)/g, (match) => {
8703
+ // Si es una clave (tiene dos puntos al final)
8704
+ if (/:$/.test(match)) {
8705
+ return `<span class="json-key">${match}</span>`;
8706
+ }
8707
+ // Detectar fechas ISO (YYYY-MM-DDTHH:MM:SS.mmmZ)
8708
+ const isoMatch = match.match(ISO_DATE_RE);
8709
+ if (isoMatch) {
8710
+ return `<span class="json-string json-date">"<span class="json-date-part">${isoMatch[1]}</span><span class="json-date-sep">T</span><span class="json-date-time">${isoMatch[2]}</span>"</span>`;
8711
+ }
8712
+ // Si es un string (no tiene dos puntos)
8713
+ return `<span class="json-string">${match}</span>`;
8714
+ })
8715
+ .replace(/\b(true|false|null)\b/g, (match) => {
8716
+ if (match === 'null') {
8717
+ return '<span class="json-null">null</span>';
8718
+ }
8719
+ return `<span class="json-boolean">${match}</span>`;
8720
+ })
8721
+ .replace(/\b(\d+\.?\d*)\b/g, '<span class="json-number">$1</span>')
8722
+ .replace(/[{}\[\]]/g, '<span class="json-bracket">$&</span>')
8723
+ .replace(/,/g, '<span class="json-comma">$&</span>')
8724
+ .replace(/:/g, '<span class="json-colon">$&</span>');
8725
+ }
8726
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
8727
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, isStandalone: true, name: "jsonHighlight" });
8728
+ }
8729
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, decorators: [{
8276
8730
  type: Pipe,
8277
8731
  args: [{
8278
- name: 'joinBy',
8279
- standalone: true,
8732
+ name: 'jsonHighlight',
8280
8733
  }]
8281
8734
  }] });
8282
8735
 
@@ -8513,26 +8966,246 @@ class DsxdateShort {
8513
8966
  useExisting: forwardRef(() => DsxdateShort),
8514
8967
  multi: true,
8515
8968
  },
8516
- ], ngImport: i0, template: "<!-- dsxdate-short.html -->\r\n<p-floatLabel variant=\"on\">\r\n <p-datepicker\r\n appendTo=\"body\"\r\n [formControl]=\"fechaControl\"\r\n [fluid]=\"true\"\r\n [showIcon]=\"showIcon()\"\r\n [showOnFocus]=\"showOnFocus()\"\r\n [showButtonBar]=\"showButtonBar()\"\r\n [dateFormat]=\"dateFormat()\"\r\n [placeholder]=\"placeholder()\"\r\n pInputMask=\"99/99/9999\"\r\n />\r\n <label>{{ label() }}</label>\r\n</p-floatLabel>\r\n\r\n<!-- \u2705 SIN CONDICI\u00D3N - IGUAL QUE EL ORIGINAL -->\r\n<dsx-message-error [control]=\"fechaControl\" />\r\n\r\n<!-- Debug info -->\r\n@if (debugEnabled() && isDevMode) {\r\n<div\r\n class=\"debug-info\"\r\n style=\"\r\n font-size: 12px;\r\n color: #666;\r\n margin-top: 4px;\r\n border-top: 1px dashed #ccc;\r\n padding-top: 4px;\r\n \"\r\n>\r\n <small>\r\n <strong>Debug:</strong>\r\n Value: {{ fechaControl.value | json }} | Valid: {{ fechaControl.valid }} |\r\n Invalid: {{ fechaControl.invalid }} | Errors: {{ fechaControl.errors | json\r\n }} | Dirty: {{ fechaControl.dirty }} | Touched: {{ fechaControl.touched }} |\r\n Status: {{ fechaControl.status }}\r\n </small>\r\n</div>\r\n}\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FloatLabel, selector: "p-floatlabel, p-floatLabel, p-float-label", inputs: ["variant"] }, { kind: "component", type: DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: AppMessageErrorComponent, selector: "dsx-message-error", inputs: ["control", "form", "debugMode"] }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] });
8969
+ ], ngImport: i0, template: "<!-- dsxdate-short.html -->\r\n<p-floatLabel variant=\"on\">\r\n <p-datepicker\r\n fluid\r\n appendTo=\"body\"\r\n [formControl]=\"fechaControl\"\r\n [fluid]=\"true\"\r\n [showIcon]=\"showIcon()\"\r\n [showOnFocus]=\"showOnFocus()\"\r\n [showButtonBar]=\"showButtonBar()\"\r\n [dateFormat]=\"dateFormat()\"\r\n [placeholder]=\"placeholder()\"\r\n pInputMask=\"99/99/9999\"\r\n />\r\n <label>{{ label() }}</label>\r\n</p-floatLabel>\r\n\r\n<!-- \u2705 SIN CONDICI\u00D3N - IGUAL QUE EL ORIGINAL -->\r\n<dsx-message-error [control]=\"fechaControl\" />\r\n\r\n<!-- Debug info -->\r\n@if (debugEnabled() && isDevMode) {\r\n<div\r\n class=\"debug-info\"\r\n style=\"\r\n font-size: 12px;\r\n color: #666;\r\n margin-top: 4px;\r\n border-top: 1px dashed #ccc;\r\n padding-top: 4px;\r\n \"\r\n>\r\n <small>\r\n <strong>Debug:</strong>\r\n Value: {{ fechaControl.value | json }} | Valid: {{ fechaControl.valid }} |\r\n Invalid: {{ fechaControl.invalid }} | Errors: {{ fechaControl.errors | json\r\n }} | Dirty: {{ fechaControl.dirty }} | Touched: {{ fechaControl.touched }} |\r\n Status: {{ fechaControl.status }}\r\n </small>\r\n</div>\r\n}\r\n", styles: [""], dependencies: [{ kind: "component", type: AppMessageErrorComponent, selector: "dsx-message-error", inputs: ["control", "form", "debugMode"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "component", type: FloatLabel, selector: "p-floatlabel, p-floatLabel, p-float-label", inputs: ["variant"] }, { kind: "ngmodule", type: InputMaskModule }, { kind: "directive", type: i1$8.InputMaskDirective, selector: "[pInputMask]", inputs: ["pInputMaskPT", "pInputMaskUnstyled", "pInputMask", "slotChar", "autoClear", "characterPattern", "keepBuffer"], outputs: ["onComplete", "onUnmaskedChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] });
8517
8970
  }
8518
8971
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxdateShort, decorators: [{
8519
8972
  type: Component,
8520
8973
  args: [{ selector: 'dsxdate-short', imports: [
8974
+ AppMessageErrorComponent,
8521
8975
  CommonModule,
8522
- FloatLabel,
8523
8976
  DatePicker,
8524
- ReactiveFormsModule,
8525
- AppMessageErrorComponent,
8977
+ FloatLabel,
8978
+ InputMaskModule,
8526
8979
  JsonPipe,
8980
+ ReactiveFormsModule,
8527
8981
  ], providers: [
8528
8982
  {
8529
8983
  provide: NG_VALUE_ACCESSOR,
8530
8984
  useExisting: forwardRef(() => DsxdateShort),
8531
8985
  multi: true,
8532
8986
  },
8533
- ], template: "<!-- dsxdate-short.html -->\r\n<p-floatLabel variant=\"on\">\r\n <p-datepicker\r\n appendTo=\"body\"\r\n [formControl]=\"fechaControl\"\r\n [fluid]=\"true\"\r\n [showIcon]=\"showIcon()\"\r\n [showOnFocus]=\"showOnFocus()\"\r\n [showButtonBar]=\"showButtonBar()\"\r\n [dateFormat]=\"dateFormat()\"\r\n [placeholder]=\"placeholder()\"\r\n pInputMask=\"99/99/9999\"\r\n />\r\n <label>{{ label() }}</label>\r\n</p-floatLabel>\r\n\r\n<!-- \u2705 SIN CONDICI\u00D3N - IGUAL QUE EL ORIGINAL -->\r\n<dsx-message-error [control]=\"fechaControl\" />\r\n\r\n<!-- Debug info -->\r\n@if (debugEnabled() && isDevMode) {\r\n<div\r\n class=\"debug-info\"\r\n style=\"\r\n font-size: 12px;\r\n color: #666;\r\n margin-top: 4px;\r\n border-top: 1px dashed #ccc;\r\n padding-top: 4px;\r\n \"\r\n>\r\n <small>\r\n <strong>Debug:</strong>\r\n Value: {{ fechaControl.value | json }} | Valid: {{ fechaControl.valid }} |\r\n Invalid: {{ fechaControl.invalid }} | Errors: {{ fechaControl.errors | json\r\n }} | Dirty: {{ fechaControl.dirty }} | Touched: {{ fechaControl.touched }} |\r\n Status: {{ fechaControl.status }}\r\n </small>\r\n</div>\r\n}\r\n" }]
8987
+ ], template: "<!-- dsxdate-short.html -->\r\n<p-floatLabel variant=\"on\">\r\n <p-datepicker\r\n fluid\r\n appendTo=\"body\"\r\n [formControl]=\"fechaControl\"\r\n [fluid]=\"true\"\r\n [showIcon]=\"showIcon()\"\r\n [showOnFocus]=\"showOnFocus()\"\r\n [showButtonBar]=\"showButtonBar()\"\r\n [dateFormat]=\"dateFormat()\"\r\n [placeholder]=\"placeholder()\"\r\n pInputMask=\"99/99/9999\"\r\n />\r\n <label>{{ label() }}</label>\r\n</p-floatLabel>\r\n\r\n<!-- \u2705 SIN CONDICI\u00D3N - IGUAL QUE EL ORIGINAL -->\r\n<dsx-message-error [control]=\"fechaControl\" />\r\n\r\n<!-- Debug info -->\r\n@if (debugEnabled() && isDevMode) {\r\n<div\r\n class=\"debug-info\"\r\n style=\"\r\n font-size: 12px;\r\n color: #666;\r\n margin-top: 4px;\r\n border-top: 1px dashed #ccc;\r\n padding-top: 4px;\r\n \"\r\n>\r\n <small>\r\n <strong>Debug:</strong>\r\n Value: {{ fechaControl.value | json }} | Valid: {{ fechaControl.valid }} |\r\n Invalid: {{ fechaControl.invalid }} | Errors: {{ fechaControl.errors | json\r\n }} | Dirty: {{ fechaControl.dirty }} | Touched: {{ fechaControl.touched }} |\r\n Status: {{ fechaControl.status }}\r\n </small>\r\n</div>\r\n}\r\n" }]
8534
8988
  }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], showIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcon", required: false }] }], showOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "showOnFocus", required: false }] }], showButtonBar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButtonBar", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }] } });
8535
8989
 
8990
+ class DsxdateTime {
8991
+ injector = inject(Injector);
8992
+ // ✅ Exponer isDevMode como propiedad pública
8993
+ isDevMode = isDevMode();
8994
+ // === INPUTS CONFIGURABLES ===
8995
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
8996
+ placeholder = input('dd/mm/yyyy 00:00', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
8997
+ dateFormat = input('dd/mm/yy', ...(ngDevMode ? [{ debugName: "dateFormat" }] : /* istanbul ignore next */ []));
8998
+ showIcon = input(true, ...(ngDevMode ? [{ debugName: "showIcon" }] : /* istanbul ignore next */ []));
8999
+ showOnFocus = input(false, ...(ngDevMode ? [{ debugName: "showOnFocus" }] : /* istanbul ignore next */ []));
9000
+ showButtonBar = input(true, ...(ngDevMode ? [{ debugName: "showButtonBar" }] : /* istanbul ignore next */ []));
9001
+ showTime = input(true, ...(ngDevMode ? [{ debugName: "showTime" }] : /* istanbul ignore next */ []));
9002
+ hourFormat = input('24', ...(ngDevMode ? [{ debugName: "hourFormat" }] : /* istanbul ignore next */ []));
9003
+ debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
9004
+ // === STATE ===
9005
+ debugEnabled = signal(false, ...(ngDevMode ? [{ debugName: "debugEnabled" }] : /* istanbul ignore next */ []));
9006
+ // === OBTENER EL CONTROL PADRE ===
9007
+ ngControl = computed(() => {
9008
+ try {
9009
+ return this.injector.get(NgControl);
9010
+ }
9011
+ catch {
9012
+ return null;
9013
+ }
9014
+ }, ...(ngDevMode ? [{ debugName: "ngControl" }] : /* istanbul ignore next */ []));
9015
+ // === CONTROL PADRE PARA ERRORES ===
9016
+ parentControl = computed(() => {
9017
+ const control = this.ngControl();
9018
+ return control?.control || null;
9019
+ }, ...(ngDevMode ? [{ debugName: "parentControl" }] : /* istanbul ignore next */ []));
9020
+ // === CONTROL INTERNO (para el template) ===
9021
+ fechaControl = new FormControl(null);
9022
+ // === CONTROL VALUE ACCESSOR ===
9023
+ onChange = () => { };
9024
+ onTouched = () => { };
9025
+ constructor() {
9026
+ // Debug effect
9027
+ effect(() => {
9028
+ const isDebug = this.debug() && this.isDevMode;
9029
+ this.debugEnabled.set(isDebug);
9030
+ if (isDebug) {
9031
+ console.log('[dsxdate-datetime] === DEBUG MODE ACTIVADO ===');
9032
+ console.log('[dsxdate-datetime] Configuración:', {
9033
+ label: this.label(),
9034
+ placeholder: this.placeholder(),
9035
+ dateFormat: this.dateFormat(),
9036
+ showIcon: this.showIcon(),
9037
+ showOnFocus: this.showOnFocus(),
9038
+ showButtonBar: this.showButtonBar(),
9039
+ showTime: this.showTime(),
9040
+ hourFormat: this.hourFormat(),
9041
+ });
9042
+ console.log('[dsxdate-datetime] Control padre:', this.parentControl());
9043
+ }
9044
+ });
9045
+ // ✅ SINCRONIZAR VALIDADORES DEL PADRE AL CONTROL INTERNO
9046
+ effect(() => {
9047
+ const parentCtrl = this.parentControl();
9048
+ if (parentCtrl) {
9049
+ // Copiar validadores del padre al control interno
9050
+ const validators = parentCtrl.validator;
9051
+ const asyncValidators = parentCtrl.asyncValidator;
9052
+ if (this.debugEnabled()) {
9053
+ console.log('[dsxdate-datetime] 🔄 Sincronizando validadores del padre');
9054
+ console.log('[dsxdate-datetime] Validadores del padre:', validators);
9055
+ }
9056
+ this.fechaControl.setValidators(validators ? [validators] : []);
9057
+ this.fechaControl.setAsyncValidators(asyncValidators ? [asyncValidators] : []);
9058
+ this.fechaControl.updateValueAndValidity({ emitEvent: false });
9059
+ if (this.debugEnabled()) {
9060
+ console.log('[dsxdate-datetime] ✅ Validadores sincronizados');
9061
+ console.log('[dsxdate-datetime] Estado del control interno:', {
9062
+ valid: this.fechaControl.valid,
9063
+ invalid: this.fechaControl.invalid,
9064
+ errors: this.fechaControl.errors,
9065
+ });
9066
+ }
9067
+ }
9068
+ });
9069
+ // Monitorear cambios del control padre
9070
+ effect(() => {
9071
+ if (this.debugEnabled()) {
9072
+ const parentCtrl = this.parentControl();
9073
+ console.log('[dsxdate-datetime] 📊 Parent Control state:', {
9074
+ value: parentCtrl?.value,
9075
+ status: parentCtrl?.status,
9076
+ errors: parentCtrl?.errors,
9077
+ valid: parentCtrl?.valid,
9078
+ invalid: parentCtrl?.invalid,
9079
+ touched: parentCtrl?.touched,
9080
+ dirty: parentCtrl?.dirty,
9081
+ });
9082
+ }
9083
+ });
9084
+ // Sincronizar cambios del control interno al padre
9085
+ this.fechaControl.valueChanges.subscribe((value) => {
9086
+ if (this.debugEnabled()) {
9087
+ console.log('[dsxdate-datetime] 🔄 valueChanges - Nuevo valor:', value);
9088
+ }
9089
+ this.onChange(value);
9090
+ });
9091
+ // Sincronizar touched
9092
+ this.fechaControl.statusChanges.subscribe(() => {
9093
+ if (this.debugEnabled()) {
9094
+ console.log('[dsxdate-datetime] 📊 statusChanges - Estado:', this.fechaControl.status);
9095
+ }
9096
+ this.onTouched();
9097
+ });
9098
+ }
9099
+ // === MÉTODOS DE DEBUG ===
9100
+ log(message, data) {
9101
+ if (this.debugEnabled() && this.isDevMode) {
9102
+ if (data !== undefined) {
9103
+ console.log(`[dsxdate-datetime] ${message}`, data);
9104
+ }
9105
+ else {
9106
+ console.log(`[dsxdate-datetime] ${message}`);
9107
+ }
9108
+ }
9109
+ }
9110
+ /**
9111
+ * Convierte cualquier fecha (string ISO, Date, moment) a Date válido
9112
+ */
9113
+ convertirFechaADate(fecha) {
9114
+ if (this.debugEnabled()) {
9115
+ this.log('🔄 convertirFechaADate - entrada:', fecha);
9116
+ }
9117
+ if (!fecha) {
9118
+ this.log('convertirFechaADate - fecha vacía, retornando null');
9119
+ return null;
9120
+ }
9121
+ if (fecha instanceof Date && !isNaN(fecha.getTime())) {
9122
+ this.log('✅ convertirFechaADate - es Date válido:', fecha);
9123
+ return fecha;
9124
+ }
9125
+ if (typeof fecha === 'string') {
9126
+ this.log('📝 convertirFechaADate - procesando string:', fecha);
9127
+ const momentDate = moment(fecha);
9128
+ if (momentDate.isValid()) {
9129
+ const result = momentDate.toDate();
9130
+ this.log('✅ convertirFechaADate - string convertido a Date:', result);
9131
+ return result;
9132
+ }
9133
+ this.log('❌ convertirFechaADate - string inválido para moment');
9134
+ }
9135
+ if (moment.isMoment(fecha)) {
9136
+ this.log('✅ convertirFechaADate - es objeto moment');
9137
+ return fecha.toDate();
9138
+ }
9139
+ const parsed = new Date(fecha);
9140
+ if (!isNaN(parsed.getTime())) {
9141
+ this.log('✅ convertirFechaADate - parseo directo exitoso:', parsed);
9142
+ return parsed;
9143
+ }
9144
+ this.log('❌ convertirFechaADate - no se pudo convertir, retornando null');
9145
+ return null;
9146
+ }
9147
+ // === CONTROL VALUE ACCESSOR IMPLEMENTATION ===
9148
+ writeValue(value) {
9149
+ if (this.debugEnabled()) {
9150
+ this.log('📥 writeValue - Valor recibido:', value);
9151
+ }
9152
+ const fechaConvertida = this.convertirFechaADate(value);
9153
+ if (this.debugEnabled()) {
9154
+ this.log('📥 writeValue - Valor convertido:', fechaConvertida);
9155
+ }
9156
+ this.fechaControl.setValue(fechaConvertida, { emitEvent: false });
9157
+ }
9158
+ registerOnChange(fn) {
9159
+ if (this.debugEnabled()) {
9160
+ this.log('📞 registerOnChange - Callback registrado');
9161
+ }
9162
+ this.onChange = fn;
9163
+ }
9164
+ registerOnTouched(fn) {
9165
+ if (this.debugEnabled()) {
9166
+ this.log('📞 registerOnTouched - Callback registrado');
9167
+ }
9168
+ this.onTouched = fn;
9169
+ }
9170
+ setDisabledState(isDisabled) {
9171
+ if (this.debugEnabled()) {
9172
+ this.log(`🚫 setDisabledState - Deshabilitado: ${isDisabled}`);
9173
+ }
9174
+ if (isDisabled) {
9175
+ this.fechaControl.disable({ emitEvent: false });
9176
+ }
9177
+ else {
9178
+ this.fechaControl.enable({ emitEvent: false });
9179
+ }
9180
+ }
9181
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxdateTime, deps: [], target: i0.ɵɵFactoryTarget.Component });
9182
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DsxdateTime, isStandalone: true, selector: "dsxdate-time", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, dateFormat: { classPropertyName: "dateFormat", publicName: "dateFormat", isSignal: true, isRequired: false, transformFunction: null }, showIcon: { classPropertyName: "showIcon", publicName: "showIcon", isSignal: true, isRequired: false, transformFunction: null }, showOnFocus: { classPropertyName: "showOnFocus", publicName: "showOnFocus", isSignal: true, isRequired: false, transformFunction: null }, showButtonBar: { classPropertyName: "showButtonBar", publicName: "showButtonBar", isSignal: true, isRequired: false, transformFunction: null }, showTime: { classPropertyName: "showTime", publicName: "showTime", isSignal: true, isRequired: false, transformFunction: null }, hourFormat: { classPropertyName: "hourFormat", publicName: "hourFormat", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
9183
+ {
9184
+ provide: NG_VALUE_ACCESSOR,
9185
+ useExisting: forwardRef(() => DsxdateTime),
9186
+ multi: true,
9187
+ },
9188
+ ], ngImport: i0, template: "<p-floatLabel variant=\"on\">\r\n <p-datepicker\r\n fluid\r\n appendTo=\"body\"\r\n [formControl]=\"fechaControl\"\r\n [fluid]=\"true\"\r\n [showIcon]=\"showIcon()\"\r\n [showOnFocus]=\"showOnFocus()\"\r\n [showButtonBar]=\"showButtonBar()\"\r\n [showTime]=\"showTime()\"\r\n [hourFormat]=\"hourFormat()\"\r\n [dateFormat]=\"dateFormat()\"\r\n [placeholder]=\"placeholder()\"\r\n pInputMask=\"99/99/9999 99:99\"\r\n />\r\n <label>{{ label() }}</label>\r\n</p-floatLabel>\r\n\r\n<!-- \u2705 SIN CONDICI\u00D3N - IGUAL QUE EL ORIGINAL -->\r\n<dsx-message-error [control]=\"fechaControl\" />\r\n\r\n<!-- Debug info -->\r\n@if (debugEnabled() && isDevMode) {\r\n<div\r\n class=\"debug-info\"\r\n style=\"\r\n font-size: 12px;\r\n color: #666;\r\n margin-top: 4px;\r\n border-top: 1px dashed #ccc;\r\n padding-top: 4px;\r\n \"\r\n>\r\n <small>\r\n <strong>Debug:</strong>\r\n Value: {{ fechaControl.value | json }} | Valid: {{ fechaControl.valid }} |\r\n Invalid: {{ fechaControl.invalid }} | Errors: {{ fechaControl.errors | json\r\n }} | Dirty: {{ fechaControl.dirty }} | Touched: {{ fechaControl.touched }} |\r\n Status: {{ fechaControl.status }}\r\n </small>\r\n</div>\r\n}\r\n", styles: [""], dependencies: [{ kind: "component", type: AppMessageErrorComponent, selector: "dsx-message-error", inputs: ["control", "form", "debugMode"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "component", type: FloatLabel, selector: "p-floatlabel, p-floatLabel, p-float-label", inputs: ["variant"] }, { kind: "ngmodule", type: InputMaskModule }, { kind: "directive", type: i1$8.InputMaskDirective, selector: "[pInputMask]", inputs: ["pInputMaskPT", "pInputMaskUnstyled", "pInputMask", "slotChar", "autoClear", "characterPattern", "keepBuffer"], outputs: ["onComplete", "onUnmaskedChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] });
9189
+ }
9190
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxdateTime, decorators: [{
9191
+ type: Component,
9192
+ args: [{ selector: 'dsxdate-time', imports: [
9193
+ AppMessageErrorComponent,
9194
+ CommonModule,
9195
+ DatePicker,
9196
+ FloatLabel,
9197
+ InputMaskModule,
9198
+ JsonPipe,
9199
+ ReactiveFormsModule,
9200
+ ], providers: [
9201
+ {
9202
+ provide: NG_VALUE_ACCESSOR,
9203
+ useExisting: forwardRef(() => DsxdateTime),
9204
+ multi: true,
9205
+ },
9206
+ ], template: "<p-floatLabel variant=\"on\">\r\n <p-datepicker\r\n fluid\r\n appendTo=\"body\"\r\n [formControl]=\"fechaControl\"\r\n [fluid]=\"true\"\r\n [showIcon]=\"showIcon()\"\r\n [showOnFocus]=\"showOnFocus()\"\r\n [showButtonBar]=\"showButtonBar()\"\r\n [showTime]=\"showTime()\"\r\n [hourFormat]=\"hourFormat()\"\r\n [dateFormat]=\"dateFormat()\"\r\n [placeholder]=\"placeholder()\"\r\n pInputMask=\"99/99/9999 99:99\"\r\n />\r\n <label>{{ label() }}</label>\r\n</p-floatLabel>\r\n\r\n<!-- \u2705 SIN CONDICI\u00D3N - IGUAL QUE EL ORIGINAL -->\r\n<dsx-message-error [control]=\"fechaControl\" />\r\n\r\n<!-- Debug info -->\r\n@if (debugEnabled() && isDevMode) {\r\n<div\r\n class=\"debug-info\"\r\n style=\"\r\n font-size: 12px;\r\n color: #666;\r\n margin-top: 4px;\r\n border-top: 1px dashed #ccc;\r\n padding-top: 4px;\r\n \"\r\n>\r\n <small>\r\n <strong>Debug:</strong>\r\n Value: {{ fechaControl.value | json }} | Valid: {{ fechaControl.valid }} |\r\n Invalid: {{ fechaControl.invalid }} | Errors: {{ fechaControl.errors | json\r\n }} | Dirty: {{ fechaControl.dirty }} | Touched: {{ fechaControl.touched }} |\r\n Status: {{ fechaControl.status }}\r\n </small>\r\n</div>\r\n}\r\n" }]
9207
+ }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], showIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcon", required: false }] }], showOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "showOnFocus", required: false }] }], showButtonBar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButtonBar", required: false }] }], showTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTime", required: false }] }], hourFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "hourFormat", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }] } });
9208
+
8536
9209
  // dsx-input-currency.ts
8537
9210
  class DsxInputCurrency {
8538
9211
  inputNumber;
@@ -10018,35 +10691,210 @@ class ResultFileService {
10018
10691
  * FileContents|fileContents, FileDownloadName|fileDownloadName,
10019
10692
  * ContentType|contentType, title y message.
10020
10693
  *
10021
- * Si el backend no cumple alguno de estos formatos, la conversión puede fallar
10022
- * y el cliente recibirá data: null con isSuccess: false.
10023
- */
10024
- /**
10025
- * Procesa un ServiceResult<FileData> para visualizar o descargar.
10026
- */
10027
- processFileResult(result, visualizar = false) {
10028
- if (!result.isSuccess || !result.data?.blob) {
10029
- this._alertaService.alertaHtmlSuccess(result.title, result.message, {
10030
- icon: 'question',
10031
- icono: '',
10032
- showConfirmButton: true,
10033
- });
10034
- return;
10035
- }
10036
- const { blob, fileName } = result.data;
10037
- if (visualizar) {
10038
- this.PdfView(blob);
10039
- return;
10040
- }
10041
- this.descargarBlob(blob, fileName);
10042
- }
10043
- /**
10044
- * Realiza un GET para descarga y normaliza la respuesta a ServiceResult<FileData>.
10694
+ * Si el backend no cumple alguno de estos formatos, la conversión puede fallar
10695
+ * y el cliente recibirá data: null con isSuccess: false.
10696
+ */
10697
+ /**
10698
+ * Procesa un ServiceResult<FileData> para visualizar o descargar.
10699
+ */
10700
+ processFileResult(result, visualizar = false) {
10701
+ if (!result.isSuccess || !result.data?.blob) {
10702
+ this._alertaService.alertaHtmlSuccess(result.title, result.message, {
10703
+ icon: 'question',
10704
+ icono: '',
10705
+ showConfirmButton: true,
10706
+ });
10707
+ return;
10708
+ }
10709
+ const { blob, fileName } = result.data;
10710
+ if (visualizar) {
10711
+ this.PdfView(blob);
10712
+ return;
10713
+ }
10714
+ this.descargarBlob(blob, fileName);
10715
+ }
10716
+ /**
10717
+ * Realiza una petición GET para descargar un archivo y normaliza la respuesta a un ServiceResult<FileData>.
10718
+ *
10719
+ * 📋 **Descripción General:**
10720
+ * Este método unifica la forma en que se manejan las descargas de archivos, soportando múltiples
10721
+ * formatos de respuesta del backend y normalizándolos a una estructura consistente.
10722
+ *
10723
+ * 🔄 **Formatos de Respuesta Soportados:**
10724
+ *
10725
+ * 1. **Binario Directo** (Recomendado):
10726
+ * - El backend retorna directamente el archivo binario.
10727
+ * - El nombre del archivo se obtiene del header `Content-Disposition`.
10728
+ * - Mensajes opcionales via headers: `x-title`, `x-message`, `title`, `message`.
10729
+ * - Ejemplo:
10730
+ * ```
10731
+ * Content-Type: application/pdf
10732
+ * Content-Disposition: attachment; filename="documento.pdf"
10733
+ * x-title: Éxito
10734
+ * x-message: Descarga completada exitosamente
10735
+ *
10736
+ * [bytes del archivo]
10737
+ * ```
10738
+ *
10739
+ * 2. **JSON Serializado con ServiceResult**:
10740
+ * - El backend retorna un JSON con estructura ServiceResult<FileContentResult>.
10741
+ * - El archivo viene codificado en Base64 o como array de bytes.
10742
+ * - Soporta campos en PascalCase o camelCase.
10743
+ * - Ejemplo:
10744
+ * ```json
10745
+ * {
10746
+ * "isSuccess": true,
10747
+ * "title": "Éxito",
10748
+ * "message": "Archivo generado correctamente",
10749
+ * "data": {
10750
+ * "fileContents": "base64EncodedString...",
10751
+ * "fileDownloadName": "reporte.xlsx",
10752
+ * "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
10753
+ * }
10754
+ * }
10755
+ * ```
10756
+ *
10757
+ * 3. **Error del Backend**:
10758
+ * - Retorna un ServiceResult con isSuccess: false.
10759
+ * - Se extrae el título y mensaje de error.
10760
+ * - Ejemplo:
10761
+ * ```json
10762
+ * {
10763
+ * "isSuccess": false,
10764
+ * "title": "Error de Validación",
10765
+ * "message": "El ID del documento no existe"
10766
+ * }
10767
+ * ```
10768
+ *
10769
+ * 4. **Error de Red/HTTP**:
10770
+ * - Captura errores de conexión, timeout, etc.
10771
+ * - Retorna un ServiceResult con isSuccess: false y mensaje genérico.
10772
+ *
10773
+ * 🎯 **Método de Uso Recomendado:**
10774
+ * ```typescript
10775
+ * // Opción 1: Descarga automática
10776
+ * this.resultFileService.getDownloadableFile('/api/documentos/123')
10777
+ * .subscribe(result => {
10778
+ * this.resultFileService.processFileResult(result, false);
10779
+ * });
10780
+ *
10781
+ * // Opción 2: Visualización de PDF
10782
+ * this.resultFileService.getDownloadableFile('/api/documentos/123')
10783
+ * .subscribe(result => {
10784
+ * this.resultFileService.processFileResult(result, true);
10785
+ * });
10786
+ *
10787
+ * // Opción 3: Procesamiento personalizado
10788
+ * this.resultFileService.getDownloadableFile('/api/documentos/123')
10789
+ * .subscribe({
10790
+ * next: (result) => {
10791
+ * if (result.isSuccess && result.data?.blob) {
10792
+ * // Procesar el blob manualmente
10793
+ * const { blob, fileName } = result.data;
10794
+ * // Hacer algo con el archivo
10795
+ * } else {
10796
+ * // Manejar error
10797
+ * console.error(result.title, result.message);
10798
+ * }
10799
+ * },
10800
+ * error: (error) => {
10801
+ * console.error('Error de red:', error);
10802
+ * }
10803
+ * });
10804
+ * ```
10805
+ *
10806
+ * 📝 **Parámetros:**
10807
+ * @param url - URL del endpoint de descarga.
10808
+ * @param options - Opciones adicionales para la solicitud.
10809
+ * @param options.params - Parámetros de consulta (Query params). Soporta:
10810
+ * - `string`: valor simple
10811
+ * - `string[]`: múltiples valores para el mismo parámetro
10812
+ * - `Date`: se convierte automáticamente a ISO string
10813
+ * - `number`: se convierte a string
10814
+ * - `HttpParams`: instancia directamente
10815
+ * @param options.headers - Headers HTTP personalizados (HttpHeaders).
10816
+ * @param options.withCredentials - Enviar cookies/credenciales en la solicitud.
10817
+ *
10818
+ * 🔍 **Estructura de Retorno:**
10819
+ * ```typescript
10820
+ * Observable<ServiceResult<FileData>>
10821
+ * ```
10822
+ *
10823
+ * Donde `ServiceResult<FileData>` tiene:
10824
+ * ```typescript
10825
+ * {
10826
+ * data: {
10827
+ * blob: Blob, // El archivo como Blob
10828
+ * fileName: string // Nombre del archivo (con extensión)
10829
+ * } | null,
10830
+ * isSuccess: boolean, // Indica si la operación fue exitosa
10831
+ * title: string, // Título del resultado (éxito o error)
10832
+ * message: string // Mensaje descriptivo
10833
+ * }
10834
+ * ```
10835
+ *
10836
+ * ⚠️ **Notas Importantes:**
10837
+ * - El método siempre retorna un Observable que emite un ServiceResult.
10838
+ * - Nunca emite error en el Observable (los errores se convierten a ServiceResult con isSuccess: false).
10839
+ * - Para descarga automática usar `processFileResult(result, false)`.
10840
+ * - Para visualización de PDF usar `processFileResult(result, true)`.
10841
+ * - El nombre del archivo se determina en este orden de prioridad:
10842
+ * 1. `Content-Disposition` header (si existe)
10843
+ * 2. `fileDownloadName` del JSON (si es respuesta JSON)
10844
+ * 3. `fileName` del JSON (si es respuesta JSON)
10845
+ * 4. Valor por defecto: 'document'
10846
+ *
10847
+ * 💡 **Ejemplos de Uso Adicionales:**
10848
+ *
10849
+ * ```typescript
10850
+ * // Con parámetros de consulta
10851
+ * const options = {
10852
+ * params: {
10853
+ * id: 123,
10854
+ * formato: 'pdf',
10855
+ * fechas: ['2024-01-01', '2024-12-31']
10856
+ * }
10857
+ * };
10858
+ * this.resultFileService.getDownloadableFile('/api/reportes', options)
10859
+ * .subscribe(result => {
10860
+ * this.resultFileService.processFileResult(result);
10861
+ * });
10862
+ *
10863
+ * // Con headers personalizados (ej: autenticación)
10864
+ * const options = {
10865
+ * headers: new HttpHeaders({
10866
+ * 'Authorization': 'Bearer ' + token,
10867
+ * 'X-Custom-Header': 'valor'
10868
+ * })
10869
+ * };
10870
+ * this.resultFileService.getDownloadableFile('/api/documentos', options)
10871
+ * .subscribe(result => {
10872
+ * this.resultFileService.processFileResult(result);
10873
+ * });
10874
+ *
10875
+ * // Con credenciales
10876
+ * const options = {
10877
+ * withCredentials: true
10878
+ * };
10879
+ * this.resultFileService.getDownloadableFile('/api/documentos', options)
10880
+ * .subscribe(result => {
10881
+ * this.resultFileService.processFileResult(result);
10882
+ * });
10883
+ * ```
10884
+ *
10885
+ * 🚀 **Flujo de Trabajo Típico:**
10886
+ * 1. Llamar a `getDownloadableFile(url, options)` con la URL del endpoint.
10887
+ * 2. Suscribirse al Observable.
10888
+ * 3. En el `next`, recibir el `ServiceResult<FileData>`.
10889
+ * 4. Usar `processFileResult(result, visualizar)` para:
10890
+ * - Descargar automáticamente (visualizar: false)
10891
+ * - Visualizar PDF en nueva pestaña (visualizar: true)
10892
+ * 5. Alternativamente, procesar el blob manualmente si se necesita.
10045
10893
  *
10046
- * Nota de integración backend:
10047
- * - Para nombre de archivo en binario directo, enviar Content-Disposition.
10048
- * - Para mensajes de éxito, enviar x-title/x-message o title/message en JSON.
10049
- * - Si se envía JSON serializado, incluir los campos de FileContentResult.
10894
+ * @see processFileResult - Para descargar o visualizar el archivo obtenido.
10895
+ * @see FileData - Estructura de datos del archivo.
10896
+ * @see BlobRequestOptions - Opciones de configuración para la solicitud.
10897
+ * @see ServiceResult - Estructura de resultado estandarizada.
10050
10898
  */
10051
10899
  getDownloadableFile(url, options = {}) {
10052
10900
  return this.http
@@ -10251,25 +11099,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
10251
11099
  }] });
10252
11100
 
10253
11101
  class SweetAlert2DialogService {
10254
- /**
10255
- * Configuración del entorno inyectada (opcional).
10256
- * Se usa para obtener el tema global de SweetAlert2 desde el environment.
10257
- */
10258
11102
  environment = inject(ENVIRONMENT, { optional: true });
10259
- /**
10260
- * Tema visual por defecto para todas las alertas SweetAlert2.
10261
- * Se inicializa desde el environment si está configurado, o puede establecerse
10262
- * manualmente llamando a setDefaultTheme().
10263
- *
10264
- * **Prioridad de temas:**
10265
- * 1. Tema específico pasado en options de cada alerta
10266
- * 2. Tema establecido con setDefaultTheme()
10267
- * 3. Tema configurado en environment.sweetAlertTheme
10268
- * 4. Tema por defecto de SweetAlert2
10269
- */
10270
11103
  defaultTheme = undefined;
11104
+ NATIVE_ICONS = [
11105
+ 'success',
11106
+ 'error',
11107
+ 'warning',
11108
+ 'info',
11109
+ 'question',
11110
+ ];
10271
11111
  constructor() {
10272
- // Inicializar el tema desde el environment si está disponible
10273
11112
  if (this.environment?.sweetAlertTheme) {
10274
11113
  const allowed = SWEET_ALERT_THEMES;
10275
11114
  const themeValue = this.environment.sweetAlertTheme;
@@ -10289,59 +11128,102 @@ class SweetAlert2DialogService {
10289
11128
  img.onerror = (err) => reject(err);
10290
11129
  });
10291
11130
  }
10292
- // 4. Implementación real del método
10293
- // 5. Implementación unificada final
10294
- Dialog(firstParam, text, icoOrIcono, options = {}) {
11131
+ // Implementación unificada
11132
+ Dialog(firstParam, text, secondOrThird, thirdOrOptions) {
10295
11133
  let title = '';
10296
- let finalOptions = { ...options };
10297
- let finalIcoOrIcono = icoOrIcono;
10298
- // DETECTAR SI EL PRIMER PARÁMETRO ES EL OBJETO COMPLETO
11134
+ let messageContent = '';
11135
+ let options = {};
11136
+ let iconFromParam = null;
11137
+ let imageFromOptions = null;
11138
+ // === PROCESAR PARÁMETROS ===
10299
11139
  if (typeof firstParam === 'object' && firstParam !== null) {
10300
- const { title: cTitle, message: cMessage, icon, icono, ...cOptions } = firstParam;
10301
- title = cTitle;
10302
- text = cMessage;
10303
- finalIcoOrIcono = icon || icono || '';
10304
- finalOptions = { ...cOptions };
11140
+ // Caso 1: ActionMessageConfig
11141
+ const config = firstParam;
11142
+ title = config.title;
11143
+ messageContent = config.message;
11144
+ // Extraer opciones
11145
+ const { title: _title, message: _message, icon: _icon, icono: _icono, html: _html, footer: _footer, ...restOptions } = config;
11146
+ options = { ...restOptions };
11147
+ if (_html)
11148
+ options.html = _html;
11149
+ if (_footer)
11150
+ options.footer = _footer;
11151
+ // ✅ Si hay icono en el config, es el parámetro
11152
+ if (config.icon) {
11153
+ iconFromParam = config.icon;
11154
+ }
11155
+ // ✅ Si hay icono personalizado en el config
11156
+ if (config.icono) {
11157
+ imageFromOptions = config.icono;
11158
+ }
10305
11159
  }
10306
11160
  else {
11161
+ // Caso 2, 3, 4: Parámetros tradicionales
10307
11162
  title = firstParam;
10308
- }
10309
- // A partir de aquí, tu lógica interna se mantiene exactamente igual usando las variables normalizadas
10310
- let ico = null;
10311
- let iconoImg = null;
10312
- if (finalIcoOrIcono) {
10313
- const nativeIcons = [
10314
- 'success',
10315
- 'error',
10316
- 'warning',
10317
- 'info',
10318
- 'question',
10319
- ];
10320
- if (nativeIcons.includes(finalIcoOrIcono)) {
10321
- ico = finalIcoOrIcono;
11163
+ messageContent = text || '';
11164
+ // Determinar qué tipo de parámetros tenemos
11165
+ if (secondOrThird !== undefined && secondOrThird !== null) {
11166
+ if (typeof secondOrThird === 'string') {
11167
+ // ✅ Es un icono nativo (Caso 4)
11168
+ if (this.NATIVE_ICONS.includes(secondOrThird)) {
11169
+ iconFromParam = secondOrThird;
11170
+ }
11171
+ options = thirdOrOptions || {};
11172
+ }
11173
+ else if (typeof secondOrThird === 'object') {
11174
+ // ✅ Son opciones (Caso 3)
11175
+ options = secondOrThird;
11176
+ // Si hay icono personalizado en options
11177
+ if (options.icono) {
11178
+ imageFromOptions = options.icono;
11179
+ }
11180
+ }
10322
11181
  }
10323
11182
  else {
10324
- iconoImg = finalIcoOrIcono;
11183
+ // Solo título y texto (Caso 2)
11184
+ options = thirdOrOptions || {};
11185
+ if (options.icono) {
11186
+ imageFromOptions = options.icono;
11187
+ }
10325
11188
  }
10326
11189
  }
10327
- // Configuración por defecto de botones y estilos
10328
- const { confirmButtonText = `Aceptar!`, cancelButtonText = `Cancelar`, showCloseButton = true, showCancelButton = true, imageWidth = 150, imageHeight = 150, theme, ...rest } = finalOptions;
11190
+ // PRIORIDAD: imageFromOptions (imagen) > iconFromParam (icono nativo)
11191
+ let ico = null;
11192
+ let iconoImg = null;
11193
+ // 1. Si hay imagen en options, usarla (máxima prioridad)
11194
+ if (imageFromOptions) {
11195
+ iconoImg = imageFromOptions;
11196
+ }
11197
+ // 2. Si hay icono en el parámetro, usarlo
11198
+ else if (iconFromParam) {
11199
+ ico = iconFromParam;
11200
+ }
11201
+ // === CONFIGURACIÓN DE LA ALERTA ===
11202
+ const { confirmButtonText = 'Aceptar', cancelButtonText = 'Cancelar', showCloseButton = true, showCancelButton = true, imageWidth = 150, imageHeight = 150, theme, ...rest } = options;
10329
11203
  const alertTheme = theme !== undefined ? theme : this.defaultTheme;
11204
+ // === CONSTRUIR FOOTER ===
10330
11205
  const year = new Date().getFullYear();
10331
- const baseFooter = this.environment?.sweetAlertFooter ?? 'DEVSOFTXela';
10332
- const footer = `${baseFooter} <strong>${year}</strong>`;
11206
+ const footerText = options.footer ?? this.environment?.sweetAlertFooter ?? 'DEVSOFTXela';
11207
+ const footer = `${footerText} <strong>${year}</strong>`;
11208
+ // === CONSTRUIR CONFIGURACIÓN DE SWEETALERT ===
10333
11209
  const alertConfig = {
10334
11210
  title,
10335
- html: text,
10336
11211
  footer,
10337
11212
  showCloseButton,
10338
11213
  showCancelButton,
10339
11214
  confirmButtonText,
10340
11215
  cancelButtonText,
10341
- //reverseButtons: true,
10342
11216
  ...(alertTheme !== undefined && { theme: alertTheme }),
10343
11217
  ...rest,
10344
11218
  };
11219
+ // === PRIORIDAD DEL CONTENIDO HTML ===
11220
+ if (options.html) {
11221
+ alertConfig.html = options.html;
11222
+ }
11223
+ else if (messageContent) {
11224
+ alertConfig.html = messageContent;
11225
+ }
11226
+ // === IMAGEN O ICONO ===
10345
11227
  if (iconoImg) {
10346
11228
  alertConfig.imageUrl = `/${iconoImg}`;
10347
11229
  alertConfig.imageWidth = imageWidth;
@@ -10365,6 +11247,320 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
10365
11247
  }]
10366
11248
  }], ctorParameters: () => [] });
10367
11249
 
11250
+ class DsxMessagesService {
11251
+ AlertaService = inject(AlertaService);
11252
+ SweetDialog = inject(SweetAlert2DialogService);
11253
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
11254
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, providedIn: 'root' });
11255
+ }
11256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, decorators: [{
11257
+ type: Injectable,
11258
+ args: [{
11259
+ providedIn: 'root',
11260
+ }]
11261
+ }] });
11262
+
11263
+ /**
11264
+ * Obtiene los datos de un FormGroup excluyendo TODAS las propiedades de tipo File
11265
+ *
11266
+ * @param form - FormGroup del cual extraer los datos
11267
+ * @returns Objeto con los datos (sin archivos) y la lista de archivos encontrados
11268
+ *
11269
+ * @internal
11270
+ */
11271
+ function getFormDataExcludingFiles(form) {
11272
+ const rawValue = form.getRawValue();
11273
+ const entries = Object.entries(rawValue);
11274
+ const files = [];
11275
+ const data = {};
11276
+ for (const [key, value] of entries) {
11277
+ if (value instanceof File) {
11278
+ files.push(value);
11279
+ }
11280
+ else {
11281
+ data[key] = value;
11282
+ }
11283
+ }
11284
+ return { data: data, files };
11285
+ }
11286
+ /**
11287
+ * Servicio para guardar entidades y subir archivos automáticamente.
11288
+ *
11289
+ * Este servicio centraliza el flujo común de:
11290
+ * 1. Extraer automáticamente el archivo del FormGroup (por tipo `File`)
11291
+ * 2. Guardar la entidad (sin el archivo) en la base de datos
11292
+ * 3. Si el guardado es exitoso y hay archivo, subirlo
11293
+ * 4. Manejar errores de forma consistente
11294
+ * 5. Resetear el formulario automáticamente en caso de éxito
11295
+ *
11296
+ * @example
11297
+ * // En tu componente:
11298
+ *
11299
+ * // Guardar sin archivo
11300
+ * this._saveEntityPostFile.saveOnly(
11301
+ * this.form,
11302
+ * this._baseService.save.bind(this._baseService),
11303
+ * (response) => {
11304
+ * this.formState.emit(response);
11305
+ * }
11306
+ * );
11307
+ *
11308
+ * // Guardar con archivo (se extrae automáticamente)
11309
+ * this._saveEntityPostFile.saveWithFile(
11310
+ * this.form,
11311
+ * this._baseService.save.bind(this._baseService),
11312
+ * this._baseService.AdjuntarSoporte.bind(this._baseService),
11313
+ * (response) => {
11314
+ * this.formState.emit(response);
11315
+ * }
11316
+ * );
11317
+ */
11318
+ class SaveEntityPostFileService {
11319
+ /** Servicio para mostrar mensajes al usuario */
11320
+ _dsxMessageService = inject(DsxMessagesService);
11321
+ /** Configuración del entorno (para controlar debug) */
11322
+ environment = inject(ENVIRONMENT);
11323
+ /**
11324
+ * Determina si el debug está activo
11325
+ *
11326
+ * @param debug - Solicitud explícita de debug
11327
+ * @returns `true` solo si `debug=true` Y no estamos en producción
11328
+ *
11329
+ * @internal
11330
+ */
11331
+ shouldLog(debug) {
11332
+ return debug && !this.environment.production;
11333
+ }
11334
+ /**
11335
+ * Guarda una entidad Y sube automáticamente el archivo extraído del FormGroup.
11336
+ *
11337
+ * El servicio extrae automáticamente el archivo del FormGroup (por tipo `File`),
11338
+ * lo excluye del objeto que se envía al backend, y después de guardar
11339
+ * exitosamente, procede a subir el archivo usando el ID generado.
11340
+ *
11341
+ * @param form - FormGroup que contiene los datos del formulario
11342
+ * @param saveMethod - Método de save que recibe los datos (sin archivo)
11343
+ * @param uploadMethod - Método de upload que recibe (id, file) y retorna Observable
11344
+ * @param onSuccess - Callback de éxito (opcional)
11345
+ * @param onError - Callback de error (opcional)
11346
+ * @param resetForm - Función para resetear el formulario (opcional)
11347
+ * @param debug - Activa logs en consola (solo funciona en desarrollo). Por defecto `false`.
11348
+ *
11349
+ * @example
11350
+ * // Uso básico
11351
+ * this._saveEntityPostFile.saveWithFile(
11352
+ * this.form,
11353
+ * this._baseService.save.bind(this._baseService),
11354
+ * this._baseService.AdjuntarSoporte.bind(this._baseService),
11355
+ * (response) => {
11356
+ * this.formState.emit(response);
11357
+ * }
11358
+ * );
11359
+ *
11360
+ * @example
11361
+ * // Con reset personalizado
11362
+ * this._saveEntityPostFile.saveWithFile(
11363
+ * this.form,
11364
+ * this._baseService.save.bind(this._baseService),
11365
+ * this._baseService.AdjuntarSoporte.bind(this._baseService),
11366
+ * (response) => {
11367
+ * this.formState.emit(response);
11368
+ * },
11369
+ * (error) => {
11370
+ * console.error('Error:', error);
11371
+ * },
11372
+ * () => {
11373
+ * this.form.reset();
11374
+ * this.getForm();
11375
+ * this.cleanFileInput();
11376
+ * }
11377
+ * );
11378
+ */
11379
+ saveWithFile(form, saveMethod, uploadMethod, deleteMethod, onSuccess, onError, resetForm, debug = false) {
11380
+ this.execute(form, saveMethod, uploadMethod, deleteMethod, onSuccess, onError, resetForm, debug);
11381
+ }
11382
+ /**
11383
+ * Ejecuta el flujo completo de guardado y subida de archivo.
11384
+ *
11385
+ * Este método es privado y contiene la lógica central del servicio.
11386
+ *
11387
+ * ## Flujo de ejecución:
11388
+ * 1. Extrae el archivo del FormGroup (por tipo `File`)
11389
+ * 2. Construye el objeto de datos sin el archivo
11390
+ * 3. Ejecuta el `saveMethod` con los datos limpios
11391
+ * 4. Si el save es exitoso y hay archivo, ejecuta el `uploadMethod`
11392
+ * 5. Si todo es exitoso, ejecuta `resetForm` y `onSuccess`
11393
+ * 6. Si hay error en cualquier paso, ejecuta `onError`
11394
+ *
11395
+ * @param form - FormGroup que contiene los datos del formulario
11396
+ * @param saveMethod - Método de save que recibe los datos (sin archivo)
11397
+ * @param uploadMethod - Método de upload que recibe (id, file) (opcional)
11398
+ * @param onSuccess - Callback de éxito (opcional)
11399
+ * @param onError - Callback de error (opcional)
11400
+ * @param resetForm - Función para resetear el formulario (opcional)
11401
+ * @param debug - Activa logs en consola (solo funciona en desarrollo)
11402
+ *
11403
+ * @internal
11404
+ */
11405
+ execute(form, saveMethod, uploadMethod, deleteMethod, onSuccess, onError, resetForm, debug = false) {
11406
+ const log = this.shouldLog(debug);
11407
+ // 1️⃣ Extraer archivo y datos del FormGroup
11408
+ const { data: saveData, files } = getFormDataExcludingFiles(form);
11409
+ const fileUpload = files.length > 0 ? files[0] : null;
11410
+ if (log) {
11411
+ console.group('📤 [SaveEntityPostFile] Preparando datos');
11412
+ console.log('📄 Datos a guardar (sin archivo):', saveData);
11413
+ console.log('📎 Archivo encontrado:', fileUpload?.name || 'Ninguno');
11414
+ console.groupEnd();
11415
+ }
11416
+ // 2️⃣ Crear el observable del save con los datos sin archivo
11417
+ const save$ = saveMethod(saveData);
11418
+ // 3️⃣ Ejecutar el flujo completo
11419
+ save$.subscribe({
11420
+ next: (saveResult) => {
11421
+ // 📥 Log del save
11422
+ if (log) {
11423
+ console.group('📥 [SaveEntityPostFile] Respuesta del save');
11424
+ console.log('Resultado:', saveResult);
11425
+ console.groupEnd();
11426
+ }
11427
+ // ❌ Si el guardado falla
11428
+ if (!saveResult.isSuccess) {
11429
+ this._dsxMessageService.SweetDialog.Dialog(saveResult.title || 'Error', saveResult.message || 'Error al guardar los datos', 'error', { showConfirmButton: true, showCancelButton: false });
11430
+ onError?.(saveResult);
11431
+ return;
11432
+ }
11433
+ // ✅ Si el guardado es exitoso y hay archivo
11434
+ if (uploadMethod && fileUpload && saveResult.data) {
11435
+ if (log)
11436
+ console.log('📤 [SaveEntityPostFile] Subiendo archivo...');
11437
+ uploadMethod(saveResult.data, fileUpload).subscribe({
11438
+ next: (uploadResult) => {
11439
+ // 📥 Log del upload
11440
+ if (log) {
11441
+ console.group('📥 [SaveEntityPostFile] Respuesta del upload');
11442
+ console.log('Resultado:', uploadResult);
11443
+ console.groupEnd();
11444
+ }
11445
+ if (uploadResult.isSuccess) {
11446
+ // ✅ Éxito: guardado + archivo
11447
+ if (log)
11448
+ console.log('✅ [SaveEntityPostFile] Subida exitosa');
11449
+ resetForm?.();
11450
+ onSuccess?.(uploadResult);
11451
+ }
11452
+ else {
11453
+ // ❌ Error en la subida
11454
+ if (log)
11455
+ console.warn('⚠️ [SaveEntityPostFile] Subida falló');
11456
+ const htmlContent = `
11457
+ <div style="text-align: left; font-family: -apple-system, system-ui, sans-serif;">
11458
+
11459
+ <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 12px;">
11460
+ <span style="font-size: 22px;">⚠️</span>
11461
+ <span style="font-size: 17px; font-weight: 600; color: #1a1a1a;">Archivo no subido</span>
11462
+ </div>
11463
+
11464
+ <p style="color: #555; font-size: 14px; line-height: 1.5; margin: 0 0 8px 0;">
11465
+ El registro se guardó, pero el archivo falló.
11466
+ </p>
11467
+
11468
+ <div style="background: #f8f4ec; padding: 6px 12px; border-radius: 4px; margin: 6px 0 14px 0;">
11469
+ <span style="color: #7a6b4a; font-size: 13px;">
11470
+ <strong>Motivo:</strong> ${uploadResult.message}
11471
+ </span>
11472
+ </div>
11473
+
11474
+ <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #eee;">
11475
+ <p style="font-weight: 500; color: #1a1a1a; font-size: 13px; margin: 0 0 6px 0;">
11476
+ ¿Qué deseas hacer?
11477
+ </p>
11478
+ <div style="font-size: 13px; color: #555; line-height: 1.7;">
11479
+ <div>📁 Guardar sin archivo <span style="color: #999; font-size: 12px;">(subir después)</span></div>
11480
+ <div>🗑️ Eliminar registro <span style="color: #999; font-size: 12px;">(corregir y reintentar)</span></div>
11481
+ </div>
11482
+ </div>
11483
+
11484
+ </div>`;
11485
+ this._dsxMessageService.SweetDialog.Dialog(uploadResult.title || 'Error', '', 'error', {
11486
+ html: htmlContent,
11487
+ showConfirmButton: true,
11488
+ confirmButtonText: 'Guardar',
11489
+ showCancelButton: true,
11490
+ cancelButtonText: 'Eliminar',
11491
+ }).then((isConfirmed) => {
11492
+ if (isConfirmed) {
11493
+ if (log)
11494
+ console.warn('⚠️ [SaveEntityPostFile] Se conservo el registro');
11495
+ resetForm?.();
11496
+ onSuccess?.(saveResult);
11497
+ }
11498
+ else {
11499
+ if (log)
11500
+ console.warn('❌ [SaveEntityPostFile] Se elimino el registro');
11501
+ // 2️⃣ Crear el observable del save con los datos sin archivo
11502
+ const delete$ = deleteMethod(saveResult.data, false);
11503
+ delete$.subscribe({
11504
+ next: (deleteResult) => {
11505
+ // 📥 Log del save
11506
+ if (log) {
11507
+ console.group('📥 [SaveEntityPostFile] Respuesta del delete');
11508
+ console.log('Resultado:', deleteResult);
11509
+ console.groupEnd();
11510
+ }
11511
+ if (deleteResult.isSuccess) {
11512
+ if (log)
11513
+ console.log('✅ [SaveEntityPostFile] Registro eliminado');
11514
+ onSuccess?.(deleteResult);
11515
+ }
11516
+ else {
11517
+ this._dsxMessageService.SweetDialog.Dialog('❌ Error al eliminar', deleteResult.message ||
11518
+ 'No se pudo eliminar el registro.', 'error', {
11519
+ showConfirmButton: true,
11520
+ showCancelButton: false,
11521
+ });
11522
+ onError?.(deleteResult);
11523
+ }
11524
+ },
11525
+ error: (err) => console.warn(err),
11526
+ complete: () => '',
11527
+ });
11528
+ }
11529
+ });
11530
+ onError?.(uploadResult);
11531
+ }
11532
+ },
11533
+ error: (uploadError) => {
11534
+ // ❌ Error en la subida (excepción)
11535
+ console.warn('Error no controlado en upload:', uploadError);
11536
+ onError?.(uploadError);
11537
+ },
11538
+ });
11539
+ return;
11540
+ }
11541
+ // ✅ Éxito sin archivo
11542
+ if (log)
11543
+ console.log('✅ [SaveEntityPostFile] Proceso completado (sin archivo)');
11544
+ resetForm?.();
11545
+ onSuccess?.(saveResult);
11546
+ },
11547
+ error: (error) => {
11548
+ // ❌ Error en el save (excepción)
11549
+ console.warn('Error no controlado en save:', error);
11550
+ onError?.(error);
11551
+ },
11552
+ });
11553
+ }
11554
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveEntityPostFileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
11555
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveEntityPostFileService, providedIn: 'root' });
11556
+ }
11557
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveEntityPostFileService, decorators: [{
11558
+ type: Injectable,
11559
+ args: [{
11560
+ providedIn: 'root',
11561
+ }]
11562
+ }] });
11563
+
10368
11564
  class NextToastNotifyService {
10369
11565
  // Configuraciones iniciales por defecto (ambos activados como en tus switches)
10370
11566
  defaultOptions = {
@@ -10408,8 +11604,10 @@ class ResultResponseService {
10408
11604
  else {
10409
11605
  const guideHtml = this.buildAssociatedGuideHtml(response?.data);
10410
11606
  const mainMessage = this.formatPrimaryAlertMessage(response.message);
10411
- this._sweetAlert2DialogService.Dialog(response.title, `${mainMessage}${guideHtml}`, // Se concatena limpiamente sin alterar la lógica
10412
- 'icon/sign.png', {
11607
+ this._sweetAlert2DialogService.Dialog(response.title,
11608
+ // Se concatena limpiamente sin alterar la lógica
11609
+ `${mainMessage}${guideHtml}`, {
11610
+ icono: 'icon/sign.png',
10413
11611
  showCancelButton: false,
10414
11612
  showConfirmButton: true,
10415
11613
  });
@@ -10455,10 +11653,407 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
10455
11653
  }]
10456
11654
  }] });
10457
11655
 
11656
+ /**
11657
+ * Servicio especializado para extraer y procesar archivos (Blob) desde respuestas del backend.
11658
+ *
11659
+ * 📋 **Propósito:**
11660
+ * Este servicio actúa como un puente entre la respuesta del backend (normalizada como ServiceResult<FileData>)
11661
+ * y el consumidor final, extrayendo el archivo (Blob) y su nombre de forma segura y validada.
11662
+ *
11663
+ * 🎯 **Cuándo usar este servicio:**
11664
+ * - Cuando necesitas obtener el Blob y el nombre de archivo de un Observable<ServiceResult<FileData>>
11665
+ * - Cuando quieres validar que el archivo existe y no está vacío antes de procesarlo
11666
+ * - Cuando necesitas asegurar que el nombre del archivo tenga la extensión correcta
11667
+ * - Cuando quieres un manejo de errores detallado y estructurado
11668
+ *
11669
+ * 🔄 **Flujo de Trabajo:**
11670
+ * 1. El backend retorna un ServiceResult<FileData> (normalizado por getDownloadableFile)
11671
+ * 2. Este servicio extrae el Blob y el fileName
11672
+ * 3. Valida que el archivo exista y tenga contenido
11673
+ * 4. Asegura que el nombre tenga la extensión correcta según el MIME type
11674
+ * 5. Retorna un objeto con { blob, fileName } o un error estructurado
11675
+ *
11676
+ * 🔗 **Relación con otros servicios:**
11677
+ * - **ResultFileService**: Proporciona el Observable<ServiceResult<FileData>> de entrada
11678
+ * - **AlertaService**: Para mostrar mensajes al usuario
11679
+ *
11680
+ * @see ResultFileService - Servicio que realiza la petición HTTP
11681
+ * @see FileData - Estructura de datos del archivo
11682
+ * @see ServiceResult - Estructura de resultado estandarizada
11683
+ * @see Blob - API nativa de Blob de JavaScript
11684
+ */
11685
+ class ResultFileReturnBlobNameService {
11686
+ /** Configuración del entorno (para controlar debug) */
11687
+ environment = inject(ENVIRONMENT);
11688
+ /**
11689
+ * Extrae el Blob y el nombre de archivo de un Observable<ServiceResult<FileData>>,
11690
+ * realizando validaciones exhaustivas y manejo de errores detallado.
11691
+ *
11692
+ * 📝 **Descripción:**
11693
+ * Este método toma el Observable resultante de `getDownloadableFile` y extrae
11694
+ * el archivo (Blob) y su nombre, validando que todo sea correcto antes de
11695
+ * entregar el resultado al consumidor.
11696
+ *
11697
+ * 🔍 **Validaciones Realizadas:**
11698
+ * 1. ✅ Resultado nulo o indefinido → Error: "El resultado es nulo o indefinido"
11699
+ * 2. ✅ isSuccess = false → Error con title y message del backend
11700
+ * 3. ✅ data es null → Error: "El resultado no contiene datos"
11701
+ * 4. ✅ blob es null → Error: "El blob del archivo no está presente"
11702
+ * 5. ✅ blob.size = 0 → Error: "El archivo descargado está vacío (0 bytes)"
11703
+ * 6. ✅ Error de conexión → Error: "No se pudo obtener el archivo del servidor"
11704
+ *
11705
+ * 📁 **Gestión de Extensiones:**
11706
+ * El método automáticamente asegura que el nombre del archivo tenga la extensión
11707
+ * correcta basada en el MIME type. Si el nombre ya tiene extensión, la mantiene.
11708
+ *
11709
+ * 💡 **Ejemplos de Uso:**
11710
+ *
11711
+ * ```typescript
11712
+ * // EJEMPLO 1: Uso básico con descarga automática
11713
+ * const fileObservable = this.resultFileService.getDownloadableFile('/api/documentos/123');
11714
+ *
11715
+ * this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(fileObservable)
11716
+ * .subscribe({
11717
+ * next: ({ blob, fileName }) => {
11718
+ * // Descargar el archivo automáticamente
11719
+ * this.resultFileService.descargarBlob(blob, fileName);
11720
+ * },
11721
+ * error: (error) => {
11722
+ * // Error estructurado
11723
+ * this.alertaService.mostrarError(error.title, error.message);
11724
+ * }
11725
+ * });
11726
+ *
11727
+ * // EJEMPLO 2: Visualizar PDF
11728
+ * const fileObservable = this.resultFileService.getDownloadableFile('/api/documentos/123');
11729
+ *
11730
+ * this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(fileObservable)
11731
+ * .subscribe({
11732
+ * next: ({ blob, fileName }) => {
11733
+ * // Crear URL para visualizar
11734
+ * const url = URL.createObjectURL(blob);
11735
+ * window.open(url, '_blank');
11736
+ * },
11737
+ * error: (error) => {
11738
+ * console.error('Error al visualizar:', error);
11739
+ * }
11740
+ * });
11741
+ *
11742
+ * // EJEMPLO 3: Procesamiento personalizado con validación
11743
+ * const fileObservable = this.resultFileService.getDownloadableFile('/api/reportes', {
11744
+ * params: { formato: 'excel' }
11745
+ * });
11746
+ *
11747
+ * this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(
11748
+ * fileObservable,
11749
+ * 'reporte_generado' // fallbackFileName
11750
+ * ).subscribe({
11751
+ * next: ({ blob, fileName }) => {
11752
+ * // Procesar el blob según necesidades
11753
+ * if (fileName.endsWith('.xlsx')) {
11754
+ * this.procesarExcel(blob);
11755
+ * } else {
11756
+ * this.descargarArchivo(blob, fileName);
11757
+ * }
11758
+ * },
11759
+ * error: (error) => {
11760
+ * // Manejo detallado de errores
11761
+ * if (error.title === 'Archivo vacío') {
11762
+ * this.alertaService.advertencia('El archivo está vacío');
11763
+ * } else if (error.title === 'Error de conexión') {
11764
+ * this.alertaService.error('Error de red', 'Verifica tu conexión');
11765
+ * } else {
11766
+ * this.alertaService.error(error.title, error.message);
11767
+ * }
11768
+ * }
11769
+ * });
11770
+ *
11771
+ * // EJEMPLO 4: Uso con loading state
11772
+ * cargando = false;
11773
+ *
11774
+ * descargarDocumento(id: number): void {
11775
+ * this.cargando = true;
11776
+ * const fileObservable = this.resultFileService.getDownloadableFile(`/api/documentos/${id}`);
11777
+ *
11778
+ * this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(
11779
+ * fileObservable,
11780
+ * `documento_${id}`
11781
+ * ).subscribe({
11782
+ * next: ({ blob, fileName }) => {
11783
+ * this.cargando = false;
11784
+ * this.resultFileService.descargarBlob(blob, fileName);
11785
+ * this.alertaService.exito('Descarga completada');
11786
+ * },
11787
+ * error: (error) => {
11788
+ * this.cargando = false;
11789
+ * this.alertaService.error('Error', error.message);
11790
+ * }
11791
+ * });
11792
+ * }
11793
+ *
11794
+ * // EJEMPLO 5: Combinación con otros operadores RxJS
11795
+ * const fileObservable = this.resultFileService.getDownloadableFile('/api/documentos/123');
11796
+ *
11797
+ * this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(fileObservable)
11798
+ * .pipe(
11799
+ * tap(({ blob, fileName }) => {
11800
+ * console.log(`Archivo: ${fileName}, Tamaño: ${blob.size} bytes`);
11801
+ * }),
11802
+ * delay(1000), // Simular procesamiento
11803
+ * map(({ blob, fileName }) => ({
11804
+ * blob,
11805
+ * fileName,
11806
+ * tipo: this.obtenerTipoArchivo(fileName)
11807
+ * }))
11808
+ * )
11809
+ * .subscribe(({ blob, fileName, tipo }) => {
11810
+ * console.log(`Tipo de archivo: ${tipo}`);
11811
+ * });
11812
+ * ```
11813
+ *
11814
+ * @param fileObservable - Observable que emite un ServiceResult<FileData>
11815
+ * @param fallbackFileName - Nombre alternativo si no se puede determinar el nombre (por defecto: 'archivo sin nombre')
11816
+ *
11817
+ * @returns Observable que emite un objeto con:
11818
+ * - blob: Blob - El archivo extraído
11819
+ * - fileName: string - Nombre del archivo con extensión
11820
+ * - headers?: HttpHeaders - Headers de la respuesta (opcional, actualmente no implementado)
11821
+ *
11822
+ * @throws {Object} En caso de error, emite un objeto con:
11823
+ * - success: false
11824
+ * - title: string - Título del error
11825
+ * - message: string - Mensaje descriptivo
11826
+ * - error?: any - Error original (en caso de error de conexión)
11827
+ *
11828
+ * @see ResultFileService.getDownloadableFile - Método que genera el Observable de entrada
11829
+ * @see ResultFileService.descargarBlob - Para descargar el archivo extraído
11830
+ * @see ResultFileService.PdfView - Para visualizar PDFs
11831
+ */
11832
+ extractFileDataFromServiceResultEnhanced(fileObservable, fallbackFileName = 'archivo sin nombre') {
11833
+ return new Observable((observer) => {
11834
+ fileObservable.subscribe({
11835
+ next: (result) => {
11836
+ // Validación completa del resultado
11837
+ if (!result) {
11838
+ observer.error({
11839
+ success: false,
11840
+ title: 'Resultado nulo',
11841
+ message: 'El resultado es nulo o indefinido',
11842
+ });
11843
+ return;
11844
+ }
11845
+ if (!result.isSuccess) {
11846
+ observer.error({
11847
+ success: false,
11848
+ title: result.title || 'Error en la operación',
11849
+ message: result.message || 'El servidor reportó un error',
11850
+ });
11851
+ return;
11852
+ }
11853
+ if (!result.data) {
11854
+ observer.error({
11855
+ success: false,
11856
+ title: 'Datos no disponibles',
11857
+ message: 'El resultado no contiene datos',
11858
+ });
11859
+ return;
11860
+ }
11861
+ const blob = result.data.blob;
11862
+ if (!blob) {
11863
+ observer.error({
11864
+ success: false,
11865
+ title: 'Archivo no disponible',
11866
+ message: 'El blob del archivo no está presente',
11867
+ });
11868
+ return;
11869
+ }
11870
+ // Validar que el blob tenga tamaño
11871
+ if (blob.size === 0) {
11872
+ observer.error({
11873
+ success: false,
11874
+ title: 'Archivo vacío',
11875
+ message: 'El archivo descargado está vacío (0 bytes)',
11876
+ });
11877
+ return;
11878
+ }
11879
+ // Procesar nombre de archivo
11880
+ let fileName = result.data.fileName || fallbackFileName;
11881
+ fileName = this.ensureFileNameWithExtension(fileName, blob.type);
11882
+ // Log para debugging
11883
+ this.logIfNotProduction('✅ Archivo extraído exitosamente:', {
11884
+ tamaño: this.formatFileSize(blob.size),
11885
+ tipo: blob.type,
11886
+ nombre: fileName,
11887
+ });
11888
+ observer.next({
11889
+ blob: blob,
11890
+ fileName: fileName,
11891
+ });
11892
+ observer.complete();
11893
+ },
11894
+ error: (err) => {
11895
+ this.logIfNotProduction('❌ Error extrayendo archivo:', err);
11896
+ observer.error({
11897
+ success: false,
11898
+ title: 'Error de conexión',
11899
+ message: 'No se pudo obtener el archivo del servidor',
11900
+ error: err,
11901
+ });
11902
+ },
11903
+ });
11904
+ });
11905
+ }
11906
+ /**
11907
+ * Asegura que el nombre de archivo tenga una extensión válida.
11908
+ *
11909
+ * 📋 **Funcionamiento:**
11910
+ * 1. Si el nombre ya tiene una extensión (contiene un punto), lo retorna sin cambios.
11911
+ * 2. Si no tiene extensión, intenta obtenerla del MIME type.
11912
+ * 3. Si se puede determinar la extensión, la agrega al nombre.
11913
+ * 4. Si no se puede determinar, retorna el nombre sin cambios.
11914
+ *
11915
+ * 💡 **Ejemplos:**
11916
+ * - `ensureFileNameWithExtension('documento', 'application/pdf')` → `'documento.pdf'`
11917
+ * - `ensureFileNameWithExtension('foto', 'image/jpeg')` → `'foto.jpg'`
11918
+ * - `ensureFileNameWithExtension('archivo.txt', 'text/plain')` → `'archivo.txt'` (ya tiene extensión)
11919
+ * - `ensureFileNameWithExtension('reporte', 'application/unknown')` → `'reporte'`
11920
+ *
11921
+ * @param fileName - Nombre del archivo (puede o no tener extensión)
11922
+ * @param mimeType - Tipo MIME del archivo
11923
+ * @returns Nombre del archivo con extensión (si fue posible determinarla)
11924
+ */
11925
+ ensureFileNameWithExtension(fileName, mimeType) {
11926
+ // Si ya tiene extensión, retornarlo
11927
+ if (fileName.includes('.')) {
11928
+ return fileName;
11929
+ }
11930
+ // Obtener extensión del MIME type
11931
+ const extension = this.getExtensionFromMimeType(mimeType);
11932
+ return extension ? `${fileName}.${extension}` : fileName;
11933
+ }
11934
+ /**
11935
+ * Obtiene la extensión del archivo basado en el MIME type.
11936
+ *
11937
+ * 📁 **Mapa de MIME Types Soportados:**
11938
+ *
11939
+ * | MIME Type | Extensión |
11940
+ * |-----------|-----------|
11941
+ * | application/pdf | pdf |
11942
+ * | application/msword | doc |
11943
+ * | application/vnd.openxmlformats-officedocument.wordprocessingml.document | docx |
11944
+ * | application/vnd.ms-excel | xls |
11945
+ * | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | xlsx |
11946
+ * | application/vnd.ms-powerpoint | ppt |
11947
+ * | application/vnd.openxmlformats-officedocument.presentationml.presentation | pptx |
11948
+ * | image/jpeg | jpg |
11949
+ * | image/png | png |
11950
+ * | image/gif | gif |
11951
+ * | text/plain | txt |
11952
+ * | application/json | json |
11953
+ * | application/zip | zip |
11954
+ * | application/octet-stream | bin |
11955
+ *
11956
+ * 🔄 **Comportamiento:**
11957
+ * 1. Busca el MIME type en el mapa predefinido.
11958
+ * 2. Si lo encuentra, retorna la extensión correspondiente.
11959
+ * 3. Si no lo encuentra, intenta extraer el subtipo del MIME (ej: 'pdf' de 'application/pdf').
11960
+ * 4. Si el subtipo es 'octet-stream' o no se puede determinar, retorna 'file'.
11961
+ *
11962
+ * @param mimeType - Tipo MIME del archivo
11963
+ * @returns Extensión del archivo (sin el punto) o 'file' si no se puede determinar
11964
+ */
11965
+ getExtensionFromMimeType(mimeType) {
11966
+ const mimeMap = {
11967
+ 'application/pdf': 'pdf',
11968
+ 'application/msword': 'doc',
11969
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
11970
+ 'application/vnd.ms-excel': 'xls',
11971
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
11972
+ 'application/vnd.ms-powerpoint': 'ppt',
11973
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
11974
+ 'image/jpeg': 'jpg',
11975
+ 'image/png': 'png',
11976
+ 'image/gif': 'gif',
11977
+ 'text/plain': 'txt',
11978
+ 'application/json': 'json',
11979
+ 'application/zip': 'zip',
11980
+ 'application/octet-stream': 'bin',
11981
+ };
11982
+ // Buscar extensión en el mapa
11983
+ const extension = mimeMap[mimeType];
11984
+ if (extension) {
11985
+ return extension;
11986
+ }
11987
+ // Si no está en el mapa, intentar extraer del MIME type
11988
+ const mimeParts = mimeType.split('/');
11989
+ if (mimeParts.length === 2) {
11990
+ const type = mimeParts[1];
11991
+ if (type !== 'octet-stream') {
11992
+ return type;
11993
+ }
11994
+ }
11995
+ // Si no se puede determinar, devolver 'file'
11996
+ return 'file';
11997
+ }
11998
+ /**
11999
+ * Formatea el tamaño del archivo para logging.
12000
+ *
12001
+ * 📊 **Formato de Salida:**
12002
+ * - Bytes: `"123 B"`
12003
+ * - Kilobytes: `"1.5 KB"`
12004
+ * - Megabytes: `"2.3 MB"`
12005
+ * - Gigabytes: `"1.2 GB"`
12006
+ *
12007
+ * 💡 **Ejemplos:**
12008
+ * - `formatFileSize(0)` → `"0 B"`
12009
+ * - `formatFileSize(1024)` → `"1.00 KB"`
12010
+ * - `formatFileSize(1536)` → `"1.50 KB"`
12011
+ * - `formatFileSize(1048576)` → `"1.00 MB"`
12012
+ *
12013
+ * @param bytes - Tamaño en bytes
12014
+ * @returns String formateado del tamaño
12015
+ */
12016
+ formatFileSize(bytes) {
12017
+ if (bytes === 0)
12018
+ return '0 B';
12019
+ const k = 1024;
12020
+ const sizes = ['B', 'KB', 'MB', 'GB'];
12021
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
12022
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
12023
+ }
12024
+ /**
12025
+ * Registra mensajes de log solo en entornos de desarrollo.
12026
+ *
12027
+ * 🛠️ **Comportamiento:**
12028
+ * - En desarrollo (production = false): Muestra los logs en consola
12029
+ * - En producción (production = true): No muestra nada
12030
+ *
12031
+ * 📝 **Uso Interno:**
12032
+ * Se utiliza para depuración sin afectar el rendimiento en producción.
12033
+ *
12034
+ * @param args - Argumentos para mostrar en el log
12035
+ */
12036
+ logIfNotProduction(...args) {
12037
+ if (!this.environment.production) {
12038
+ console.log('[ResultFileService]', ...args);
12039
+ }
12040
+ }
12041
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResultFileReturnBlobNameService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
12042
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResultFileReturnBlobNameService, providedIn: 'root' });
12043
+ }
12044
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResultFileReturnBlobNameService, decorators: [{
12045
+ type: Injectable,
12046
+ args: [{
12047
+ providedIn: 'root',
12048
+ }]
12049
+ }] });
12050
+
10458
12051
  class HelpersService {
10459
12052
  Array = inject(ArrayHelpersService);
12053
+ ServiceEntityPostFile = inject(SaveEntityPostFileService);
10460
12054
  ServiceResultFile = inject(ResultFileService);
10461
12055
  ServiceResultResponse = inject(ResultResponseService);
12056
+ ServiceResultFileReturnBlobName = inject(ResultFileReturnBlobNameService);
10462
12057
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HelpersService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10463
12058
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HelpersService, providedIn: 'root' });
10464
12059
  }
@@ -10916,19 +12511,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
10916
12511
  }]
10917
12512
  }] });
10918
12513
 
10919
- class DsxMessagesService {
10920
- AlertaService = inject(AlertaService);
10921
- SweetDialog = inject(SweetAlert2DialogService);
10922
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10923
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, providedIn: 'root' });
10924
- }
10925
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, decorators: [{
10926
- type: Injectable,
10927
- args: [{
10928
- providedIn: 'root',
10929
- }]
10930
- }] });
10931
-
10932
12514
  // form-preview.service.ts
10933
12515
  class FormPreviewService {
10934
12516
  environment;
@@ -12175,5 +13757,5 @@ function sorensenDiceValidator(dataSource, key, umbral = 0.7) {
12175
13757
  * Generated bundle index. Do not edit.
12176
13758
  */
12177
13759
 
12178
- export { ACTION_TYPES, AlertaService, AppMessageErrorComponent, AppMessageHelpComponent, ArrowNavigationDirective, AuthorizeService, AutoScrollHeightDirective, BaseCRUDService, CACHE_KEYS, CacheService, CssV2Component, DSX_PALETTE, DateIndicator, DocxPreviewComponent, DsxAddToolsModule, DsxAutocomplete, DsxButtonComponent, DsxEnableDisable, DsxInputCurrency, DsxMessagesService, DsxSelect, DsxStatusToggle, DsxdateShort, DteService, ENVIRONMENT, EndpointService, ErrorHandlerService, FileComponent, FormPreviewService, GTQFormatter, HeaderDsx, HelpersService, HttpHelpersService, INITIAL_PARAMETERS, IcoLabel, IconDsxComponent, JoinByPipe, JsonHighlightPipe, JsonValuesDebujComponent, JsonViewerComponent, KpicardComponent, LoadingComponent, LoadingLottieComponent, LogoDsxComponent, MasterDetailChangeService, NavbarDsxComponent, NetworkStatusComponent, OnlyRangoPatternDirective, ParameterValuesService, PdfPreviewComponent, PrimeNgModule, QrGenerator, ResultFileService, SWEET_ALERT_THEMES, ScreenInspector, SecurityService, SelectAllOnFocusDirective, SpinnerLoadingService, SweetAlert2DialogService, TablePreviewService, TemplateHighlight, TokenPurposeLogin, TruncatePipe, UtilityAddService, asyncExistsValidator, atLeastOneFieldRequiredValidator, chainControlGroups, createCurrencyFormatter, createInitialCache, createTypedCacheProvider, cuiValidator, dateMinMaxValidator, dateRangeValidator, dateRangeValidatorFromTo, developmentEnvironment, getActionMessageConfig, getZeroBasedRolIndex, guardTokenPurposeGuard, httpAuthorizeInterceptor, minimumAgeValidator, nitValidator, productionEnvironment, provideEnvironment, sorensenDiceValidator, templateStructureValidator, templateVariablesValidator, validateEnvironmentConfig };
13760
+ export { ACTION_TYPES, AlertaService, AppMessageErrorComponent, AppMessageHelpComponent, ArrowNavigationDirective, AuthorizeService, AutoScrollHeightDirective, BaseCRUDService, CACHE_KEYS, CacheService, CssV2Component, DSX_PALETTE, DateIndicator, DocxPreviewComponent, DsxAddToolsModule, DsxAutocomplete, DsxButtonComponent, DsxEnableDisable, DsxInputCurrency, DsxMessagesService, DsxSelect, DsxStatusToggle, DsxdateShort, DsxdateTime, DteService, ENVIRONMENT, EndpointService, ErrorHandlerService, FileComponent, FormPreviewService, GTQFormatter, HeaderDsx, HelpersService, HttpHelpersService, INITIAL_PARAMETERS, IcoLabel, IconDsxComponent, JoinByPipe, JsonHighlightPipe, JsonValuesDebujComponent, JsonViewerComponent, KpicardComponent, LoadingComponent, LoadingLottieComponent, LogoDsxComponent, MasterDetailChangeService, NavbarDsxComponent, NetworkStatusComponent, OnlyRangoPatternDirective, ParameterValuesService, PdfPreviewComponent, PrimeNgModule, QrGenerator, ResultFileService, SWEET_ALERT_THEMES, ScreenInspector, SecurityService, SelectAllOnFocusDirective, SpinnerLoadingService, SweetAlert2DialogService, TablePreviewService, TemplateHighlight, TokenPurposeLogin, TruncatePipe, UtilityAddService, asyncExistsValidator, atLeastOneFieldRequiredValidator, chainControlGroups, createCurrencyFormatter, createInitialCache, createTypedCacheProvider, cuiValidator, dateMinMaxValidator, dateRangeValidator, dateRangeValidatorFromTo, developmentEnvironment, getActionMessageConfig, getZeroBasedRolIndex, guardTokenPurposeGuard, httpAuthorizeInterceptor, minimumAgeValidator, nitValidator, productionEnvironment, provideEnvironment, sorensenDiceValidator, templateStructureValidator, templateVariablesValidator, validateEnvironmentConfig };
12179
13761
  //# sourceMappingURL=ngx-dsxlibrary.mjs.map