ngx-dsxlibrary 2.21.71 → 2.21.73

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, DecimalPipe, DatePipe, 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';
@@ -4300,25 +4301,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
4300
4301
  * | details | Azul índigo | Abrir detalle de registro |
4301
4302
  */
4302
4303
  const DSX_PALETTE = {
4303
- danger: '#EF5350',
4304
- warning: '#FF7043',
4304
+ brown: '#8D6E63',
4305
4305
  caution: '#FFB300',
4306
- success: '#0ba055',
4307
- primary: '#0066cc',
4308
- info: '#26C6DA',
4309
- neutral: '#90A4AE',
4310
- restore: '#B0BEC5',
4311
- return: '#11438d',
4312
- view: '#00838F',
4306
+ cyan: '#00BCD4',
4307
+ danger: '#EF5350',
4308
+ details: '#3949AB',
4309
+ download: '#e6763a',
4313
4310
  excel: '#217346',
4314
- download: '#E65100',
4315
4311
  import: '#546E7A',
4316
- details: '#3949AB',
4312
+ info: '#26C6DA',
4317
4313
  magenta: '#D81B60',
4318
- cyan: '#00BCD4',
4314
+ neutral: '#90A4AE',
4319
4315
  olive: '#808000',
4320
- brown: '#8D6E63',
4316
+ primary: '#0066cc',
4317
+ restore: '#B0BEC5',
4318
+ return: '#11438d',
4321
4319
  slate: '#607D8B',
4320
+ success: '#0ba055',
4321
+ upload: '#5C6BC0', // 👈 Agregado: Índigo para contrastar limpiamente con download (naranja)
4322
+ view: '#00838F',
4323
+ warning: '#FF7043',
4322
4324
  };
4323
4325
  /** Tipos que necesitan un `[id]` mayor a 0 para poder mostrarse. */
