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.
- package/README.md +51 -0
- package/fesm2022/ngx-dsxlibrary.mjs +1956 -348
- package/fesm2022/ngx-dsxlibrary.mjs.map +1 -1
- package/ngx-dsxlibrary-2.21.73.tgz +0 -0
- package/package.json +1 -1
- package/src/assets/css/primeng_dsx_table.css +5 -5
- package/src/assets/css/sweetAlert2.css +16 -0
- package/types/ngx-dsxlibrary.d.ts +672 -100
- package/ngx-dsxlibrary-2.21.71.tgz +0 -0
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
4304
|
-
warning: '#FF7043',
|
|
4304
|
+
brown: '#8D6E63',
|
|
4305
4305
|
caution: '#FFB300',
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
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
|
-
|
|
4312
|
+
info: '#26C6DA',
|
|
4317
4313
|
magenta: '#D81B60',
|
|
4318
|
-
|
|
4314
|
+
neutral: '#90A4AE',
|
|
4319
4315
|
olive: '#808000',
|
|
4320
|
-
|
|
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-
|
|
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: '
|
|
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
|
-
//
|
|
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()
|
|
5005
|
-
if (this.debug() && this.isViewActive())
|
|
5006
|
-
this.log('hasExistingFile', {
|
|
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', {
|
|
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
|
-
|
|
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
|
-
//
|
|
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]
|
|
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
|
|
5168
|
+
// --- Métodos públicos ---
|
|
5088
5169
|
onSelect(event) {
|
|
5089
|
-
if (
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
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 (
|
|
5098
|
-
this.
|
|
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
|
-
|
|
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('
|
|
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('
|
|
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('
|
|
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 (
|
|
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
|
-
|
|
5382
|
+
}
|
|
5383
|
+
// ✅ Pasa si hay archivo guardado O hay archivo seleccionado
|
|
5384
|
+
if (this.hasExistingFile() || this.value) {
|
|
5168
5385
|
return null;
|
|
5169
|
-
|
|
5170
|
-
|
|
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 },
|
|
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 }] }],
|
|
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
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
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
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
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
|
-
|
|
5233
|
-
|
|
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
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
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 (
|
|
5242
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
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
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
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
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
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
|
-
|
|
5284
|
-
|
|
5645
|
+
}
|
|
5646
|
+
// ✅ Clonar objeto procesando archivos recursivamente
|
|
5647
|
+
cloneWithFileProcessing(obj) {
|
|
5648
|
+
if (!obj || typeof obj !== 'object') {
|
|
5649
|
+
return obj;
|
|
5285
5650
|
}
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
.replace(/>/g, '>')
|
|
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
|
-
|
|
5300
|
-
|
|
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
|
-
|
|
5305
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
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
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
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
|
-
|
|
5344
|
-
|
|
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
|
-
|
|
5349
|
-
|
|
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(
|
|
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 (
|
|
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: [
|
|
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, '&')
|
|
8726
|
+
.replace(/</g, '<')
|
|
8727
|
+
.replace(/>/g, '>')
|
|
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: "
|
|
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
|
-
|
|
8525
|
-
|
|
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
|
-
|
|
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(
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
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
|
-
|
|
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('[
|
|
8589
|
-
console.log('[
|
|
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
|
-
|
|
8597
|
-
|
|
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('[
|
|
9068
|
+
console.log('[dsxdate-datetime] Control padre:', this.parentControl());
|
|
8600
9069
|
}
|
|
8601
9070
|
});
|
|
8602
|
-
//
|
|
9071
|
+
// ✅ SINCRONIZAR VALIDADORES DEL PADRE AL CONTROL INTERNO
|
|
8603
9072
|
effect(() => {
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
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
|
|
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
|
-
*
|
|
10047
|
-
* -
|
|
10048
|
-
* -
|
|
10049
|
-
*
|
|
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
|
-
//
|
|
10293
|
-
|
|
10294
|
-
Dialog(firstParam, text, icoOrIcono, options = {}) {
|
|
11157
|
+
// Implementación unificada
|
|
11158
|
+
Dialog(firstParam, text, secondOrThird, thirdOrOptions) {
|
|
10295
11159
|
let title = '';
|
|
10296
|
-
let
|
|
10297
|
-
let
|
|
10298
|
-
|
|
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
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
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
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
'
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
10328
|
-
|
|
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
|
|
10332
|
-
const footer = `${
|
|
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,
|
|
10412
|
-
|
|
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
|