4324
4326
  const TYPES_REQUIRING_ID = [
@@ -4931,7 +4933,7 @@ const ACTION_CONFIG = {
4931
4933
  details: {
4932
4934
  label: 'Detalles',
4933
4935
  icon: 'list',
4934
- primeIcon: 'fa-solid fa-list-ol',
4936
+ primeIcon: 'fa-solid fa-list-check',
4935
4937
  colorToken: 'details',
4936
4938
  tooltip: 'Detalle de registro',
4937
4939
  },
@@ -4959,7 +4961,7 @@ const ACTION_CONFIG = {
4959
4961
  download: {
4960
4962
  label: 'Descargar',
4961
4963
  icon: 'download',
4962
- primeIcon: 'pi pi-download',
4964
+ primeIcon: 'fa-solid fa-cloud-arrow-down',
4963
4965
  colorToken: 'download',
4964
4966
  tooltip: 'Descargar archivo',
4965
4967
  },
@@ -4978,7 +4980,6 @@ class FileComponent {
4978
4980
  existingFileName = input(null, ...(ngDevMode ? [{ debugName: "existingFileName" }] : /* istanbul ignore next */ []));
4979
4981
  overrideFileName = input(null, ...(ngDevMode ? [{ debugName: "overrideFileName" }] : /* istanbul ignore next */ []));
4980
4982
  debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
4981
- required = input(true, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
4982
4983
  accept = input('.xls,.xlsx', ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
4983
4984
  pTooltipOverride = input('Seleccione un archivo', ...(ngDevMode ? [{ debugName: "pTooltipOverride" }] : /* istanbul ignore next */ []));
4984
4985
  tooltipPositionOverride = input('top', ...(ngDevMode ? [{ debugName: "tooltipPositionOverride" }] : /* istanbul ignore next */ []));
@@ -4990,6 +4991,7 @@ class FileComponent {
4990
4991
  // Signals de estado
4991
4992
  isReplacing = signal(false, ...(ngDevMode ? [{ debugName: "isReplacing" }] : /* istanbul ignore next */ []));
4992
4993
  disabled = signal(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4994
+ errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
4993
4995
  isViewActive = signal(false, ...(ngDevMode ? [{ debugName: "isViewActive" }] : /* istanbul ignore next */ []));
4994
4996
  value = null;
4995
4997
  touched = false;
@@ -4997,33 +4999,64 @@ class FileComponent {
4997
4999
  // Dependencias
4998
5000
  cdr = inject(ChangeDetectorRef);
4999
5001
  fileUpload = viewChild('fileUpload', ...(ngDevMode ? [{ debugName: "fileUpload" }] : /* istanbul ignore next */ []));
5000
- // Computed properties (solo se "activan" si la vista está activa)
5002
+ // --- Para el manejo de errores del formulario y debug ---
5003
+ _control = null;
5004
+ // Computed para el control de errores
5005
+ errorControl = computed(() => this._control, ...(ngDevMode ? [{ debugName: "errorControl" }] : /* istanbul ignore next */ []));
5006
+ controlErrors = signal(null, ...(ngDevMode ? [{ debugName: "controlErrors" }] : /* istanbul ignore next */ []));
5007
+ controlValid = signal(true, ...(ngDevMode ? [{ debugName: "controlValid" }] : /* istanbul ignore next */ []));
5008
+ controlTouched = signal(false, ...(ngDevMode ? [{ debugName: "controlTouched" }] : /* istanbul ignore next */ []));
5009
+ controlDirty = signal(false, ...(ngDevMode ? [{ debugName: "controlDirty" }] : /* istanbul ignore next */ []));
5010
+ // --- Detección de modo desarrollo ---
5011
+ get isDevMode() {
5012
+ return (typeof ngDevMode !== 'undefined' &&
5013
+ ngDevMode !== null &&
5014
+ ngDevMode !== false);
5015
+ }
5016
+ // ✅ Método para detectar si el control es requerido
5017
+ isRequired() {
5018
+ if (!this._control)
5019
+ return false;
5020
+ return this._control.errors?.['required'] ?? false;
5021
+ }
5022
+ // ============================================
5023
+ // COMPUTED PROPERTIES - LIMPIOS Y CLAROS
5024
+ // ============================================
5025
+ // ✅ Hay archivo guardado en BD (fuente: existingFileName)
5001
5026
  hasExistingFile = computed(() => {
5002
5027
  if (!this.isViewActive())
5003
5028
  return false;
5004
- const hasFile = !!this.existingFileName() && !this.isReplacing() && !this.existingFile();
5005
- if (this.debug() && this.isViewActive())
5006
- this.log('hasExistingFile', { hasFile });
5029
+ const hasFile = !!this.existingFileName() && !this.isReplacing();
5030
+ if (this.debug() && this.isViewActive()) {
5031
+ this.log('hasExistingFile', {
5032
+ hasFile,
5033
+ existingFileName: this.existingFileName(),
5034
+ isReplacing: this.isReplacing(),
5035
+ });
5036
+ }
5007
5037
  return hasFile;
5008
5038
  }, ...(ngDevMode ? [{ debugName: "hasExistingFile" }] : /* istanbul ignore next */ []));
5039
+ // ✅ Mostrar estado de edición (archivo guardado SIN archivo seleccionado)
5009
5040
  showExistingFile = computed(() => {
5010
5041
  if (!this.isViewActive())
5011
5042
  return false;
5012
5043
  const show = this.hasExistingFile() && !this.isReplacing();
5013
- if (this.debug() && this.isViewActive())
5014
- this.log('showExistingFile', { show });
5044
+ if (this.debug() && this.isViewActive()) {
5045
+ this.log('showExistingFile', {
5046
+ show,
5047
+ hasExistingFile: this.hasExistingFile(),
5048
+ isReplacing: this.isReplacing(),
5049
+ });
5050
+ }
5015
5051
  return show;
5016
5052
  }, ...(ngDevMode ? [{ debugName: "showExistingFile" }] : /* istanbul ignore next */ []));
5053
+ // ✅ El upload está habilitado solo si el padre no lo deshabilita
5017
5054
  isFileUploadEnabled = computed(() => {
5018
5055
  if (!this.isViewActive())
5019
5056
  return false;
5020
- if (this.disabled())
5021
- return false;
5022
- const enabled = !this.hasExistingFile() || this.isReplacing();
5023
- if (this.debug() && this.isViewActive())
5024
- this.log('isFileUploadEnabled', { enabled });
5025
- return enabled;
5057
+ return !this.disabled();
5026
5058
  }, ...(ngDevMode ? [{ debugName: "isFileUploadEnabled" }] : /* istanbul ignore next */ []));
5059
+ // ✅ Botón de reemplazo deshabilitado
5027
5060
  isReplaceButtonDisabled = computed(() => {
5028
5061
  if (!this.isViewActive())
5029
5062
  return true;
@@ -5033,14 +5066,28 @@ class FileComponent {
5033
5066
  return true;
5034
5067
  return false;
5035
5068
  }, ...(ngDevMode ? [{ debugName: "isReplaceButtonDisabled" }] : /* istanbul ignore next */ []));
5036
- // --- Constructor: Aquí se crean los effects (contexto de inyección válido) ---
5069
+ // Tipo de archivo permitido para mostrar
5070
+ allowedFileTypeLabel = computed(() => {
5071
+ if (!this.isViewActive())
5072
+ return 'archivo';
5073
+ const acceptStr = this.accept();
5074
+ if (!acceptStr)
5075
+ return 'archivo';
5076
+ const extensions = acceptStr.split(',').map((ext) => ext.trim());
5077
+ const cleanExtensions = extensions
5078
+ .map((ext) => ext.replace('.', '').toUpperCase())
5079
+ .filter((ext) => ext.length > 0);
5080
+ if (cleanExtensions.length === 0)
5081
+ return 'archivo';
5082
+ if (cleanExtensions.length === 1)
5083
+ return cleanExtensions[0];
5084
+ return cleanExtensions.join(' o ');
5085
+ }, ...(ngDevMode ? [{ debugName: "allowedFileTypeLabel" }] : /* istanbul ignore next */ []));
5086
+ // --- Constructor ---
5037
5087
  constructor() {
5038
- // Efecto 1: Re-evaluar validación cuando cambie el estado relevante
5039
5088
  effect(() => {
5040
- // Solo ejecutar si la vista está activa
5041
5089
  if (!this.isViewActive())
5042
5090
  return;
5043
- // "Tocar" estas signals para que el efecto dependa de ellas
5044
5091
  const hasFile = this.hasExistingFile();
5045
5092
  const replacing = this.isReplacing();
5046
5093
  const fileName = this.existingFileName();
@@ -5051,14 +5098,12 @@ class FileComponent {
5051
5098
  fileName,
5052
5099
  });
5053
5100
  if (this.control) {
5054
- // Usar untracked para no crear bucles infinitos
5055
5101
  untracked(() => {
5056
5102
  this.control?.updateValueAndValidity({ emitEvent: false });
5057
5103
  });
5058
5104
  }
5059
5105
  this.cdr.detectChanges();
5060
5106
  });
5061
- // Efecto 2: Cancelar reemplazo si el componente se deshabilita
5062
5107
  effect(() => {
5063
5108
  if (!this.isViewActive())
5064
5109
  return;
@@ -5072,105 +5117,274 @@ class FileComponent {
5072
5117
  });
5073
5118
  }
5074
5119
  ngAfterViewInit() {
5075
- // Activar el componente después de que la vista esté lista
5076
5120
  setTimeout(() => {
5077
5121
  this.isViewActive.set(true);
5078
- if (this.debug())
5079
- console.log('[FileComponent] View activated');
5122
+ if (this.debug()) {
5123
+ console.log('[FileComponent] View activated');
5124
+ console.log('[FileComponent] 📋 Configuración:', {
5125
+ accept: this.accept(),
5126
+ maxFileSize: this.maxFileSize(),
5127
+ debug: this.debug(),
5128
+ existingFileName: this.existingFileName(),
5129
+ });
5130
+ }
5080
5131
  });
5081
5132
  }
5082
5133
  ngOnDestroy() {
5083
5134
  this.isViewActive.set(false);
5084
5135
  if (this.debug())
5085
- console.log('[FileComponent] Component destroyed');
5136
+ console.log('[FileComponent] 🗑️ Destroyed');
5137
+ }
5138
+ // --- Validación de archivo ---
5139
+ validateFile(file) {
5140
+ if (this.debug()) {
5141
+ this.log('🔍 validateFile', {
5142
+ fileName: file.name,
5143
+ fileSize: file.size,
5144
+ accept: this.accept(),
5145
+ maxSize: this.maxFileSize(),
5146
+ });
5147
+ }
5148
+ const acceptStr = this.accept();
5149
+ if (acceptStr) {
5150
+ const acceptedExtensions = acceptStr
5151
+ .split(',')
5152
+ .map((ext) => ext.trim().toLowerCase());
5153
+ const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
5154
+ if (!acceptedExtensions.includes(fileExtension)) {
5155
+ const detail = this.invalidDetail().replace('{0}', this.allowedFileTypeLabel());
5156
+ return { valid: false, error: `${this.invalidSummary()} ${detail}` };
5157
+ }
5158
+ }
5159
+ const maxSizeBytes = this.maxFileSize() * 1024 * 1024;
5160
+ if (file.size > maxSizeBytes) {
5161
+ const detail = this.invalidSizeDetail().replace('{0}', `${this.maxFileSize()} MB`);
5162
+ return { valid: false, error: `${this.invalidSizeSummary()} ${detail}` };
5163
+ }
5164
+ if (this.debug())
5165
+ this.log('✅ validateFile - Válido');
5166
+ return { valid: true };
5086
5167
  }
5087
- // --- Métodos públicos y ControlValueAccessor ---
5168
+ // --- Métodos públicos ---
5088
5169
  onSelect(event) {
5089
- if (!this.isViewActive())
5090
- return;
5091
- if (!this.isFileUploadEnabled()) {
5092
- console.warn('File upload is disabled');
5170
+ if (this.debug()) {
5171
+ this.log('📤 onSelect', {
5172
+ isReplacing: this.isReplacing(),
5173
+ hasExistingFile: this.hasExistingFile(),
5174
+ disabled: this.disabled(),
5175
+ });
5176
+ }
5177
+ if (!this.isViewActive() || this.disabled()) {
5178
+ if (this.disabled())
5179
+ console.warn('[FileComponent] ⚠️ Disabled');
5093
5180
  this.fileUpload()?.clear();
5094
5181
  return;
5095
5182
  }
5096
5183
  const file = event.files?.[0] ?? null;
5097
- if (this.debug())
5098
- this.log('File selected', { name: file?.name });
5184
+ if (!file) {
5185
+ if (this.debug())
5186
+ this.log('⚠️ No file selected');
5187
+ return;
5188
+ }
5189
+ if (this.debug()) {
5190
+ this.log('📄 File selected', {
5191
+ name: file.name,
5192
+ size: this.formatFileSize(file.size),
5193
+ type: file.type,
5194
+ });
5195
+ }
5196
+ const validation = this.validateFile(file);
5197
+ if (!validation.valid) {
5198
+ this.errorMessage.set(validation.error || 'Archivo inválido');
5199
+ this.fileUpload()?.clear();
5200
+ this.value = null;
5201
+ this.onChange(null);
5202
+ setTimeout(() => {
5203
+ this.errorMessage.set(null);
5204
+ if (this.debug())
5205
+ this.log('⏰ Error auto-ocultado');
5206
+ }, 5000);
5207
+ this.updateControlState();
5208
+ return;
5209
+ }
5210
+ // ✅ Archivo válido
5211
+ this.errorMessage.set(null);
5099
5212
  this.value = file;
5100
5213
  this.onChange(file);
5101
5214
  this.markAsTouched();
5102
- if (this.isReplacing() && file) {
5215
+ // Salir del modo reemplazo si estaba activo
5216
+ if (this.isReplacing()) {
5103
5217
  this.isReplacing.set(false);
5218
+ if (this.debug())
5219
+ this.log('🔄 Reemplazo completado', { file: file.name });
5104
5220
  }
5105
5221
  if (this.control) {
5106
5222
  this.control.updateValueAndValidity();
5107
5223
  }
5224
+ this.updateControlState();
5225
+ if (this.debug())
5226
+ this.log('⏱️ onSelect completado');
5108
5227
  }
5109
5228
  clear() {
5229
+ if (this.debug())
5230
+ this.log('🧹 clearFile()');
5110
5231
  if (!this.isViewActive())
5111
5232
  return;
5112
5233
  this.fileUpload()?.clear();
5113
5234
  this.value = null;
5235
+ this.errorMessage.set(null);
5114
5236
  this.onChange(null);
5115
5237
  this.markAsTouched();
5116
5238
  if (this.control)
5117
5239
  this.control.updateValueAndValidity();
5118
- }
5119
- writeValue(value) {
5120
- if (!this.isViewActive())
5121
- return;
5240
+ this.updateControlState();
5122
5241
  if (this.debug())
5123
- this.log('writeValue', { value: value?.name || null });
5124
- this.value = value;
5125
- }
5126
- registerOnChange(fn) {
5127
- this.onChange = fn;
5128
- }
5129
- registerOnTouched(fn) {
5130
- this.onTouched = fn;
5131
- }
5132
- setDisabledState(isDisabled) {
5133
- if (!this.isViewActive())
5134
- return;
5135
- this.disabled.set(isDisabled);
5242
+ this.log(' Archivo limpiado');
5136
5243
  }
5137
5244
  startReplace() {
5245
+ if (this.debug()) {
5246
+ this.log('🔄 startReplace', {
5247
+ hasExistingFile: this.hasExistingFile(),
5248
+ existingFileName: this.existingFileName(),
5249
+ });
5250
+ }
5138
5251
  if (!this.isViewActive())
5139
5252
  return;
5140
- if (this.disabled())
5253
+ if (this.disabled()) {
5254
+ if (this.debug())
5255
+ this.log('⛔ Reemplazo cancelado - disabled');
5256
+ return;
5257
+ }
5258
+ if (!this.hasExistingFile()) {
5259
+ if (this.debug())
5260
+ this.log('⛔ Reemplazo cancelado - no hay archivo existente');
5141
5261
  return;
5262
+ }
5142
5263
  this.fileUpload()?.clear();
5143
5264
  this.value = null;
5265
+ this.errorMessage.set(null);
5144
5266
  this.onChange(null);
5145
5267
  this.isReplacing.set(true);
5146
5268
  if (this.debug())
5147
- this.log('startReplace');
5269
+ this.log('✅ Reemplazo iniciado');
5148
5270
  }
5149
5271
  cancelReplace() {
5272
+ if (this.debug())
5273
+ this.log('❌ cancelReplace');
5150
5274
  if (!this.isViewActive())
5151
5275
  return;
5152
5276
  this.isReplacing.set(false);
5153
5277
  this.fileUpload()?.clear();
5154
5278
  this.value = null;
5279
+ this.errorMessage.set(null);
5155
5280
  this.onChange(null);
5156
5281
  this.markAsTouched();
5157
5282
  if (this.debug())
5158
- this.log('cancelReplace');
5283
+ this.log('✅ Reemplazo cancelado');
5284
+ }
5285
+ choose(event, chooseCallback) {
5286
+ if (this.debug())
5287
+ this.log('🔍 choose - Abriendo selector');
5288
+ chooseCallback();
5289
+ }
5290
+ // --- ControlValueAccessor ---
5291
+ writeValue(value) {
5292
+ if (!this.isViewActive())
5293
+ return;
5294
+ if (this.debug())
5295
+ this.log('✍️ writeValue', { value: value?.name || null });
5296
+ this.value = value;
5297
+ }
5298
+ registerOnChange(fn) {
5299
+ if (this.debug())
5300
+ this.log('📝 registerOnChange');
5301
+ this.onChange = fn;
5302
+ }
5303
+ registerOnTouched(fn) {
5304
+ if (this.debug())
5305
+ this.log('📝 registerOnTouched');
5306
+ this.onTouched = fn;
5307
+ }
5308
+ setDisabledState(isDisabled) {
5309
+ if (!this.isViewActive())
5310
+ return;
5311
+ if (this.debug())
5312
+ this.log('🔒 setDisabledState', { isDisabled });
5313
+ this.disabled.set(isDisabled);
5314
+ }
5315
+ // --- Helpers ---
5316
+ formatFileSize(bytes) {
5317
+ if (!bytes)
5318
+ return '';
5319
+ const sizes = ['B', 'KB', 'MB', 'GB'];
5320
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
5321
+ return `${(bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0)} ${sizes[i]}`;
5322
+ }
5323
+ showError() {
5324
+ return !!(this._control && this._control.invalid && this._control.touched);
5325
+ }
5326
+ updateControlState() {
5327
+ if (this._control) {
5328
+ this.controlValid.set(this._control.valid);
5329
+ this.controlTouched.set(this._control.touched);
5330
+ this.controlDirty.set(this._control.dirty);
5331
+ this.controlErrors.set(this._control.errors);
5332
+ }
5333
+ }
5334
+ // ✅ Método para diagnóstico (solo debug)
5335
+ diagnoseState() {
5336
+ if (this.debug()) {
5337
+ console.log('🔍 === DIAGNÓSTICO ===');
5338
+ console.log('isReplacing:', this.isReplacing());
5339
+ console.log('existingFileName:', this.existingFileName());
5340
+ console.log('hasExistingFile:', this.hasExistingFile());
5341
+ console.log('showExistingFile:', this.showExistingFile());
5342
+ console.log('isFileUploadEnabled:', this.isFileUploadEnabled());
5343
+ console.log('isReplaceButtonDisabled:', this.isReplaceButtonDisabled());
5344
+ console.log('value:', this.value);
5345
+ console.log('disabled:', this.disabled());
5346
+ console.log('isRequired:', this.isRequired());
5347
+ console.log('🔍 === FIN ===');
5348
+ }
5349
+ }
5350
+ forceReload() {
5351
+ if (this.debug()) {
5352
+ console.log('[FileComponent] 🔄 Force reload');
5353
+ if (this._control) {
5354
+ this._control.updateValueAndValidity();
5355
+ this.updateControlState();
5356
+ }
5357
+ this.cdr.detectChanges();
5358
+ }
5159
5359
  }
5160
5360
  // --- Validación ---
5161
5361
  validate(control) {
5362
+ this._control = control;
5162
5363
  this.control = control;
5163
5364
  if (!this.isViewActive())
5164
5365
  return null;
5165
- if (!this.required())
5366
+ if (this.debug()) {
5367
+ this.log('🔍 validate', {
5368
+ isRequired: this.isRequired(),
5369
+ hasExistingFile: this.hasExistingFile(),
5370
+ isReplacing: this.isReplacing(),
5371
+ hasValue: !!this.value,
5372
+ valueName: this.value?.name || null,
5373
+ errorMessage: this.errorMessage(),
5374
+ });
5375
+ }
5376
+ this.updateControlState();
5377
+ if (this.errorMessage()) {
5378
+ return { invalidFile: true };
5379
+ }
5380
+ if (!this.isRequired()) {
5166
5381
  return null;
5167
- if (this.hasExistingFile())
5382
+ }
5383
+ // ✅ Pasa si hay archivo guardado O hay archivo seleccionado
5384
+ if (this.hasExistingFile() || this.value) {
5168
5385
  return null;
5169
- if (this.isReplacing() && !this.value)
5170
- return { required: true };
5171
- if (!this.hasExistingFile() && !this.value)
5172
- return { required: true };
5173
- return null;
5386
+ }
5387
+ return { required: true };
5174
5388
  }
5175
5389
  // --- Privados ---
5176
5390
  onChange = (_) => { };
@@ -5179,13 +5393,15 @@ class FileComponent {
5179
5393
  if (!this.touched) {
5180
5394
  this.touched = true;
5181
5395
  this.onTouched();
5396
+ if (this.debug())
5397
+ this.log('👆 markAsTouched');
5182
5398
  }
5183
5399
  }
5184
5400
  log(method, data) {
5185
5401
  console.log(`[FileComponent] ${method}`, data || '');
5186
5402
  }
5187
5403
  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: [
5404
+ 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
5405
  {
5190
5406
  provide: NG_VALUE_ACCESSOR,
5191
5407
  useExisting: forwardRef(() => FileComponent),
@@ -5196,11 +5412,11 @@ class FileComponent {
5196
5412
  useExisting: forwardRef(() => FileComponent),
5197
5413
  multi: true,
5198
5414
  },
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"] }] });
5415
+ ], 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)=\"clear()\"\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 <!-- Bot\u00F3n para cancelar remplazo -->\r\n @if (isReplacing()) {\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 <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 <!-- === ESTADOS ADICIONALES PARA DEBUG === -->\r\n <span\r\n class=\"debug-item\"\r\n style=\"background: #f0f0f0; padding: 0 6px; border-radius: 4px\"\r\n >\r\n \uD83D\uDCCA hasExistingFile: {{ hasExistingFile() ? \"\u2705\" : \"\u274C\" }}\r\n </span>\r\n <span\r\n class=\"debug-item\"\r\n style=\"background: #f0f0f0; padding: 0 6px; border-radius: 4px\"\r\n >\r\n \uD83D\uDCCA existingFileName: {{ existingFileName() || \"null\" }}\r\n </span>\r\n <span\r\n class=\"debug-item\"\r\n style=\"background: #f0f0f0; padding: 0 6px; border-radius: 4px\"\r\n >\r\n \uD83D\uDCCA isFileUploadEnabled: {{ isFileUploadEnabled() ? \"\u2705\" : \"\u274C\" }}\r\n </span>\r\n <span\r\n class=\"debug-item\"\r\n style=\"background: #f0f0f0; padding: 0 6px; border-radius: 4px\"\r\n >\r\n \uD83D\uDCCA showExistingFile: {{ showExistingFile() ? \"\u2705\" : \"\u274C\" }}\r\n </span>\r\n <span\r\n class=\"debug-item\"\r\n style=\"background: #f0f0f0; padding: 0 6px; border-radius: 4px\"\r\n >\r\n \uD83D\uDCCA value: {{ value ? value.name : \"null\" }}\r\n </span>\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)=\"clear()\" class=\"debug-btn\" type=\"button\">\r\n \uD83E\uDDF9 Limpiar\r\n </button>\r\n <button (click)=\"diagnoseState()\" class=\"debug-btn\" type=\"button\">\r\n \uD83D\uDCCA Diagn\u00F3stico\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-weight:500;color:#1d8678;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:#22994a}.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
5416
  }
5201
5417
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FileComponent, decorators: [{
5202
5418
  type: Component,
5203
- args: [{ selector: 'dsx-file-upload', imports: [FileUpload, Tooltip, Button], providers: [
5419
+ args: [{ selector: 'dsx-file-upload', imports: [FileUpload, Tooltip, Button, JsonPipe, AppMessageErrorComponent], providers: [
5204
5420
  {
5205
5421
  provide: NG_VALUE_ACCESSOR,
5206
5422
  useExisting: forwardRef(() => FileComponent),
@@ -5211,187 +5427,339 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
5211
5427
  useExisting: forwardRef(() => FileComponent),
5212
5428
  multi: true,
5213
5429
  },
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 }] }] } });
5430
+ ], 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)=\"clear()\"\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 <!-- Bot\u00F3n para cancelar remplazo -->\r\n @if (isReplacing()) {\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 <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 <!-- === ESTADOS ADICIONALES PARA DEBUG === -->\r\n <span\r\n class=\"debug-item\"\r\n style=\"background: #f0f0f0; padding: 0 6px; border-radius: 4px\"\r\n >\r\n \uD83D\uDCCA hasExistingFile: {{ hasExistingFile() ? \"\u2705\" : \"\u274C\" }}\r\n </span>\r\n <span\r\n class=\"debug-item\"\r\n style=\"background: #f0f0f0; padding: 0 6px; border-radius: 4px\"\r\n >\r\n \uD83D\uDCCA existingFileName: {{ existingFileName() || \"null\" }}\r\n </span>\r\n <span\r\n class=\"debug-item\"\r\n style=\"background: #f0f0f0; padding: 0 6px; border-radius: 4px\"\r\n >\r\n \uD83D\uDCCA isFileUploadEnabled: {{ isFileUploadEnabled() ? \"\u2705\" : \"\u274C\" }}\r\n </span>\r\n <span\r\n class=\"debug-item\"\r\n style=\"background: #f0f0f0; padding: 0 6px; border-radius: 4px\"\r\n >\r\n \uD83D\uDCCA showExistingFile: {{ showExistingFile() ? \"\u2705\" : \"\u274C\" }}\r\n </span>\r\n <span\r\n class=\"debug-item\"\r\n style=\"background: #f0f0f0; padding: 0 6px; border-radius: 4px\"\r\n >\r\n \uD83D\uDCCA value: {{ value ? value.name : \"null\" }}\r\n </span>\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)=\"clear()\" class=\"debug-btn\" type=\"button\">\r\n \uD83E\uDDF9 Limpiar\r\n </button>\r\n <button (click)=\"diagnoseState()\" class=\"debug-btn\" type=\"button\">\r\n \uD83D\uDCCA Diagn\u00F3stico\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-weight:500;color:#1d8678;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:#22994a}.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"] }]
5431
+ }], 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 }] }] } });
5216
5432
 
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';
5433
+ function uid() {
5434
+ return Math.random().toString(36).substring(2, 8);
5435
+ }
5436
+ class JsonValuesDebujComponent {
5437
+ form = input(null, ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
5438
+ debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
5439
+ debounceTime = input(300, ...(ngDevMode ? [{ debugName: "debounceTime" }] : /* istanbul ignore next */ []));
5440
+ debugValue = signal(null, ...(ngDevMode ? [{ debugName: "debugValue" }] : /* istanbul ignore next */ []));
5441
+ serializedValue = signal('', ...(ngDevMode ? [{ debugName: "serializedValue" }] : /* istanbul ignore next */ []));
5442
+ fileInfo = signal(null, ...(ngDevMode ? [{ debugName: "fileInfo" }] : /* istanbul ignore next */ []));
5443
+ filesSummary = signal('', ...(ngDevMode ? [{ debugName: "filesSummary" }] : /* istanbul ignore next */ []));
5444
+ isDevModeSignal = computed(() => isDevMode(), ...(ngDevMode ? [{ debugName: "isDevModeSignal" }] : /* istanbul ignore next */ []));
5445
+ isDebugEnabledSignal = computed(() => {
5446
+ const isDev = this.isDevModeSignal();
5447
+ const debug = this.debug();
5448
+ const result = isDev && debug;
5449
+ if (this._lastDebugState !== result) {
5450
+ this._lastDebugState = result;
5451
+ if (result) {
5452
+ console.log(`[JsonDebug:${this.id}] 📝 Logs activados`, {
5453
+ isDevMode: isDev,
5454
+ debug: debug,
5455
+ });
5456
+ }
5225
5457
  }
5226
- if (typeof obj !== 'object') {
5227
- if (typeof obj === 'string') {
5228
- return JSON.stringify(obj);
5458
+ return result;
5459
+ }, ...(ngDevMode ? [{ debugName: "isDebugEnabledSignal" }] : /* istanbul ignore next */ []));
5460
+ _lastDebugState = null;
5461
+ _lastVisualState = null;
5462
+ id = uid();
5463
+ subscription;
5464
+ changesCount = 0;
5465
+ isInitialized = false;
5466
+ shouldShowVisual = computed(() => {
5467
+ const show = this.isDevModeSignal();
5468
+ if (this._lastVisualState !== show) {
5469
+ this._lastVisualState = show;
5470
+ if (this.shouldShowLogs()) {
5471
+ console.log(`[JsonDebug:${this.id}] 👁️ Visual: ${show}`, {
5472
+ isDevMode: isDevMode(),
5473
+ reason: show ? 'Modo desarrollo activo' : 'Modo producción',
5474
+ });
5229
5475
  }
5230
- return String(obj);
5231
5476
  }
5232
- if (obj instanceof Date) {
5233
- return JSON.stringify(obj.toISOString());
5477
+ return show;
5478
+ }, ...(ngDevMode ? [{ debugName: "shouldShowVisual" }] : /* istanbul ignore next */ []));
5479
+ shouldShowLogs = computed(() => {
5480
+ const isDev = this.isDevModeSignal();
5481
+ const debug = this.debug();
5482
+ const show = isDev && debug;
5483
+ if (this._lastDebugState !== show) {
5484
+ this._lastDebugState = show;
5485
+ if (show) {
5486
+ console.log(`[JsonDebug:${this.id}] 📝 Logs activados`, {
5487
+ isDevMode: isDev,
5488
+ debug: debug,
5489
+ });
5490
+ }
5234
5491
  }
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());
5492
+ return show;
5493
+ }, ...(ngDevMode ? [{ debugName: "shouldShowLogs" }] : /* istanbul ignore next */ []));
5494
+ constructor() {
5495
+ if (this.shouldShowLogs()) {
5496
+ console.log(`[JsonDebug:${this.id}] 🏗️ Constructor`, {
5497
+ id: this.id,
5498
+ isDevMode: isDevMode(),
5499
+ debug: this.debug(),
5500
+ debounceTime: this.debounceTime(),
5501
+ });
5502
+ }
5503
+ effect((onCleanup) => {
5504
+ const form = this.form();
5505
+ if (!form) {
5506
+ if (this.shouldShowLogs() && this.isInitialized) {
5507
+ console.log(`[JsonDebug:${this.id}] ⚠️ Formulario eliminado`);
5240
5508
  }
5241
- if (typeof item === 'object' && item !== null) {
5242
- return this.compactStringify(item, indent);
5509
+ if (this.subscription) {
5510
+ this.subscription.unsubscribe();
5511
+ this.subscription = undefined;
5512
+ }
5513
+ this.isInitialized = false;
5514
+ return;
5515
+ }
5516
+ if (this.isInitialized && this.subscription) {
5517
+ if (this.shouldShowLogs()) {
5518
+ console.log(`[JsonDebug:${this.id}] 🔄 Formulario ya inicializado, saltando...`);
5519
+ }
5520
+ return;
5521
+ }
5522
+ if (this.shouldShowLogs()) {
5523
+ console.log(`[JsonDebug:${this.id}] 🔄 Effect iniciado`, {
5524
+ controls: Object.keys(form.controls).length,
5525
+ timestamp: new Date().toISOString(),
5526
+ debounceTime: this.debounceTime(),
5527
+ });
5528
+ }
5529
+ if (this.subscription) {
5530
+ this.subscription.unsubscribe();
5531
+ this.subscription = undefined;
5532
+ }
5533
+ const rawValue = form.getRawValue();
5534
+ this.debugValue.set(rawValue);
5535
+ this.updateSerializedValue(rawValue);
5536
+ this.updateFileInfo(rawValue);
5537
+ if (this.shouldShowLogs()) {
5538
+ const filesFound = this.findFilesInObject(rawValue);
5539
+ console.log(`[JsonDebug:${this.id}] 📊 Procesamiento inicial`, {
5540
+ hasFile: !!rawValue?.file,
5541
+ fileType: rawValue?.file ? typeof rawValue.file : 'N/A',
5542
+ serializedLength: this.serializedValue().length,
5543
+ filesFound: filesFound.length,
5544
+ filesSummary: filesFound.map((f) => f.path).join(', '),
5545
+ });
5546
+ }
5547
+ this.subscription = form.valueChanges
5548
+ .pipe(debounceTime(this.debounceTime()), distinctUntilChanged((prev, curr) => {
5549
+ return JSON.stringify(prev) === JSON.stringify(curr);
5550
+ }))
5551
+ .subscribe(() => {
5552
+ const startTime = performance.now();
5553
+ const rawValue = form.getRawValue();
5554
+ this.debugValue.set(rawValue);
5555
+ this.updateSerializedValue(rawValue);
5556
+ this.updateFileInfo(rawValue);
5557
+ this.changesCount++;
5558
+ const endTime = performance.now();
5559
+ if (this.shouldShowLogs()) {
5560
+ const elapsed = endTime - startTime;
5561
+ const filesFound = this.findFilesInObject(rawValue);
5562
+ console.log(`[JsonDebug:${this.id}] 🔄 Cambio #${this.changesCount}`, {
5563
+ tiempoMs: elapsed.toFixed(2),
5564
+ hasFile: !!rawValue?.file,
5565
+ serializedLength: this.serializedValue().length,
5566
+ filesFound: filesFound.length,
5567
+ filesSummary: filesFound
5568
+ .map((f) => `${f.path} (${f.file?.name || 'sin nombre'})`)
5569
+ .join(', '),
5570
+ warning: elapsed > 10 ? '⚠️ Cambio lento' : '✅ OK',
5571
+ });
5572
+ if (this.changesCount % 10 === 0) {
5573
+ console.log(`[JsonDebug:${this.id}] 📊 Estadísticas`, {
5574
+ cambios: this.changesCount,
5575
+ promedioMs: (elapsed / this.changesCount).toFixed(2),
5576
+ ultimoMs: elapsed.toFixed(2),
5577
+ });
5578
+ }
5243
5579
  }
5244
- return typeof item === 'string'
5245
- ? JSON.stringify(item)
5246
- : item === null
5247
- ? 'null'
5248
- : String(item);
5249
5580
  });
5250
- return `[${items.join(', ')}]`;
5581
+ this.isInitialized = true;
5582
+ onCleanup(() => {
5583
+ if (this.shouldShowLogs()) {
5584
+ console.log(`[JsonDebug:${this.id}] 🧹 Limpieza effect`, {
5585
+ cambiosProcesados: this.changesCount,
5586
+ isInitialized: this.isInitialized,
5587
+ });
5588
+ }
5589
+ });
5590
+ });
5591
+ }
5592
+ // ✅ Método recursivo para encontrar archivos en toda la estructura
5593
+ findFilesInObject(obj, path = 'root') {
5594
+ const files = [];
5595
+ if (!obj || typeof obj !== 'object') {
5596
+ return files;
5597
+ }
5598
+ // Verificar si el objeto actual tiene un archivo
5599
+ if (obj.file && (obj.file instanceof File || obj.file instanceof Blob)) {
5600
+ files.push({
5601
+ path: path,
5602
+ file: obj.file,
5603
+ name: obj.file.name || 'sin nombre',
5604
+ size: obj.file.size || 0,
5605
+ type: obj.file.type || 'desconocido',
5606
+ });
5251
5607
  }
5252
- // Para objetos, mantener la indentación
5253
- const keys = Object.keys(obj);
5254
- if (keys.length === 0) {
5255
- return '{}';
5608
+ // Si es un array, recorrer sus elementos
5609
+ if (Array.isArray(obj)) {
5610
+ obj.forEach((item, index) => {
5611
+ const childFiles = this.findFilesInObject(item, `${path}[${index}]`);
5612
+ files.push(...childFiles);
5613
+ });
5256
5614
  }
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}}`;
5615
+ // Si es un objeto, recorrer sus propiedades
5616
+ else if (typeof obj === 'object') {
5617
+ Object.keys(obj).forEach((key) => {
5618
+ // Saltar la propiedad 'file' si ya la procesamos y es un File
5619
+ if (key === 'file' &&
5620
+ (obj.file instanceof File || obj.file instanceof Blob)) {
5621
+ return;
5622
+ }
5623
+ const childFiles = this.findFilesInObject(obj[key], `${path}.${key}`);
5624
+ files.push(...childFiles);
5625
+ });
5626
+ }
5627
+ return files;
5273
5628
  }
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}`);
5629
+ updateSerializedValue(value) {
5630
+ if (!value) {
5631
+ this.serializedValue.set('');
5632
+ return;
5633
+ }
5634
+ try {
5635
+ // ✅ Clonar y procesar archivos en toda la estructura
5636
+ const copy = this.cloneWithFileProcessing(value);
5637
+ this.serializedValue.set(JSON.stringify(copy, null, 2));
5638
+ }
5639
+ catch (error) {
5640
+ this.serializedValue.set('Error al serializar: ' + error);
5641
+ if (this.shouldShowLogs()) {
5642
+ console.error('[JsonDebug] Error serializing value:', error);
5281
5643
  }
5282
5644
  }
5283
- if (!value) {
5284
- return '';
5645
+ }
5646
+ // ✅ Clonar objeto procesando archivos recursivamente
5647
+ cloneWithFileProcessing(obj) {
5648
+ if (!obj || typeof obj !== 'object') {
5649
+ return obj;
5285
5650
  }
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>`;
5651
+ // Si es un File o Blob, extraer sus propiedades
5652
+ if (obj instanceof File || obj instanceof Blob) {
5653
+ const fileProps = {
5654
+ __type: obj instanceof File ? 'File' : 'Blob',
5655
+ };
5656
+ if ('name' in obj && obj.name !== undefined) {
5657
+ fileProps.name = obj.name;
5298
5658
  }
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>`;
5659
+ if ('size' in obj && obj.size !== undefined) {
5660
+ fileProps.size = obj.size;
5303
5661
  }
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>';
5662
+ if ('type' in obj && obj.type !== undefined) {
5663
+ fileProps.type = obj.type;
5310
5664
  }
5311
- return `<span class="json-boolean">${match}</span>`;
5665
+ if ('lastModified' in obj && obj.lastModified !== undefined) {
5666
+ fileProps.lastModified = obj.lastModified;
5667
+ }
5668
+ return fileProps;
5669
+ }
5670
+ // Si es un array, procesar cada elemento
5671
+ if (Array.isArray(obj)) {
5672
+ return obj.map((item) => this.cloneWithFileProcessing(item));
5673
+ }
5674
+ // Si es un objeto, procesar cada propiedad
5675
+ const result = {};
5676
+ Object.keys(obj).forEach((key) => {
5677
+ // Si la propiedad es 'file' y es un File/Blob, procesarla
5678
+ if (key === 'file' &&
5679
+ (obj[key] instanceof File || obj[key] instanceof Blob)) {
5680
+ result[key] = this.cloneWithFileProcessing(obj[key]);
5681
+ }
5682
+ else {
5683
+ result[key] = this.cloneWithFileProcessing(obj[key]);
5684
+ }
5685
+ });
5686
+ return result;
5687
+ }
5688
+ updateFileInfo(value) {
5689
+ if (!value) {
5690
+ this.fileInfo.set(null);
5691
+ this.filesSummary.set('');
5692
+ return;
5693
+ }
5694
+ // ✅ Buscar todos los archivos en la estructura
5695
+ const files = this.findFilesInObject(value);
5696
+ if (files.length === 0) {
5697
+ this.fileInfo.set({
5698
+ exists: false,
5699
+ isEmpty: true,
5700
+ message: 'No hay archivos seleccionados',
5701
+ count: 0,
5702
+ });
5703
+ this.filesSummary.set('📭 Sin archivos');
5704
+ return;
5705
+ }
5706
+ // ✅ Resumir información de archivos
5707
+ const summary = files
5708
+ .map((f) => {
5709
+ const name = f.file?.name || 'sin nombre';
5710
+ const size = f.file?.size || 0;
5711
+ const sizeStr = size > 0 ? `${(size / 1024).toFixed(1)} KB` : '0 KB';
5712
+ return `${name} (${sizeStr}) en ${f.path}`;
5312
5713
  })
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>');
5714
+ .join('; ');
5715
+ this.filesSummary.set(`📁 ${files.length} archivo(s): ${summary}`);
5716
+ // Información detallada
5717
+ const info = {
5718
+ exists: true,
5719
+ count: files.length,
5720
+ files: files.map((f) => ({
5721
+ path: f.path,
5722
+ name: f.name,
5723
+ size: f.size,
5724
+ type: f.type,
5725
+ })),
5726
+ summary: summary,
5727
+ };
5728
+ this.fileInfo.set(info);
5317
5729
  }
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
- }] });
5327
-
5328
- function uid() {
5329
- return Math.random().toString(36).substring(2, 8);
5330
- }
5331
- class JsonValuesDebujComponent {
5332
- form = input(null, ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
5333
- debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
5334
- debugValue = signal(null, ...(ngDevMode ? [{ debugName: "debugValue" }] : /* istanbul ignore next */ []));
5335
- id = uid();
5336
- subscription;
5337
- changesCount = 0;
5338
- // Indica si debemos mostrar el componente visualmente
5339
- get shouldShowVisual() {
5340
- // Solo mostrar visualmente en modo desarrollo
5341
- return isDevMode();
5730
+ hasFileProperty() {
5731
+ const value = this.debugValue();
5732
+ if (!value)
5733
+ return false;
5734
+ // ✅ Buscar archivos en toda la estructura
5735
+ const files = this.findFilesInObject(value);
5736
+ return files.length > 0;
5342
5737
  }
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;
5738
+ getFileInfo() {
5739
+ return this.fileInfo();
5347
5740
  }
5348
- constructor() {
5349
- if (this.shouldShowLogs) {
5350
- console.log('[JsonDebug] CONSTRUCTOR', this.id);
5351
- }
5352
- effect((onCleanup) => {
5353
- const form = this.form();
5354
- if (!form)
5355
- return;
5356
- if (this.shouldShowLogs) {
5357
- console.log(`[json-debug:${this.id}] EFFECT INIT`, {
5358
- controls: Object.keys(form.controls).length,
5359
- });
5360
- }
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(),
5369
- });
5370
- }
5371
- });
5372
- onCleanup(() => {
5373
- this.subscription?.unsubscribe();
5374
- });
5375
- });
5741
+ getFilesSummary() {
5742
+ return this.filesSummary();
5376
5743
  }
5377
5744
  ngOnDestroy() {
5378
- if (this.shouldShowLogs) {
5379
- console.log('[JsonDebug] DESTROY', this.id);
5380
- }
5381
- this.subscription?.unsubscribe();
5382
- if (this.shouldShowLogs) {
5383
- console.log(`[json-debug:${this.id}] DESTROY SUMMARY`, {
5745
+ if (this.shouldShowLogs()) {
5746
+ console.log(`[JsonDebug:${this.id}] 💀 Destroy`, {
5384
5747
  totalChanges: this.changesCount,
5748
+ tiempoVida: `${(performance.now() / 1000).toFixed(2)}s`,
5385
5749
  });
5386
5750
  }
5751
+ if (this.subscription) {
5752
+ this.subscription.unsubscribe();
5753
+ this.subscription = undefined;
5754
+ }
5387
5755
  }
5388
5756
  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 });
5757
+ 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 <!-- \u2705 Informaci\u00F3n de archivos encontrados en toda la estructura -->\r\n @if (hasFileProperty()) {\r\n <div class=\"file-info-box\">\r\n <div class=\"file-info-header\">\r\n \uD83D\uDCC1 Archivos encontrados ({{ getFileInfo()?.count || 0 }})\r\n </div>\r\n <div class=\"file-info-grid\">\r\n @for (file of getFileInfo()?.files || []; track file.path) {\r\n <div class=\"file-info-item file-item\">\r\n <div class=\"file-path\">\uD83D\uDCC4 {{ file.path }}</div>\r\n <div class=\"file-details\">\r\n <span class=\"label\">Nombre:</span>\r\n <span class=\"value\">{{ file.name }}</span>\r\n </div>\r\n <div class=\"file-details\">\r\n <span class=\"label\">Tama\u00F1o:</span>\r\n <span class=\"value\"\r\n >{{ file.size / 1024 | number: \"1.1-1\" }} KB</span\r\n >\r\n </div>\r\n <div class=\"file-details\">\r\n <span class=\"label\">Tipo:</span>\r\n <span class=\"value\">{{ file.type || \"Desconocido\" }}</span>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"file-info-box file-empty\">\r\n <div class=\"file-info-header\">\uD83D\uDCED Sin archivos seleccionados</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: DecimalPipe, name: "number" }], encapsulation: i0.ViewEncapsulation.None });
5390
5758
  }
5391
5759
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonValuesDebujComponent, decorators: [{
5392
5760
  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 }] }] } });
5761
+ 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 <!-- \u2705 Informaci\u00F3n de archivos encontrados en toda la estructura -->\r\n @if (hasFileProperty()) {\r\n <div class=\"file-info-box\">\r\n <div class=\"file-info-header\">\r\n \uD83D\uDCC1 Archivos encontrados ({{ getFileInfo()?.count || 0 }})\r\n </div>\r\n <div class=\"file-info-grid\">\r\n @for (file of getFileInfo()?.files || []; track file.path) {\r\n <div class=\"file-info-item file-item\">\r\n <div class=\"file-path\">\uD83D\uDCC4 {{ file.path }}</div>\r\n <div class=\"file-details\">\r\n <span class=\"label\">Nombre:</span>\r\n <span class=\"value\">{{ file.name }}</span>\r\n </div>\r\n <div class=\"file-details\">\r\n <span class=\"label\">Tama\u00F1o:</span>\r\n <span class=\"value\"\r\n >{{ file.size / 1024 | number: \"1.1-1\" }} KB</span\r\n >\r\n </div>\r\n <div class=\"file-details\">\r\n <span class=\"label\">Tipo:</span>\r\n <span class=\"value\">{{ file.type || \"Desconocido\" }}</span>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"file-info-box file-empty\">\r\n <div class=\"file-info-header\">\uD83D\uDCED Sin archivos seleccionados</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"] }]
5762
+ }], 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
5763
 
5396
5764
  class IcoLabel {
5397
5765
  ico = input.required(...(ngDevMode ? [{ debugName: "ico" }] : /* istanbul ignore next */ []));
@@ -8280,6 +8648,117 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
8280
8648
  }]
8281
8649
  }] });
8282
8650
 
8651
+ class JsonHighlightPipe {
8652
+ /**
8653
+ * Stringifica el JSON manteniendo arrays en una línea para una vista más compacta
8654
+ */
8655
+ compactStringify(obj, indent = 0) {
8656
+ const spaces = ' '.repeat(indent);
8657
+ if (obj === null) {
8658
+ return 'null';
8659
+ }
8660
+ if (typeof obj !== 'object') {
8661
+ if (typeof obj === 'string') {
8662
+ return JSON.stringify(obj);
8663
+ }
8664
+ return String(obj);
8665
+ }
8666
+ if (obj instanceof Date) {
8667
+ return JSON.stringify(obj.toISOString());
8668
+ }
8669
+ if (Array.isArray(obj)) {
8670
+ // Mantener arrays en una línea
8671
+ const items = obj.map((item) => {
8672
+ if (item instanceof Date) {
8673
+ return JSON.stringify(item.toISOString());
8674
+ }
8675
+ if (typeof item === 'object' && item !== null) {
8676
+ return this.compactStringify(item, indent);
8677
+ }
8678
+ return typeof item === 'string'
8679
+ ? JSON.stringify(item)
8680
+ : item === null
8681
+ ? 'null'
8682
+ : String(item);
8683
+ });
8684
+ return `[${items.join(', ')}]`;
8685
+ }
8686
+ // Para objetos, mantener la indentación
8687
+ const keys = Object.keys(obj);
8688
+ if (keys.length === 0) {
8689
+ return '{}';
8690
+ }
8691
+ const innerIndent = indent + 2;
8692
+ const innerSpaces = ' '.repeat(innerIndent);
8693
+ const lines = keys.map((key) => {
8694
+ const value = obj[key];
8695
+ const stringified = value instanceof Date
8696
+ ? JSON.stringify(value.toISOString())
8697
+ : typeof value === 'object' && value !== null
8698
+ ? this.compactStringify(value, innerIndent)
8699
+ : typeof value === 'string'
8700
+ ? JSON.stringify(value)
8701
+ : value === null
8702
+ ? 'null'
8703
+ : String(value);
8704
+ return `${innerSpaces}"${key}": ${stringified}`;
8705
+ });
8706
+ return `{\n${lines.join(',\n')}\n${spaces}}`;
8707
+ }
8708
+ instanceId = Math.random().toString(36).slice(2, 8);
8709
+ executions = 0;
8710
+ transform(value) {
8711
+ if (isDevMode()) {
8712
+ this.executions++;
8713
+ if (this.executions % 10 === 0) {
8714
+ console.log(`[jsonHighlight:${this.instanceId}] ${this.executions}`);
8715
+ }
8716
+ }
8717
+ if (!value) {
8718
+ return '';
8719
+ }
8720
+ const json = this.compactStringify(value);
8721
+ // Detecta fechas ISO: "YYYY-MM-DDTHH:MM:SS.mmmZ"
8722
+ const ISO_DATE_RE = /^"(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}\.\d{3}Z)"$/;
8723
+ // Aplica estilo y colores para diferenciar claves, valores, booleanos, números, etc.
8724
+ return json
8725
+ .replace(/&/g, '&amp;')
8726
+ .replace(/</g, '&lt;')
8727
+ .replace(/>/g, '&gt;')
8728
+ .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?)/g, (match) => {
8729
+ // Si es una clave (tiene dos puntos al final)
8730
+ if (/:$/.test(match)) {
8731
+ return `<span class="json-key">${match}</span>`;
8732
+ }
8733
+ // Detectar fechas ISO (YYYY-MM-DDTHH:MM:SS.mmmZ)
8734
+ const isoMatch = match.match(ISO_DATE_RE);
8735
+ if (isoMatch) {
8736
+ 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>`;
8737
+ }
8738
+ // Si es un string (no tiene dos puntos)
8739
+ return `<span class="json-string">${match}</span>`;
8740
+ })
8741
+ .replace(/\b(true|false|null)\b/g, (match) => {
8742
+ if (match === 'null') {
8743
+ return '<span class="json-null">null</span>';
8744
+ }
8745
+ return `<span class="json-boolean">${match}</span>`;
8746
+ })
8747
+ .replace(/\b(\d+\.?\d*)\b/g, '<span class="json-number">$1</span>')
8748
+ .replace(/[{}\[\]]/g, '<span class="json-bracket">$&</span>')
8749
+ .replace(/,/g, '<span class="json-comma">$&</span>')
8750
+ .replace(/:/g, '<span class="json-colon">$&</span>');
8751
+ }
8752
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
8753
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, isStandalone: true, name: "jsonHighlight" });
8754
+ }
8755
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, decorators: [{
8756
+ type: Pipe,
8757
+ args: [{
8758
+ name: 'jsonHighlight',
8759
+ }]
8760
+ }] });
8761
+
8283
8762
  class TruncatePipe {
8284
8763
  /**
8285
8764
  * Transforma una cadena de texto truncándola según los parámetros proporcionados.
@@ -8513,53 +8992,43 @@ class DsxdateShort {
8513
8992
  useExisting: forwardRef(() => DsxdateShort),
8514
8993
  multi: true,
8515
8994
  },
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" }] });
8995
+ ], 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
8996
  }
8518
8997
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxdateShort, decorators: [{
8519
8998
  type: Component,
8520
8999
  args: [{ selector: 'dsxdate-short', imports: [
9000
+ AppMessageErrorComponent,
8521
9001
  CommonModule,
8522
- FloatLabel,
8523
9002
  DatePicker,
8524
- ReactiveFormsModule,
8525
- AppMessageErrorComponent,
9003
+ FloatLabel,
9004
+ InputMaskModule,
8526
9005
  JsonPipe,
9006
+ ReactiveFormsModule,
8527
9007
  ], providers: [
8528
9008
  {
8529
9009
  provide: NG_VALUE_ACCESSOR,
8530
9010
  useExisting: forwardRef(() => DsxdateShort),
8531
9011
  multi: true,
8532
9012
  },
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" }]
9013
+ ], 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
9014
  }], 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
9015
 
8536
- // dsx-input-currency.ts
8537
- class DsxInputCurrency {
8538
- inputNumber;
9016
+ class DsxdateTime {
8539
9017
  injector = inject(Injector);
9018
+ // ✅ Exponer isDevMode como propiedad pública
9019
+ isDevMode = isDevMode();
8540
9020
  // === INPUTS CONFIGURABLES ===
8541
- label = input('Saldo Actual', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
8542
- currency = input('GTQ', ...(ngDevMode ? [{ debugName: "currency" }] : /* istanbul ignore next */ []));
8543
- locale = input('es-GT', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
8544
- minFractionDigits = input(2, ...(ngDevMode ? [{ debugName: "minFractionDigits" }] : /* istanbul ignore next */ []));
8545
- maxFractionDigits = input(2, ...(ngDevMode ? [{ debugName: "maxFractionDigits" }] : /* istanbul ignore next */ []));
8546
- placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
8547
- readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
8548
- selectAllOnFocus = input(true, ...(ngDevMode ? [{ debugName: "selectAllOnFocus" }] : /* istanbul ignore next */ []));
9021
+ label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
9022
+ placeholder = input('dd/mm/yyyy 00:00', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
9023
+ dateFormat = input('dd/mm/yy', ...(ngDevMode ? [{ debugName: "dateFormat" }] : /* istanbul ignore next */ []));
9024
+ showIcon = input(true, ...(ngDevMode ? [{ debugName: "showIcon" }] : /* istanbul ignore next */ []));
9025
+ showOnFocus = input(false, ...(ngDevMode ? [{ debugName: "showOnFocus" }] : /* istanbul ignore next */ []));
9026
+ showButtonBar = input(true, ...(ngDevMode ? [{ debugName: "showButtonBar" }] : /* istanbul ignore next */ []));
9027
+ showTime = input(true, ...(ngDevMode ? [{ debugName: "showTime" }] : /* istanbul ignore next */ []));
9028
+ hourFormat = input('24', ...(ngDevMode ? [{ debugName: "hourFormat" }] : /* istanbul ignore next */ []));
8549
9029
  debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
8550
- showError = input(true, ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
8551
- // === OUTPUTS ===
8552
- valueChange = output();
8553
- focus = output();
8554
- blur = output();
8555
9030
  // === STATE ===
8556
- valueSignal = signal(null, ...(ngDevMode ? [{ debugName: "valueSignal" }] : /* istanbul ignore next */ []));
8557
- touched = signal(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
8558
9031
  debugEnabled = signal(false, ...(ngDevMode ? [{ debugName: "debugEnabled" }] : /* istanbul ignore next */ []));
8559
- // === COMPUTED ===
8560
- resolvedLabel = computed(() => this.label(), ...(ngDevMode ? [{ debugName: "resolvedLabel" }] : /* istanbul ignore next */ []));
8561
- resolvedCurrency = computed(() => this.currency(), ...(ngDevMode ? [{ debugName: "resolvedCurrency" }] : /* istanbul ignore next */ []));
8562
- resolvedLocale = computed(() => this.locale(), ...(ngDevMode ? [{ debugName: "resolvedLocale" }] : /* istanbul ignore next */ []));
8563
9032
  // === OBTENER EL CONTROL PADRE ===
8564
9033
  ngControl = computed(() => {
8565
9034
  try {
@@ -8575,44 +9044,274 @@ class DsxInputCurrency {
8575
9044
  return control?.control || null;
8576
9045
  }, ...(ngDevMode ? [{ debugName: "parentControl" }] : /* istanbul ignore next */ []));
8577
9046
  // === CONTROL INTERNO (para el template) ===
8578
- control = new FormControl();
9047
+ fechaControl = new FormControl(null);
8579
9048
  // === CONTROL VALUE ACCESSOR ===
8580
9049
  onChange = () => { };
8581
9050
  onTouched = () => { };
8582
9051
  constructor() {
8583
9052
  // Debug effect
8584
9053
  effect(() => {
8585
- const isDebug = this.debug() && isDevMode();
9054
+ const isDebug = this.debug() && this.isDevMode;
8586
9055
  this.debugEnabled.set(isDebug);
8587
9056
  if (isDebug) {
8588
- console.log('[dsx-input-currency] === DEBUG MODE ACTIVADO ===');
8589
- console.log('[dsx-input-currency] Configuración:', {
9057
+ console.log('[dsxdate-datetime] === DEBUG MODE ACTIVADO ===');
9058
+ console.log('[dsxdate-datetime] Configuración:', {
8590
9059
  label: this.label(),
8591
- currency: this.currency(),
8592
- locale: this.locale(),
8593
- minFractionDigits: this.minFractionDigits(),
8594
- maxFractionDigits: this.maxFractionDigits(),
8595
9060
  placeholder: this.placeholder(),
8596
- readonly: this.readonly(),
8597
- selectAllOnFocus: this.selectAllOnFocus(),
9061
+ dateFormat: this.dateFormat(),
9062
+ showIcon: this.showIcon(),
9063
+ showOnFocus: this.showOnFocus(),
9064
+ showButtonBar: this.showButtonBar(),
9065
+ showTime: this.showTime(),
9066
+ hourFormat: this.hourFormat(),
8598
9067
  });
8599
- console.log('[dsx-input-currency] Control padre:', this.parentControl());
9068
+ console.log('[dsxdate-datetime] Control padre:', this.parentControl());
8600
9069
  }
8601
9070
  });
8602
- // Monitorear cambios del control padre
9071
+ // SINCRONIZAR VALIDADORES DEL PADRE AL CONTROL INTERNO
8603
9072
  effect(() => {
8604
- if (this.debugEnabled()) {
8605
- const parentCtrl = this.parentControl();
8606
- console.log('[dsx-input-currency] 📊 Parent Control state:', {
8607
- value: parentCtrl?.value,
8608
- status: parentCtrl?.status,
8609
- errors: parentCtrl?.errors,
8610
- valid: parentCtrl?.valid,
8611
- invalid: parentCtrl?.invalid,
8612
- touched: parentCtrl?.touched,
8613
- dirty: parentCtrl?.dirty,
8614
- });
8615
- }
9073
+ const parentCtrl = this.parentControl();
9074
+ if (parentCtrl) {
9075
+ // Copiar validadores del padre al control interno
9076
+ const validators = parentCtrl.validator;
9077
+ const asyncValidators = parentCtrl.asyncValidator;
9078
+ if (this.debugEnabled()) {
9079
+ console.log('[dsxdate-datetime] 🔄 Sincronizando validadores del padre');
9080
+ console.log('[dsxdate-datetime] Validadores del padre:', validators);
9081
+ }
9082
+ this.fechaControl.setValidators(validators ? [validators] : []);
9083
+ this.fechaControl.setAsyncValidators(asyncValidators ? [asyncValidators] : []);
9084
+ this.fechaControl.updateValueAndValidity({ emitEvent: false });
9085
+ if (this.debugEnabled()) {
9086
+ console.log('[dsxdate-datetime] ✅ Validadores sincronizados');
9087
+ console.log('[dsxdate-datetime] Estado del control interno:', {
9088
+ valid: this.fechaControl.valid,
9089
+ invalid: this.fechaControl.invalid,
9090
+ errors: this.fechaControl.errors,
9091
+ });
9092
+ }
9093
+ }
9094
+ });
9095
+ // Monitorear cambios del control padre
9096
+ effect(() => {
9097
+ if (this.debugEnabled()) {
9098
+ const parentCtrl = this.parentControl();
9099
+ console.log('[dsxdate-datetime] 📊 Parent Control state:', {
9100
+ value: parentCtrl?.value,
9101
+ status: parentCtrl?.status,
9102
+ errors: parentCtrl?.errors,
9103
+ valid: parentCtrl?.valid,
9104
+ invalid: parentCtrl?.invalid,
9105
+ touched: parentCtrl?.touched,
9106
+ dirty: parentCtrl?.dirty,
9107
+ });
9108
+ }
9109
+ });
9110
+ // Sincronizar cambios del control interno al padre
9111
+ this.fechaControl.valueChanges.subscribe((value) => {
9112
+ if (this.debugEnabled()) {
9113
+ console.log('[dsxdate-datetime] 🔄 valueChanges - Nuevo valor:', value);
9114
+ }
9115
+ this.onChange(value);
9116
+ });
9117
+ // Sincronizar touched
9118
+ this.fechaControl.statusChanges.subscribe(() => {
9119
+ if (this.debugEnabled()) {
9120
+ console.log('[dsxdate-datetime] 📊 statusChanges - Estado:', this.fechaControl.status);
9121
+ }
9122
+ this.onTouched();
9123
+ });
9124
+ }
9125
+ // === MÉTODOS DE DEBUG ===
9126
+ log(message, data) {
9127
+ if (this.debugEnabled() && this.isDevMode) {
9128
+ if (data !== undefined) {
9129
+ console.log(`[dsxdate-datetime] ${message}`, data);
9130
+ }
9131
+ else {
9132
+ console.log(`[dsxdate-datetime] ${message}`);
9133
+ }
9134
+ }
9135
+ }
9136
+ /**
9137
+ * Convierte cualquier fecha (string ISO, Date, moment) a Date válido
9138
+ */
9139
+ convertirFechaADate(fecha) {
9140
+ if (this.debugEnabled()) {
9141
+ this.log('🔄 convertirFechaADate - entrada:', fecha);
9142
+ }
9143
+ if (!fecha) {
9144
+ this.log('convertirFechaADate - fecha vacía, retornando null');
9145
+ return null;
9146
+ }
9147
+ if (fecha instanceof Date && !isNaN(fecha.getTime())) {
9148
+ this.log('✅ convertirFechaADate - es Date válido:', fecha);
9149
+ return fecha;
9150
+ }
9151
+ if (typeof fecha === 'string') {
9152
+ this.log('📝 convertirFechaADate - procesando string:', fecha);
9153
+ const momentDate = moment(fecha);
9154
+ if (momentDate.isValid()) {
9155
+ const result = momentDate.toDate();
9156
+ this.log('✅ convertirFechaADate - string convertido a Date:', result);
9157
+ return result;
9158
+ }
9159
+ this.log('❌ convertirFechaADate - string inválido para moment');
9160
+ }
9161
+ if (moment.isMoment(fecha)) {
9162
+ this.log('✅ convertirFechaADate - es objeto moment');
9163
+ return fecha.toDate();
9164
+ }
9165
+ const parsed = new Date(fecha);
9166
+ if (!isNaN(parsed.getTime())) {
9167
+ this.log('✅ convertirFechaADate - parseo directo exitoso:', parsed);
9168
+ return parsed;
9169
+ }
9170
+ this.log('❌ convertirFechaADate - no se pudo convertir, retornando null');
9171
+ return null;
9172
+ }
9173
+ // === CONTROL VALUE ACCESSOR IMPLEMENTATION ===
9174
+ writeValue(value) {
9175
+ if (this.debugEnabled()) {
9176
+ this.log('📥 writeValue - Valor recibido:', value);
9177
+ }
9178
+ const fechaConvertida = this.convertirFechaADate(value);
9179
+ if (this.debugEnabled()) {
9180
+ this.log('📥 writeValue - Valor convertido:', fechaConvertida);
9181
+ }
9182
+ this.fechaControl.setValue(fechaConvertida, { emitEvent: false });
9183
+ }
9184
+ registerOnChange(fn) {
9185
+ if (this.debugEnabled()) {
9186
+ this.log('📞 registerOnChange - Callback registrado');
9187
+ }
9188
+ this.onChange = fn;
9189
+ }
9190
+ registerOnTouched(fn) {
9191
+ if (this.debugEnabled()) {
9192
+ this.log('📞 registerOnTouched - Callback registrado');
9193
+ }
9194
+ this.onTouched = fn;
9195
+ }
9196
+ setDisabledState(isDisabled) {
9197
+ if (this.debugEnabled()) {
9198
+ this.log(`🚫 setDisabledState - Deshabilitado: ${isDisabled}`);
9199
+ }
9200
+ if (isDisabled) {
9201
+ this.fechaControl.disable({ emitEvent: false });
9202
+ }
9203
+ else {
9204
+ this.fechaControl.enable({ emitEvent: false });
9205
+ }
9206
+ }
9207
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxdateTime, deps: [], target: i0.ɵɵFactoryTarget.Component });
9208
+ 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: [
9209
+ {
9210
+ provide: NG_VALUE_ACCESSOR,
9211
+ useExisting: forwardRef(() => DsxdateTime),
9212
+ multi: true,
9213
+ },
9214
+ ], 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" }] });
9215
+ }
9216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxdateTime, decorators: [{
9217
+ type: Component,
9218
+ args: [{ selector: 'dsxdate-time', imports: [
9219
+ AppMessageErrorComponent,
9220
+ CommonModule,
9221
+ DatePicker,
9222
+ FloatLabel,
9223
+ InputMaskModule,
9224
+ JsonPipe,
9225
+ ReactiveFormsModule,
9226
+ ], providers: [
9227
+ {
9228
+ provide: NG_VALUE_ACCESSOR,
9229
+ useExisting: forwardRef(() => DsxdateTime),
9230
+ multi: true,
9231
+ },
9232
+ ], 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" }]
9233
+ }], 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 }] }] } });
9234
+
9235
+ // dsx-input-currency.ts
9236
+ class DsxInputCurrency {
9237
+ inputNumber;
9238
+ injector = inject(Injector);
9239
+ // === INPUTS CONFIGURABLES ===
9240
+ label = input('Saldo Actual', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
9241
+ currency = input('GTQ', ...(ngDevMode ? [{ debugName: "currency" }] : /* istanbul ignore next */ []));
9242
+ locale = input('es-GT', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
9243
+ minFractionDigits = input(2, ...(ngDevMode ? [{ debugName: "minFractionDigits" }] : /* istanbul ignore next */ []));
9244
+ maxFractionDigits = input(2, ...(ngDevMode ? [{ debugName: "maxFractionDigits" }] : /* istanbul ignore next */ []));
9245
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
9246
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
9247
+ selectAllOnFocus = input(true, ...(ngDevMode ? [{ debugName: "selectAllOnFocus" }] : /* istanbul ignore next */ []));
9248
+ debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
9249
+ showError = input(true, ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
9250
+ // === OUTPUTS ===
9251
+ valueChange = output();
9252
+ focus = output();
9253
+ blur = output();
9254
+ // === STATE ===
9255
+ valueSignal = signal(null, ...(ngDevMode ? [{ debugName: "valueSignal" }] : /* istanbul ignore next */ []));
9256
+ touched = signal(false, ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
9257
+ debugEnabled = signal(false, ...(ngDevMode ? [{ debugName: "debugEnabled" }] : /* istanbul ignore next */ []));
9258
+ // === COMPUTED ===
9259
+ resolvedLabel = computed(() => this.label(), ...(ngDevMode ? [{ debugName: "resolvedLabel" }] : /* istanbul ignore next */ []));
9260
+ resolvedCurrency = computed(() => this.currency(), ...(ngDevMode ? [{ debugName: "resolvedCurrency" }] : /* istanbul ignore next */ []));
9261
+ resolvedLocale = computed(() => this.locale(), ...(ngDevMode ? [{ debugName: "resolvedLocale" }] : /* istanbul ignore next */ []));
9262
+ // === OBTENER EL CONTROL PADRE ===
9263
+ ngControl = computed(() => {
9264
+ try {
9265
+ return this.injector.get(NgControl);
9266
+ }
9267
+ catch {
9268
+ return null;
9269
+ }
9270
+ }, ...(ngDevMode ? [{ debugName: "ngControl" }] : /* istanbul ignore next */ []));
9271
+ // === CONTROL PADRE PARA ERRORES ===
9272
+ parentControl = computed(() => {
9273
+ const control = this.ngControl();
9274
+ return control?.control || null;
9275
+ }, ...(ngDevMode ? [{ debugName: "parentControl" }] : /* istanbul ignore next */ []));
9276
+ // === CONTROL INTERNO (para el template) ===
9277
+ control = new FormControl();
9278
+ // === CONTROL VALUE ACCESSOR ===
9279
+ onChange = () => { };
9280
+ onTouched = () => { };
9281
+ constructor() {
9282
+ // Debug effect
9283
+ effect(() => {
9284
+ const isDebug = this.debug() && isDevMode();
9285
+ this.debugEnabled.set(isDebug);
9286
+ if (isDebug) {
9287
+ console.log('[dsx-input-currency] === DEBUG MODE ACTIVADO ===');
9288
+ console.log('[dsx-input-currency] Configuración:', {
9289
+ label: this.label(),
9290
+ currency: this.currency(),
9291
+ locale: this.locale(),
9292
+ minFractionDigits: this.minFractionDigits(),
9293
+ maxFractionDigits: this.maxFractionDigits(),
9294
+ placeholder: this.placeholder(),
9295
+ readonly: this.readonly(),
9296
+ selectAllOnFocus: this.selectAllOnFocus(),
9297
+ });
9298
+ console.log('[dsx-input-currency] Control padre:', this.parentControl());
9299
+ }
9300
+ });
9301
+ // Monitorear cambios del control padre
9302
+ effect(() => {
9303
+ if (this.debugEnabled()) {
9304
+ const parentCtrl = this.parentControl();
9305
+ console.log('[dsx-input-currency] 📊 Parent Control state:', {
9306
+ value: parentCtrl?.value,
9307
+ status: parentCtrl?.status,
9308
+ errors: parentCtrl?.errors,
9309
+ valid: parentCtrl?.valid,
9310
+ invalid: parentCtrl?.invalid,
9311
+ touched: parentCtrl?.touched,
9312
+ dirty: parentCtrl?.dirty,
9313
+ });
9314
+ }
8616
9315
  });
8617
9316
  }
8618
9317
  ngAfterViewInit() {
@@ -10041,12 +10740,187 @@ class ResultFileService {
10041
10740
  this.descargarBlob(blob, fileName);
10042
10741
  }
10043
10742
  /**
10044
- * Realiza un GET para descarga y normaliza la respuesta a ServiceResult<FileData>.
10743
+ * Realiza una petición GET para descargar un archivo y normaliza la respuesta a un ServiceResult<FileData>.
10744
+ *
10745
+ * 📋 **Descripción General:**
10746
+ * Este método unifica la forma en que se manejan las descargas de archivos, soportando múltiples
10747
+ * formatos de respuesta del backend y normalizándolos a una estructura consistente.
10748
+ *
10749
+ * 🔄 **Formatos de Respuesta Soportados:**
10750
+ *
10751
+ * 1. **Binario Directo** (Recomendado):
10752
+ * - El backend retorna directamente el archivo binario.
10753
+ * - El nombre del archivo se obtiene del header `Content-Disposition`.
10754
+ * - Mensajes opcionales via headers: `x-title`, `x-message`, `title`, `message`.
10755
+ * - Ejemplo:
10756
+ * ```
10757
+ * Content-Type: application/pdf
10758
+ * Content-Disposition: attachment; filename="documento.pdf"
10759
+ * x-title: Éxito
10760
+ * x-message: Descarga completada exitosamente
10761
+ *
10762
+ * [bytes del archivo]
10763
+ * ```
10764
+ *
10765
+ * 2. **JSON Serializado con ServiceResult**:
10766
+ * - El backend retorna un JSON con estructura ServiceResult<FileContentResult>.
10767
+ * - El archivo viene codificado en Base64 o como array de bytes.
10768
+ * - Soporta campos en PascalCase o camelCase.
10769
+ * - Ejemplo:
10770
+ * ```json
10771
+ * {
10772
+ * "isSuccess": true,
10773
+ * "title": "Éxito",
10774
+ * "message": "Archivo generado correctamente",
10775
+ * "data": {
10776
+ * "fileContents": "base64EncodedString...",
10777
+ * "fileDownloadName": "reporte.xlsx",
10778
+ * "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
10779
+ * }
10780
+ * }
10781
+ * ```
10782
+ *
10783
+ * 3. **Error del Backend**:
10784
+ * - Retorna un ServiceResult con isSuccess: false.
10785
+ * - Se extrae el título y mensaje de error.
10786
+ * - Ejemplo:
10787
+ * ```json
10788
+ * {
10789
+ * "isSuccess": false,
10790
+ * "title": "Error de Validación",
10791
+ * "message": "El ID del documento no existe"
10792
+ * }
10793
+ * ```
10794
+ *
10795
+ * 4. **Error de Red/HTTP**:
10796
+ * - Captura errores de conexión, timeout, etc.
10797
+ * - Retorna un ServiceResult con isSuccess: false y mensaje genérico.
10798
+ *
10799
+ * 🎯 **Método de Uso Recomendado:**
10800
+ * ```typescript
10801
+ * // Opción 1: Descarga automática
10802
+ * this.resultFileService.getDownloadableFile('/api/documentos/123')
10803
+ * .subscribe(result => {
10804
+ * this.resultFileService.processFileResult(result, false);
10805
+ * });
10806
+ *
10807
+ * // Opción 2: Visualización de PDF
10808
+ * this.resultFileService.getDownloadableFile('/api/documentos/123')
10809
+ * .subscribe(result => {
10810
+ * this.resultFileService.processFileResult(result, true);
10811
+ * });
10812
+ *
10813
+ * // Opción 3: Procesamiento personalizado
10814
+ * this.resultFileService.getDownloadableFile('/api/documentos/123')
10815
+ * .subscribe({
10816
+ * next: (result) => {
10817
+ * if (result.isSuccess && result.data?.blob) {
10818
+ * // Procesar el blob manualmente
10819
+ * const { blob, fileName } = result.data;
10820
+ * // Hacer algo con el archivo
10821
+ * } else {
10822
+ * // Manejar error
10823
+ * console.error(result.title, result.message);
10824
+ * }
10825
+ * },
10826
+ * error: (error) => {
10827
+ * console.error('Error de red:', error);
10828
+ * }
10829
+ * });
10830
+ * ```
10831
+ *
10832
+ * 📝 **Parámetros:**
10833
+ * @param url - URL del endpoint de descarga.
10834
+ * @param options - Opciones adicionales para la solicitud.
10835
+ * @param options.params - Parámetros de consulta (Query params). Soporta:
10836
+ * - `string`: valor simple
10837
+ * - `string[]`: múltiples valores para el mismo parámetro
10838
+ * - `Date`: se convierte automáticamente a ISO string
10839
+ * - `number`: se convierte a string
10840
+ * - `HttpParams`: instancia directamente
10841
+ * @param options.headers - Headers HTTP personalizados (HttpHeaders).
10842
+ * @param options.withCredentials - Enviar cookies/credenciales en la solicitud.
10843
+ *
10844
+ * 🔍 **Estructura de Retorno:**
10845
+ * ```typescript
10846
+ * Observable<ServiceResult<FileData>>
10847
+ * ```
10848
+ *
10849
+ * Donde `ServiceResult<FileData>` tiene:
10850
+ * ```typescript
10851
+ * {
10852
+ * data: {
10853
+ * blob: Blob, // El archivo como Blob
10854
+ * fileName: string // Nombre del archivo (con extensión)
10855
+ * } | null,
10856
+ * isSuccess: boolean, // Indica si la operación fue exitosa
10857
+ * title: string, // Título del resultado (éxito o error)
10858
+ * message: string // Mensaje descriptivo
10859
+ * }
10860
+ * ```
10861
+ *
10862
+ * ⚠️ **Notas Importantes:**
10863
+ * - El método siempre retorna un Observable que emite un ServiceResult.
10864
+ * - Nunca emite error en el Observable (los errores se convierten a ServiceResult con isSuccess: false).
10865
+ * - Para descarga automática usar `processFileResult(result, false)`.
10866
+ * - Para visualización de PDF usar `processFileResult(result, true)`.
10867
+ * - El nombre del archivo se determina en este orden de prioridad:
10868
+ * 1. `Content-Disposition` header (si existe)
10869
+ * 2. `fileDownloadName` del JSON (si es respuesta JSON)
10870
+ * 3. `fileName` del JSON (si es respuesta JSON)
10871
+ * 4. Valor por defecto: 'document'
10872
+ *
10873
+ * 💡 **Ejemplos de Uso Adicionales:**
10874
+ *
10875
+ * ```typescript
10876
+ * // Con parámetros de consulta
10877
+ * const options = {
10878
+ * params: {
10879
+ * id: 123,
10880
+ * formato: 'pdf',
10881
+ * fechas: ['2024-01-01', '2024-12-31']
10882
+ * }
10883
+ * };
10884
+ * this.resultFileService.getDownloadableFile('/api/reportes', options)
10885
+ * .subscribe(result => {
10886
+ * this.resultFileService.processFileResult(result);
10887
+ * });
10888
+ *
10889
+ * // Con headers personalizados (ej: autenticación)
10890
+ * const options = {
10891
+ * headers: new HttpHeaders({
10892
+ * 'Authorization': 'Bearer ' + token,
10893
+ * 'X-Custom-Header': 'valor'
10894
+ * })
10895
+ * };
10896
+ * this.resultFileService.getDownloadableFile('/api/documentos', options)
10897
+ * .subscribe(result => {
10898
+ * this.resultFileService.processFileResult(result);
10899
+ * });
10900
+ *
10901
+ * // Con credenciales
10902
+ * const options = {
10903
+ * withCredentials: true
10904
+ * };
10905
+ * this.resultFileService.getDownloadableFile('/api/documentos', options)
10906
+ * .subscribe(result => {
10907
+ * this.resultFileService.processFileResult(result);
10908
+ * });
10909
+ * ```
10910
+ *
10911
+ * 🚀 **Flujo de Trabajo Típico:**
10912
+ * 1. Llamar a `getDownloadableFile(url, options)` con la URL del endpoint.
10913
+ * 2. Suscribirse al Observable.
10914
+ * 3. En el `next`, recibir el `ServiceResult<FileData>`.
10915
+ * 4. Usar `processFileResult(result, visualizar)` para:
10916
+ * - Descargar automáticamente (visualizar: false)
10917
+ * - Visualizar PDF en nueva pestaña (visualizar: true)
10918
+ * 5. Alternativamente, procesar el blob manualmente si se necesita.
10045
10919
  *
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.
10920
+ * @see processFileResult - Para descargar o visualizar el archivo obtenido.
10921
+ * @see FileData - Estructura de datos del archivo.
10922
+ * @see BlobRequestOptions - Opciones de configuración para la solicitud.
10923
+ * @see ServiceResult - Estructura de resultado estandarizada.
10050
10924
  */
10051
10925
  getDownloadableFile(url, options = {}) {
10052
10926
  return this.http
@@ -10251,25 +11125,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
10251
11125
  }] });
10252
11126
 
10253
11127
  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
11128
  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
11129
  defaultTheme = undefined;
11130
+ NATIVE_ICONS = [
11131
+ 'success',
11132
+ 'error',
11133
+ 'warning',
11134
+ 'info',
11135
+ 'question',
11136
+ ];
10271
11137
  constructor() {
10272
- // Inicializar el tema desde el environment si está disponible
10273
11138
  if (this.environment?.sweetAlertTheme) {
10274
11139
  const allowed = SWEET_ALERT_THEMES;
10275
11140
  const themeValue = this.environment.sweetAlertTheme;
@@ -10289,59 +11154,102 @@ class SweetAlert2DialogService {
10289
11154
  img.onerror = (err) => reject(err);
10290
11155
  });
10291
11156
  }
10292
- // 4. Implementación real del método
10293
- // 5. Implementación unificada final
10294
- Dialog(firstParam, text, icoOrIcono, options = {}) {
11157
+ // Implementación unificada
11158
+ Dialog(firstParam, text, secondOrThird, thirdOrOptions) {
10295
11159
  let title = '';
10296
- let finalOptions = { ...options };
10297
- let finalIcoOrIcono = icoOrIcono;
10298
- // DETECTAR SI EL PRIMER PARÁMETRO ES EL OBJETO COMPLETO
11160
+ let messageContent = '';
11161
+ let options = {};
11162
+ let iconFromParam = null;
11163
+ let imageFromOptions = null;
11164
+ // === PROCESAR PARÁMETROS ===
10299
11165
  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 };
11166
+ // Caso 1: ActionMessageConfig
11167
+ const config = firstParam;
11168
+ title = config.title;
11169
+ messageContent = config.message;
11170
+ // Extraer opciones
11171
+ const { title: _title, message: _message, icon: _icon, icono: _icono, html: _html, footer: _footer, ...restOptions } = config;
11172
+ options = { ...restOptions };
11173
+ if (_html)
11174
+ options.html = _html;
11175
+ if (_footer)
11176
+ options.footer = _footer;
11177
+ // ✅ Si hay icono en el config, es el parámetro
11178
+ if (config.icon) {
11179
+ iconFromParam = config.icon;
11180
+ }
11181
+ // ✅ Si hay icono personalizado en el config
11182
+ if (config.icono) {
11183
+ imageFromOptions = config.icono;
11184
+ }
10305
11185
  }
10306
11186
  else {
11187
+ // Caso 2, 3, 4: Parámetros tradicionales
10307
11188
  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;
11189
+ messageContent = text || '';
11190
+ // Determinar qué tipo de parámetros tenemos
11191
+ if (secondOrThird !== undefined && secondOrThird !== null) {
11192
+ if (typeof secondOrThird === 'string') {
11193
+ // ✅ Es un icono nativo (Caso 4)
11194
+ if (this.NATIVE_ICONS.includes(secondOrThird)) {
11195
+ iconFromParam = secondOrThird;
11196
+ }
11197
+ options = thirdOrOptions || {};
11198
+ }
11199
+ else if (typeof secondOrThird === 'object') {
11200
+ // ✅ Son opciones (Caso 3)
11201
+ options = secondOrThird;
11202
+ // Si hay icono personalizado en options
11203
+ if (options.icono) {
11204
+ imageFromOptions = options.icono;
11205
+ }
11206
+ }
10322
11207
  }
10323
11208
  else {
10324
- iconoImg = finalIcoOrIcono;
11209
+ // Solo título y texto (Caso 2)
11210
+ options = thirdOrOptions || {};
11211
+ if (options.icono) {
11212
+ imageFromOptions = options.icono;
11213
+ }
10325
11214
  }
10326
11215
  }
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;
11216
+ // PRIORIDAD: imageFromOptions (imagen) > iconFromParam (icono nativo)
11217
+ let ico = null;
11218
+ let iconoImg = null;
11219
+ // 1. Si hay imagen en options, usarla (máxima prioridad)
11220
+ if (imageFromOptions) {
11221
+ iconoImg = imageFromOptions;
11222
+ }
11223
+ // 2. Si hay icono en el parámetro, usarlo
11224
+ else if (iconFromParam) {
11225
+ ico = iconFromParam;
11226
+ }
11227
+ // === CONFIGURACIÓN DE LA ALERTA ===
11228
+ const { confirmButtonText = 'Aceptar', cancelButtonText = 'Cancelar', showCloseButton = true, showCancelButton = true, imageWidth = 150, imageHeight = 150, theme, ...rest } = options;
10329
11229
  const alertTheme = theme !== undefined ? theme : this.defaultTheme;
11230
+ // === CONSTRUIR FOOTER ===
10330
11231
  const year = new Date().getFullYear();
10331
- const baseFooter = this.environment?.sweetAlertFooter ?? 'DEVSOFTXela';
10332
- const footer = `${baseFooter} <strong>${year}</strong>`;
11232
+ const footerText = options.footer ?? this.environment?.sweetAlertFooter ?? 'DEVSOFTXela';
11233
+ const footer = `${footerText} <strong>${year}</strong>`;
11234
+ // === CONSTRUIR CONFIGURACIÓN DE SWEETALERT ===
10333
11235
  const alertConfig = {
10334
11236
  title,
10335
- html: text,
10336
11237
  footer,
10337
11238
  showCloseButton,
10338
11239
  showCancelButton,
10339
11240
  confirmButtonText,
10340
11241
  cancelButtonText,
10341
- //reverseButtons: true,
10342
11242
  ...(alertTheme !== undefined && { theme: alertTheme }),
10343
11243
  ...rest,
10344
11244
  };
11245
+ // === PRIORIDAD DEL CONTENIDO HTML ===
11246
+ if (options.html) {
11247
+ alertConfig.html = options.html;
11248
+ }
11249
+ else if (messageContent) {
11250
+ alertConfig.html = messageContent;
11251
+ }
11252
+ // === IMAGEN O ICONO ===
10345
11253
  if (iconoImg) {
10346
11254
  alertConfig.imageUrl = `/${iconoImg}`;
10347
11255
  alertConfig.imageWidth = imageWidth;
@@ -10365,6 +11273,320 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
10365
11273
  }]
10366
11274
  }], ctorParameters: () => [] });
10367
11275
 
11276
+ class DsxMessagesService {
11277
+ AlertaService = inject(AlertaService);
11278
+ SweetDialog = inject(SweetAlert2DialogService);
11279
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
11280
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, providedIn: 'root' });
11281
+ }
11282
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, decorators: [{
11283
+ type: Injectable,
11284
+ args: [{
11285
+ providedIn: 'root',
11286
+ }]
11287
+ }] });
11288
+
11289
+ /**
11290
+ * Obtiene los datos de un FormGroup excluyendo TODAS las propiedades de tipo File
11291
+ *
11292
+ * @param form - FormGroup del cual extraer los datos
11293
+ * @returns Objeto con los datos (sin archivos) y la lista de archivos encontrados
11294
+ *
11295
+ * @internal
11296
+ */
11297
+ function getFormDataExcludingFiles(form) {
11298
+ const rawValue = form.getRawValue();
11299
+ const entries = Object.entries(rawValue);
11300
+ const files = [];
11301
+ const data = {};
11302
+ for (const [key, value] of entries) {
11303
+ if (value instanceof File) {
11304
+ files.push(value);
11305
+ }
11306
+ else {
11307
+ data[key] = value;
11308
+ }
11309
+ }
11310
+ return { data: data, files };
11311
+ }
11312
+ /**
11313
+ * Servicio para guardar entidades y subir archivos automáticamente.
11314
+ *
11315
+ * Este servicio centraliza el flujo común de:
11316
+ * 1. Extraer automáticamente el archivo del FormGroup (por tipo `File`)
11317
+ * 2. Guardar la entidad (sin el archivo) en la base de datos
11318
+ * 3. Si el guardado es exitoso y hay archivo, subirlo
11319
+ * 4. Manejar errores de forma consistente
11320
+ * 5. Resetear el formulario automáticamente en caso de éxito
11321
+ *
11322
+ * @example
11323
+ * // En tu componente:
11324
+ *
11325
+ * // Guardar sin archivo
11326
+ * this._saveEntityPostFile.saveOnly(
11327
+ * this.form,
11328
+ * this._baseService.save.bind(this._baseService),
11329
+ * (response) => {
11330
+ * this.formState.emit(response);
11331
+ * }
11332
+ * );
11333
+ *
11334
+ * // Guardar con archivo (se extrae automáticamente)
11335
+ * this._saveEntityPostFile.saveWithFile(
11336
+ * this.form,
11337
+ * this._baseService.save.bind(this._baseService),
11338
+ * this._baseService.AdjuntarSoporte.bind(this._baseService),
11339
+ * (response) => {
11340
+ * this.formState.emit(response);
11341
+ * }
11342
+ * );
11343
+ */
11344
+ class SaveEntityPostFileService {
11345
+ /** Servicio para mostrar mensajes al usuario */
11346
+ _dsxMessageService = inject(DsxMessagesService);
11347
+ /** Configuración del entorno (para controlar debug) */
11348
+ environment = inject(ENVIRONMENT);
11349
+ /**
11350
+ * Determina si el debug está activo
11351
+ *
11352
+ * @param debug - Solicitud explícita de debug
11353
+ * @returns `true` solo si `debug=true` Y no estamos en producción
11354
+ *
11355
+ * @internal
11356
+ */
11357
+ shouldLog(debug) {
11358
+ return debug && !this.environment.production;
11359
+ }
11360
+ /**
11361
+ * Guarda una entidad Y sube automáticamente el archivo extraído del FormGroup.
11362
+ *
11363
+ * El servicio extrae automáticamente el archivo del FormGroup (por tipo `File`),
11364
+ * lo excluye del objeto que se envía al backend, y después de guardar
11365
+ * exitosamente, procede a subir el archivo usando el ID generado.
11366
+ *
11367
+ * @param form - FormGroup que contiene los datos del formulario
11368
+ * @param saveMethod - Método de save que recibe los datos (sin archivo)
11369
+ * @param uploadMethod - Método de upload que recibe (id, file) y retorna Observable
11370
+ * @param onSuccess - Callback de éxito (opcional)
11371
+ * @param onError - Callback de error (opcional)
11372
+ * @param resetForm - Función para resetear el formulario (opcional)
11373
+ * @param debug - Activa logs en consola (solo funciona en desarrollo). Por defecto `false`.
11374
+ *
11375
+ * @example
11376
+ * // Uso básico
11377
+ * this._saveEntityPostFile.saveWithFile(
11378
+ * this.form,
11379
+ * this._baseService.save.bind(this._baseService),
11380
+ * this._baseService.AdjuntarSoporte.bind(this._baseService),
11381
+ * (response) => {
11382
+ * this.formState.emit(response);
11383
+ * }
11384
+ * );
11385
+ *
11386
+ * @example
11387
+ * // Con reset personalizado
11388
+ * this._saveEntityPostFile.saveWithFile(
11389
+ * this.form,
11390
+ * this._baseService.save.bind(this._baseService),
11391
+ * this._baseService.AdjuntarSoporte.bind(this._baseService),
11392
+ * (response) => {
11393
+ * this.formState.emit(response);
11394
+ * },
11395
+ * (error) => {
11396
+ * console.error('Error:', error);
11397
+ * },
11398
+ * () => {
11399
+ * this.form.reset();
11400
+ * this.getForm();
11401
+ * this.cleanFileInput();
11402
+ * }
11403
+ * );
11404
+ */
11405
+ saveWithFile(form, saveMethod, uploadMethod, deleteMethod, onSuccess, onError, resetForm, debug = false) {
11406
+ this.execute(form, saveMethod, uploadMethod, deleteMethod, onSuccess, onError, resetForm, debug);
11407
+ }
11408
+ /**
11409
+ * Ejecuta el flujo completo de guardado y subida de archivo.
11410
+ *
11411
+ * Este método es privado y contiene la lógica central del servicio.
11412
+ *
11413
+ * ## Flujo de ejecución:
11414
+ * 1. Extrae el archivo del FormGroup (por tipo `File`)
11415
+ * 2. Construye el objeto de datos sin el archivo
11416
+ * 3. Ejecuta el `saveMethod` con los datos limpios
11417
+ * 4. Si el save es exitoso y hay archivo, ejecuta el `uploadMethod`
11418
+ * 5. Si todo es exitoso, ejecuta `resetForm` y `onSuccess`
11419
+ * 6. Si hay error en cualquier paso, ejecuta `onError`
11420
+ *
11421
+ * @param form - FormGroup que contiene los datos del formulario
11422
+ * @param saveMethod - Método de save que recibe los datos (sin archivo)
11423
+ * @param uploadMethod - Método de upload que recibe (id, file) (opcional)
11424
+ * @param onSuccess - Callback de éxito (opcional)
11425
+ * @param onError - Callback de error (opcional)
11426
+ * @param resetForm - Función para resetear el formulario (opcional)
11427
+ * @param debug - Activa logs en consola (solo funciona en desarrollo)
11428
+ *
11429
+ * @internal
11430
+ */
11431
+ execute(form, saveMethod, uploadMethod, deleteMethod, onSuccess, onError, resetForm, debug = false) {
11432
+ const log = this.shouldLog(debug);
11433
+ // 1️⃣ Extraer archivo y datos del FormGroup
11434
+ const { data: saveData, files } = getFormDataExcludingFiles(form);
11435
+ const fileUpload = files.length > 0 ? files[0] : null;
11436
+ if (log) {
11437
+ console.group('📤 [SaveEntityPostFile] Preparando datos');
11438
+ console.log('📄 Datos a guardar (sin archivo):', saveData);
11439
+ console.log('📎 Archivo encontrado:', fileUpload?.name || 'Ninguno');
11440
+ console.groupEnd();
11441
+ }
11442
+ // 2️⃣ Crear el observable del save con los datos sin archivo
11443
+ const save$ = saveMethod(saveData);
11444
+ // 3️⃣ Ejecutar el flujo completo
11445
+ save$.subscribe({
11446
+ next: (saveResult) => {
11447
+ // 📥 Log del save
11448
+ if (log) {
11449
+ console.group('📥 [SaveEntityPostFile] Respuesta del save');
11450
+ console.log('Resultado:', saveResult);
11451
+ console.groupEnd();
11452
+ }
11453
+ // ❌ Si el guardado falla
11454
+ if (!saveResult.isSuccess) {
11455
+ this._dsxMessageService.SweetDialog.Dialog(saveResult.title || 'Error', saveResult.message || 'Error al guardar los datos', 'error', { showConfirmButton: true, showCancelButton: false });
11456
+ onError?.(saveResult);
11457
+ return;
11458
+ }
11459
+ // ✅ Si el guardado es exitoso y hay archivo
11460
+ if (uploadMethod && fileUpload && saveResult.data) {
11461
+ if (log)
11462
+ console.log('📤 [SaveEntityPostFile] Subiendo archivo...');
11463
+ uploadMethod(saveResult.data, fileUpload).subscribe({
11464
+ next: (uploadResult) => {
11465
+ // 📥 Log del upload
11466
+ if (log) {
11467
+ console.group('📥 [SaveEntityPostFile] Respuesta del upload');
11468
+ console.log('Resultado:', uploadResult);
11469
+ console.groupEnd();
11470
+ }
11471
+ if (uploadResult.isSuccess) {
11472
+ // ✅ Éxito: guardado + archivo
11473
+ if (log)
11474
+ console.log('✅ [SaveEntityPostFile] Subida exitosa');
11475
+ resetForm?.();
11476
+ onSuccess?.(uploadResult);
11477
+ }
11478
+ else {
11479
+ // ❌ Error en la subida
11480
+ if (log)
11481
+ console.warn('⚠️ [SaveEntityPostFile] Subida falló');
11482
+ const htmlContent = `
11483
+ <div style="text-align: left; font-family: -apple-system, system-ui, sans-serif;">
11484
+
11485
+ <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 12px;">
11486
+ <span style="font-size: 22px;">⚠️</span>
11487
+ <span style="font-size: 17px; font-weight: 600; color: #1a1a1a;">Archivo no subido</span>
11488
+ </div>
11489
+
11490
+ <p style="color: #555; font-size: 14px; line-height: 1.5; margin: 0 0 8px 0;">
11491
+ El registro se guardó, pero el archivo falló.
11492
+ </p>
11493
+
11494
+ <div style="background: #f8f4ec; padding: 6px 12px; border-radius: 4px; margin: 6px 0 14px 0;">
11495
+ <span style="color: #7a6b4a; font-size: 13px;">
11496
+ <strong>Motivo:</strong> ${uploadResult.message}
11497
+ </span>
11498
+ </div>
11499
+
11500
+ <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #eee;">
11501
+ <p style="font-weight: 500; color: #1a1a1a; font-size: 13px; margin: 0 0 6px 0;">
11502
+ ¿Qué deseas hacer?
11503
+ </p>
11504
+ <div style="font-size: 13px; color: #555; line-height: 1.7;">
11505
+ <div>📁 Guardar sin archivo <span style="color: #999; font-size: 12px;">(subir después)</span></div>
11506
+ <div>🗑️ Eliminar registro <span style="color: #999; font-size: 12px;">(corregir y reintentar)</span></div>
11507
+ </div>
11508
+ </div>
11509
+
11510
+ </div>`;
11511
+ this._dsxMessageService.SweetDialog.Dialog(uploadResult.title || 'Error', '', 'error', {
11512
+ html: htmlContent,
11513
+ showConfirmButton: true,
11514
+ confirmButtonText: 'Guardar',
11515
+ showCancelButton: true,
11516
+ cancelButtonText: 'Eliminar',
11517
+ }).then((isConfirmed) => {
11518
+ if (isConfirmed) {
11519
+ if (log)
11520
+ console.warn('⚠️ [SaveEntityPostFile] Se conservo el registro');
11521
+ resetForm?.();
11522
+ onSuccess?.(saveResult);
11523
+ }
11524
+ else {
11525
+ if (log)
11526
+ console.warn('❌ [SaveEntityPostFile] Se elimino el registro');
11527
+ // 2️⃣ Crear el observable del save con los datos sin archivo
11528
+ const delete$ = deleteMethod(saveResult.data, false);
11529
+ delete$.subscribe({
11530
+ next: (deleteResult) => {
11531
+ // 📥 Log del save
11532
+ if (log) {
11533
+ console.group('📥 [SaveEntityPostFile] Respuesta del delete');
11534
+ console.log('Resultado:', deleteResult);
11535
+ console.groupEnd();
11536
+ }
11537
+ if (deleteResult.isSuccess) {
11538
+ if (log)
11539
+ console.log('✅ [SaveEntityPostFile] Registro eliminado');
11540
+ onSuccess?.(deleteResult);
11541
+ }
11542
+ else {
11543
+ this._dsxMessageService.SweetDialog.Dialog('❌ Error al eliminar', deleteResult.message ||
11544
+ 'No se pudo eliminar el registro.', 'error', {
11545
+ showConfirmButton: true,
11546
+ showCancelButton: false,
11547
+ });
11548
+ onError?.(deleteResult);
11549
+ }
11550
+ },
11551
+ error: (err) => console.warn(err),
11552
+ complete: () => '',
11553
+ });
11554
+ }
11555
+ });
11556
+ onError?.(uploadResult);
11557
+ }
11558
+ },
11559
+ error: (uploadError) => {
11560
+ // ❌ Error en la subida (excepción)
11561
+ console.warn('Error no controlado en upload:', uploadError);
11562
+ onError?.(uploadError);
11563
+ },
11564
+ });
11565
+ return;
11566
+ }
11567
+ // ✅ Éxito sin archivo
11568
+ if (log)
11569
+ console.log('✅ [SaveEntityPostFile] Proceso completado (sin archivo)');
11570
+ resetForm?.();
11571
+ onSuccess?.(saveResult);
11572
+ },
11573
+ error: (error) => {
11574
+ // ❌ Error en el save (excepción)
11575
+ console.warn('Error no controlado en save:', error);
11576
+ onError?.(error);
11577
+ },
11578
+ });
11579
+ }
11580
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveEntityPostFileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
11581
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveEntityPostFileService, providedIn: 'root' });
11582
+ }
11583
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveEntityPostFileService, decorators: [{
11584
+ type: Injectable,
11585
+ args: [{
11586
+ providedIn: 'root',
11587
+ }]
11588
+ }] });
11589
+
10368
11590
  class NextToastNotifyService {
10369
11591
  // Configuraciones iniciales por defecto (ambos activados como en tus switches)
10370
11592
  defaultOptions = {
@@ -10408,8 +11630,10 @@ class ResultResponseService {
10408
11630
  else {
10409
11631
  const guideHtml = this.buildAssociatedGuideHtml(response?.data);
10410
11632
  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', {
11633
+ this._sweetAlert2DialogService.Dialog(response.title,
11634
+ // Se concatena limpiamente sin alterar la lógica
11635
+ `${mainMessage}${guideHtml}`, {
11636
+ icono: 'icon/sign.png',
10413
11637
  showCancelButton: false,
10414
11638
  showConfirmButton: true,
10415
11639
  });
@@ -10455,10 +11679,407 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
10455
11679
  }]
10456
11680
  }] });
10457
11681
 
11682
+ /**
11683
+ * Servicio especializado para extraer y procesar archivos (Blob) desde respuestas del backend.
11684
+ *
11685
+ * 📋 **Propósito:**
11686
+ * Este servicio actúa como un puente entre la respuesta del backend (normalizada como ServiceResult<FileData>)
11687
+ * y el consumidor final, extrayendo el archivo (Blob) y su nombre de forma segura y validada.
11688
+ *
11689
+ * 🎯 **Cuándo usar este servicio:**
11690
+ * - Cuando necesitas obtener el Blob y el nombre de archivo de un Observable<ServiceResult<FileData>>
11691
+ * - Cuando quieres validar que el archivo existe y no está vacío antes de procesarlo
11692
+ * - Cuando necesitas asegurar que el nombre del archivo tenga la extensión correcta
11693
+ * - Cuando quieres un manejo de errores detallado y estructurado
11694
+ *
11695
+ * 🔄 **Flujo de Trabajo:**
11696
+ * 1. El backend retorna un ServiceResult<FileData> (normalizado por getDownloadableFile)
11697
+ * 2. Este servicio extrae el Blob y el fileName
11698
+ * 3. Valida que el archivo exista y tenga contenido
11699
+ * 4. Asegura que el nombre tenga la extensión correcta según el MIME type
11700
+ * 5. Retorna un objeto con { blob, fileName } o un error estructurado
11701
+ *
11702
+ * 🔗 **Relación con otros servicios:**
11703
+ * - **ResultFileService**: Proporciona el Observable<ServiceResult<FileData>> de entrada
11704
+ * - **AlertaService**: Para mostrar mensajes al usuario
11705
+ *
11706
+ * @see ResultFileService - Servicio que realiza la petición HTTP
11707
+ * @see FileData - Estructura de datos del archivo
11708
+ * @see ServiceResult - Estructura de resultado estandarizada
11709
+ * @see Blob - API nativa de Blob de JavaScript
11710
+ */
11711
+ class ResultFileReturnBlobNameService {
11712
+ /** Configuración del entorno (para controlar debug) */
11713
+ environment = inject(ENVIRONMENT);
11714
+ /**
11715
+ * Extrae el Blob y el nombre de archivo de un Observable<ServiceResult<FileData>>,
11716
+ * realizando validaciones exhaustivas y manejo de errores detallado.
11717
+ *
11718
+ * 📝 **Descripción:**
11719
+ * Este método toma el Observable resultante de `getDownloadableFile` y extrae
11720
+ * el archivo (Blob) y su nombre, validando que todo sea correcto antes de
11721
+ * entregar el resultado al consumidor.
11722
+ *
11723
+ * 🔍 **Validaciones Realizadas:**
11724
+ * 1. ✅ Resultado nulo o indefinido → Error: "El resultado es nulo o indefinido"
11725
+ * 2. ✅ isSuccess = false → Error con title y message del backend
11726
+ * 3. ✅ data es null → Error: "El resultado no contiene datos"
11727
+ * 4. ✅ blob es null → Error: "El blob del archivo no está presente"
11728
+ * 5. ✅ blob.size = 0 → Error: "El archivo descargado está vacío (0 bytes)"
11729
+ * 6. ✅ Error de conexión → Error: "No se pudo obtener el archivo del servidor"
11730
+ *
11731
+ * 📁 **Gestión de Extensiones:**
11732
+ * El método automáticamente asegura que el nombre del archivo tenga la extensión
11733
+ * correcta basada en el MIME type. Si el nombre ya tiene extensión, la mantiene.
11734
+ *
11735
+ * 💡 **Ejemplos de Uso:**
11736
+ *
11737
+ * ```typescript
11738
+ * // EJEMPLO 1: Uso básico con descarga automática
11739
+ * const fileObservable = this.resultFileService.getDownloadableFile('/api/documentos/123');
11740
+ *
11741
+ * this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(fileObservable)
11742
+ * .subscribe({
11743
+ * next: ({ blob, fileName }) => {
11744
+ * // Descargar el archivo automáticamente
11745
+ * this.resultFileService.descargarBlob(blob, fileName);
11746
+ * },
11747
+ * error: (error) => {
11748
+ * // Error estructurado
11749
+ * this.alertaService.mostrarError(error.title, error.message);
11750
+ * }
11751
+ * });
11752
+ *
11753
+ * // EJEMPLO 2: Visualizar PDF
11754
+ * const fileObservable = this.resultFileService.getDownloadableFile('/api/documentos/123');
11755
+ *
11756
+ * this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(fileObservable)
11757
+ * .subscribe({
11758
+ * next: ({ blob, fileName }) => {
11759
+ * // Crear URL para visualizar
11760
+ * const url = URL.createObjectURL(blob);
11761
+ * window.open(url, '_blank');
11762
+ * },
11763
+ * error: (error) => {
11764
+ * console.error('Error al visualizar:', error);
11765
+ * }
11766
+ * });
11767
+ *
11768
+ * // EJEMPLO 3: Procesamiento personalizado con validación
11769
+ * const fileObservable = this.resultFileService.getDownloadableFile('/api/reportes', {
11770
+ * params: { formato: 'excel' }
11771
+ * });
11772
+ *
11773
+ * this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(
11774
+ * fileObservable,
11775
+ * 'reporte_generado' // fallbackFileName
11776
+ * ).subscribe({
11777
+ * next: ({ blob, fileName }) => {
11778
+ * // Procesar el blob según necesidades
11779
+ * if (fileName.endsWith('.xlsx')) {
11780
+ * this.procesarExcel(blob);
11781
+ * } else {
11782
+ * this.descargarArchivo(blob, fileName);
11783
+ * }
11784
+ * },
11785
+ * error: (error) => {
11786
+ * // Manejo detallado de errores
11787
+ * if (error.title === 'Archivo vacío') {
11788
+ * this.alertaService.advertencia('El archivo está vacío');
11789
+ * } else if (error.title === 'Error de conexión') {
11790
+ * this.alertaService.error('Error de red', 'Verifica tu conexión');
11791
+ * } else {
11792
+ * this.alertaService.error(error.title, error.message);
11793
+ * }
11794
+ * }
11795
+ * });
11796
+ *
11797
+ * // EJEMPLO 4: Uso con loading state
11798
+ * cargando = false;
11799
+ *
11800
+ * descargarDocumento(id: number): void {
11801
+ * this.cargando = true;
11802
+ * const fileObservable = this.resultFileService.getDownloadableFile(`/api/documentos/${id}`);
11803
+ *
11804
+ * this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(
11805
+ * fileObservable,
11806
+ * `documento_${id}`
11807
+ * ).subscribe({
11808
+ * next: ({ blob, fileName }) => {
11809
+ * this.cargando = false;
11810
+ * this.resultFileService.descargarBlob(blob, fileName);
11811
+ * this.alertaService.exito('Descarga completada');
11812
+ * },
11813
+ * error: (error) => {
11814
+ * this.cargando = false;
11815
+ * this.alertaService.error('Error', error.message);
11816
+ * }
11817
+ * });
11818
+ * }
11819
+ *
11820
+ * // EJEMPLO 5: Combinación con otros operadores RxJS
11821
+ * const fileObservable = this.resultFileService.getDownloadableFile('/api/documentos/123');
11822
+ *
11823
+ * this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(fileObservable)
11824
+ * .pipe(
11825
+ * tap(({ blob, fileName }) => {
11826
+ * console.log(`Archivo: ${fileName}, Tamaño: ${blob.size} bytes`);
11827
+ * }),
11828
+ * delay(1000), // Simular procesamiento
11829
+ * map(({ blob, fileName }) => ({
11830
+ * blob,
11831
+ * fileName,
11832
+ * tipo: this.obtenerTipoArchivo(fileName)
11833
+ * }))
11834
+ * )
11835
+ * .subscribe(({ blob, fileName, tipo }) => {
11836
+ * console.log(`Tipo de archivo: ${tipo}`);
11837
+ * });
11838
+ * ```
11839
+ *
11840
+ * @param fileObservable - Observable que emite un ServiceResult<FileData>
11841
+ * @param fallbackFileName - Nombre alternativo si no se puede determinar el nombre (por defecto: 'archivo sin nombre')
11842
+ *
11843
+ * @returns Observable que emite un objeto con:
11844
+ * - blob: Blob - El archivo extraído
11845
+ * - fileName: string - Nombre del archivo con extensión
11846
+ * - headers?: HttpHeaders - Headers de la respuesta (opcional, actualmente no implementado)
11847
+ *
11848
+ * @throws {Object} En caso de error, emite un objeto con:
11849
+ * - success: false
11850
+ * - title: string - Título del error
11851
+ * - message: string - Mensaje descriptivo
11852
+ * - error?: any - Error original (en caso de error de conexión)
11853
+ *
11854
+ * @see ResultFileService.getDownloadableFile - Método que genera el Observable de entrada
11855
+ * @see ResultFileService.descargarBlob - Para descargar el archivo extraído
11856
+ * @see ResultFileService.PdfView - Para visualizar PDFs
11857
+ */
11858
+ extractFileDataFromServiceResultEnhanced(fileObservable, fallbackFileName = 'archivo sin nombre') {
11859
+ return new Observable((observer) => {
11860
+ fileObservable.subscribe({
11861
+ next: (result) => {
11862
+ // Validación completa del resultado
11863
+ if (!result) {
11864
+ observer.error({
11865
+ success: false,
11866
+ title: 'Resultado nulo',
11867
+ message: 'El resultado es nulo o indefinido',
11868
+ });
11869
+ return;
11870
+ }
11871
+ if (!result.isSuccess) {
11872
+ observer.error({
11873
+ success: false,
11874
+ title: result.title || 'Error en la operación',
11875
+ message: result.message || 'El servidor reportó un error',
11876
+ });
11877
+ return;
11878
+ }
11879
+ if (!result.data) {
11880
+ observer.error({
11881
+ success: false,
11882
+ title: 'Datos no disponibles',
11883
+ message: 'El resultado no contiene datos',
11884
+ });
11885
+ return;
11886
+ }
11887
+ const blob = result.data.blob;
11888
+ if (!blob) {
11889
+ observer.error({
11890
+ success: false,
11891
+ title: 'Archivo no disponible',
11892
+ message: 'El blob del archivo no está presente',
11893
+ });
11894
+ return;
11895
+ }
11896
+ // Validar que el blob tenga tamaño
11897
+ if (blob.size === 0) {
11898
+ observer.error({
11899
+ success: false,
11900
+ title: 'Archivo vacío',
11901
+ message: 'El archivo descargado está vacío (0 bytes)',
11902
+ });
11903
+ return;
11904
+ }
11905
+ // Procesar nombre de archivo
11906
+ let fileName = result.data.fileName || fallbackFileName;
11907
+ fileName = this.ensureFileNameWithExtension(fileName, blob.type);
11908
+ // Log para debugging
11909
+ this.logIfNotProduction('✅ Archivo extraído exitosamente:', {
11910
+ tamaño: this.formatFileSize(blob.size),
11911
+ tipo: blob.type,
11912
+ nombre: fileName,
11913
+ });
11914
+ observer.next({
11915
+ blob: blob,
11916
+ fileName: fileName,
11917
+ });
11918
+ observer.complete();
11919
+ },
11920
+ error: (err) => {
11921
+ this.logIfNotProduction('❌ Error extrayendo archivo:', err);
11922
+ observer.error({
11923
+ success: false,
11924
+ title: 'Error de conexión',
11925
+ message: 'No se pudo obtener el archivo del servidor',
11926
+ error: err,
11927
+ });
11928
+ },
11929
+ });
11930
+ });
11931
+ }
11932
+ /**
11933
+ * Asegura que el nombre de archivo tenga una extensión válida.
11934
+ *
11935
+ * 📋 **Funcionamiento:**
11936
+ * 1. Si el nombre ya tiene una extensión (contiene un punto), lo retorna sin cambios.
11937
+ * 2. Si no tiene extensión, intenta obtenerla del MIME type.
11938
+ * 3. Si se puede determinar la extensión, la agrega al nombre.
11939
+ * 4. Si no se puede determinar, retorna el nombre sin cambios.
11940
+ *
11941
+ * 💡 **Ejemplos:**
11942
+ * - `ensureFileNameWithExtension('documento', 'application/pdf')` → `'documento.pdf'`
11943
+ * - `ensureFileNameWithExtension('foto', 'image/jpeg')` → `'foto.jpg'`
11944
+ * - `ensureFileNameWithExtension('archivo.txt', 'text/plain')` → `'archivo.txt'` (ya tiene extensión)
11945
+ * - `ensureFileNameWithExtension('reporte', 'application/unknown')` → `'reporte'`
11946
+ *
11947
+ * @param fileName - Nombre del archivo (puede o no tener extensión)
11948
+ * @param mimeType - Tipo MIME del archivo
11949
+ * @returns Nombre del archivo con extensión (si fue posible determinarla)
11950
+ */
11951
+ ensureFileNameWithExtension(fileName, mimeType) {
11952
+ // Si ya tiene extensión, retornarlo
11953
+ if (fileName.includes('.')) {
11954
+ return fileName;
11955
+ }
11956
+ // Obtener extensión del MIME type
11957
+ const extension = this.getExtensionFromMimeType(mimeType);
11958
+ return extension ? `${fileName}.${extension}` : fileName;
11959
+ }
11960
+ /**
11961
+ * Obtiene la extensión del archivo basado en el MIME type.
11962
+ *
11963
+ * 📁 **Mapa de MIME Types Soportados:**
11964
+ *
11965
+ * | MIME Type | Extensión |
11966
+ * |-----------|-----------|
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
+ * 🔄 **Comportamiento:**
11983
+ * 1. Busca el MIME type en el mapa predefinido.
11984
+ * 2. Si lo encuentra, retorna la extensión correspondiente.
11985
+ * 3. Si no lo encuentra, intenta extraer el subtipo del MIME (ej: 'pdf' de 'application/pdf').
11986
+ * 4. Si el subtipo es 'octet-stream' o no se puede determinar, retorna 'file'.
11987
+ *
11988
+ * @param mimeType - Tipo MIME del archivo
11989
+ * @returns Extensión del archivo (sin el punto) o 'file' si no se puede determinar
11990
+ */
11991
+ getExtensionFromMimeType(mimeType) {
11992
+ const mimeMap = {
11993
+ 'application/pdf': 'pdf',
11994
+ 'application/msword': 'doc',
11995
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
11996
+ 'application/vnd.ms-excel': 'xls',
11997
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
11998
+ 'application/vnd.ms-powerpoint': 'ppt',
11999
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
12000
+ 'image/jpeg': 'jpg',
12001
+ 'image/png': 'png',
12002
+ 'image/gif': 'gif',
12003
+ 'text/plain': 'txt',
12004
+ 'application/json': 'json',
12005
+ 'application/zip': 'zip',
12006
+ 'application/octet-stream': 'bin',
12007
+ };
12008
+ // Buscar extensión en el mapa
12009
+ const extension = mimeMap[mimeType];
12010
+ if (extension) {
12011
+ return extension;
12012
+ }
12013
+ // Si no está en el mapa, intentar extraer del MIME type
12014
+ const mimeParts = mimeType.split('/');
12015
+ if (mimeParts.length === 2) {
12016
+ const type = mimeParts[1];
12017
+ if (type !== 'octet-stream') {
12018
+ return type;
12019
+ }
12020
+ }
12021
+ // Si no se puede determinar, devolver 'file'
12022
+ return 'file';
12023
+ }
12024
+ /**
12025
+ * Formatea el tamaño del archivo para logging.
12026
+ *
12027
+ * 📊 **Formato de Salida:**
12028
+ * - Bytes: `"123 B"`
12029
+ * - Kilobytes: `"1.5 KB"`
12030
+ * - Megabytes: `"2.3 MB"`
12031
+ * - Gigabytes: `"1.2 GB"`
12032
+ *
12033
+ * 💡 **Ejemplos:**
12034
+ * - `formatFileSize(0)` → `"0 B"`
12035
+ * - `formatFileSize(1024)` → `"1.00 KB"`
12036
+ * - `formatFileSize(1536)` → `"1.50 KB"`
12037
+ * - `formatFileSize(1048576)` → `"1.00 MB"`
12038
+ *
12039
+ * @param bytes - Tamaño en bytes
12040
+ * @returns String formateado del tamaño
12041
+ */
12042
+ formatFileSize(bytes) {
12043
+ if (bytes === 0)
12044
+ return '0 B';
12045
+ const k = 1024;
12046
+ const sizes = ['B', 'KB', 'MB', 'GB'];
12047
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
12048
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
12049
+ }
12050
+ /**
12051
+ * Registra mensajes de log solo en entornos de desarrollo.
12052
+ *
12053
+ * 🛠️ **Comportamiento:**
12054
+ * - En desarrollo (production = false): Muestra los logs en consola
12055
+ * - En producción (production = true): No muestra nada
12056
+ *
12057
+ * 📝 **Uso Interno:**
12058
+ * Se utiliza para depuración sin afectar el rendimiento en producción.
12059
+ *
12060
+ * @param args - Argumentos para mostrar en el log
12061
+ */
12062
+ logIfNotProduction(...args) {
12063
+ if (!this.environment.production) {
12064
+ console.log('[ResultFileService]', ...args);
12065
+ }
12066
+ }
12067
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResultFileReturnBlobNameService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
12068
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResultFileReturnBlobNameService, providedIn: 'root' });
12069
+ }
12070
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResultFileReturnBlobNameService, decorators: [{
12071
+ type: Injectable,
12072
+ args: [{
12073
+ providedIn: 'root',
12074
+ }]
12075
+ }] });
12076
+
10458
12077
  class HelpersService {
10459
12078
  Array = inject(ArrayHelpersService);
12079
+ ServiceEntityPostFile = inject(SaveEntityPostFileService);
10460
12080
  ServiceResultFile = inject(ResultFileService);
10461
12081
  ServiceResultResponse = inject(ResultResponseService);
12082
+ ServiceResultFileReturnBlobName = inject(ResultFileReturnBlobNameService);
10462
12083
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HelpersService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10463
12084
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HelpersService, providedIn: 'root' });
10464
12085
  }
@@ -10916,19 +12537,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
10916
12537
  }]
10917
12538
  }] });
10918
12539
 
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
12540
  // form-preview.service.ts
10933
12541
  class FormPreviewService {
10934
12542
  environment;
@@ -12175,5 +13783,5 @@ function sorensenDiceValidator(dataSource, key, umbral = 0.7) {
12175
13783
  * Generated bundle index. Do not edit.
12176
13784
  */
12177
13785
 
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 };
13786
+ 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
13787
  //# sourceMappingURL=ngx-dsxlibrary.mjs.map