ngx-dsxlibrary 2.21.70 → 2.21.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/fesm2022/ngx-dsxlibrary.mjs +2475 -698
- package/fesm2022/ngx-dsxlibrary.mjs.map +1 -1
- package/ngx-dsxlibrary-2.21.72.tgz +0 -0
- package/package.json +1 -1
- package/src/assets/css/primeng_dsx_table.css +1 -2
- package/src/assets/css/sweetAlert2.css +16 -0
- package/types/ngx-dsxlibrary.d.ts +661 -111
- package/ngx-dsxlibrary-2.21.70.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, Location
|
|
8
|
+
import { CommonModule, AsyncPipe, JsonPipe, DatePipe, DecimalPipe, Location } from '@angular/common';
|
|
9
9
|
import * as i2$1 from 'primeng/tree';
|
|
10
10
|
import { TreeModule } from 'primeng/tree';
|
|
11
11
|
import * as i3 from 'primeng/api';
|
|
@@ -60,6 +60,7 @@ import { DrawerModule } from 'primeng/drawer';
|
|
|
60
60
|
import { FieldsetModule } from 'primeng/fieldset';
|
|
61
61
|
import { IconFieldModule } from 'primeng/iconfield';
|
|
62
62
|
import { InputIconModule } from 'primeng/inputicon';
|
|
63
|
+
import * as i1$8 from 'primeng/inputmask';
|
|
63
64
|
import { InputMaskModule } from 'primeng/inputmask';
|
|
64
65
|
import { InputNumberModule, InputNumber } from 'primeng/inputnumber';
|
|
65
66
|
import { InputTextModule } from 'primeng/inputtext';
|
|
@@ -2184,6 +2185,181 @@ function provideEnvironment(environment) {
|
|
|
2184
2185
|
};
|
|
2185
2186
|
}
|
|
2186
2187
|
|
|
2188
|
+
/**
|
|
2189
|
+
* action-message.model.ts
|
|
2190
|
+
*
|
|
2191
|
+
* Centraliza los tipos de acción y la configuración de mensajes para acciones de botones y operaciones comunes en la UI.
|
|
2192
|
+
*
|
|
2193
|
+
* - Define los tipos de acción estándar usados en dsx-button y otros componentes.
|
|
2194
|
+
* - Permite mantener uniformidad y evitar errores por typos en los tipos de acción.
|
|
2195
|
+
* - Provee la función getActionMessageConfig para obtener los textos, títulos e íconos de confirmación según la acción y el estado.
|
|
2196
|
+
* - Si se usa una acción válida pero sin mensaje configurado, en modo desarrollo muestra un warning para facilitar el mantenimiento.
|
|
2197
|
+
*
|
|
2198
|
+
* Uso recomendado:
|
|
2199
|
+
* - Importa ACTION_TYPES y ActionType donde se requiera validar o tipar acciones de botones.
|
|
2200
|
+
* - Usa getActionMessageConfig para obtener la configuración de confirmación antes de ejecutar acciones destructivas o de restauración.
|
|
2201
|
+
*
|
|
2202
|
+
* Ejemplo:
|
|
2203
|
+
* import { ACTION_TYPES, ActionType, getActionMessageConfig } from '.../action-message.model';
|
|
2204
|
+
* const config = getActionMessageConfig('softDelete', true);
|
|
2205
|
+
* // config: { title, message, icon }
|
|
2206
|
+
*
|
|
2207
|
+
* Si agregas un nuevo tipo de acción, recuerda añadir su mensaje en getActionMessageConfig.
|
|
2208
|
+
*/
|
|
2209
|
+
/**
|
|
2210
|
+
* Tipos de acción disponibles para el componente `dsx-button`.
|
|
2211
|
+
*
|
|
2212
|
+
* Usa `ACTION_TYPES` en el consumidor para evitar typos.
|
|
2213
|
+
*
|
|
2214
|
+
* **Persistencia de datos (formulario)**
|
|
2215
|
+
* - `create` → Navega a la ruta de nuevo registro (azul)
|
|
2216
|
+
* - `saveOrUpdate` → Muestra "Guardar" si `id` es 0, "Actualizar" si `id` > 0 (amarillo)
|
|
2217
|
+
*
|
|
2218
|
+
* **Eliminar**
|
|
2219
|
+
* - `delete` → Eliminar registro permanentemente (rojo)
|
|
2220
|
+
* - `softDelete` → Borrar registro con borrado lógico (amarillo)
|
|
2221
|
+
*
|
|
2222
|
+
* **Formulario**
|
|
2223
|
+
* - `validate` → Validar formulario sin guardar (gris)
|
|
2224
|
+
* - `restore` → Restaurar datos originales (gris)
|
|
2225
|
+
*
|
|
2226
|
+
* **Navegación / Utilidades**
|
|
2227
|
+
* - `home` → Ir al inicio (gris)
|
|
2228
|
+
* - `refresh` → Recargar datos desde el servidor (morado)
|
|
2229
|
+
*
|
|
2230
|
+
* **Consulta / Descarga**
|
|
2231
|
+
* - `details` → Ver el detalle de un registro (azul índigo)
|
|
2232
|
+
* - `view` → Previsualizar o abrir detalle (azul cielo)
|
|
2233
|
+
* - `export` → Exportar información a Excel (verde Excel)
|
|
2234
|
+
* - `download` → Descargar archivo o recurso (ámbar cálido)
|
|
2235
|
+
* - `import` → Importar datos desde archivo (azul acero)
|
|
2236
|
+
*/
|
|
2237
|
+
const ACTION_TYPES = {
|
|
2238
|
+
hardDelete: 'hardDelete',
|
|
2239
|
+
softDelete: 'softDelete',
|
|
2240
|
+
return: 'return',
|
|
2241
|
+
home: 'home',
|
|
2242
|
+
validate: 'validate',
|
|
2243
|
+
restore: 'restore',
|
|
2244
|
+
edit: 'edit',
|
|
2245
|
+
create: 'create',
|
|
2246
|
+
save: 'save',
|
|
2247
|
+
update: 'update',
|
|
2248
|
+
refresh: 'refresh',
|
|
2249
|
+
details: 'details',
|
|
2250
|
+
view: 'view',
|
|
2251
|
+
export: 'export',
|
|
2252
|
+
download: 'download',
|
|
2253
|
+
import: 'import',
|
|
2254
|
+
};
|
|
2255
|
+
function getActionMessageConfig(action, secondArg, thirdArg) {
|
|
2256
|
+
const isActive = action === 'softDelete' || action === 'hardDelete'
|
|
2257
|
+
? typeof secondArg === 'boolean'
|
|
2258
|
+
? secondArg
|
|
2259
|
+
: true
|
|
2260
|
+
: true;
|
|
2261
|
+
const dataPreview = action === 'softDelete' || action === 'hardDelete'
|
|
2262
|
+
? thirdArg
|
|
2263
|
+
: typeof secondArg === 'string'
|
|
2264
|
+
? secondArg
|
|
2265
|
+
: undefined;
|
|
2266
|
+
// Valores por defecto para todas las propiedades
|
|
2267
|
+
const defaults = {
|
|
2268
|
+
title: '',
|
|
2269
|
+
message: '',
|
|
2270
|
+
icon: undefined,
|
|
2271
|
+
confirmButtonText: undefined,
|
|
2272
|
+
cancelButtonText: undefined,
|
|
2273
|
+
showConfirmButton: undefined,
|
|
2274
|
+
showCancelButton: undefined,
|
|
2275
|
+
};
|
|
2276
|
+
if (action === 'softDelete') {
|
|
2277
|
+
if (isActive) {
|
|
2278
|
+
return {
|
|
2279
|
+
...defaults,
|
|
2280
|
+
title: '¿Borrar registro?',
|
|
2281
|
+
message: 'El registro dejará de estar disponible en las listas principales.' +
|
|
2282
|
+
(dataPreview ? `<br>${dataPreview}` : ''),
|
|
2283
|
+
icono: 'icon/trash-bin.png',
|
|
2284
|
+
confirmButtonText: 'Eliminar',
|
|
2285
|
+
cancelButtonText: 'Cancelar',
|
|
2286
|
+
showConfirmButton: true,
|
|
2287
|
+
showCancelButton: true,
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
else {
|
|
2291
|
+
return {
|
|
2292
|
+
...defaults,
|
|
2293
|
+
title: '¿Restaurar registro?',
|
|
2294
|
+
message: 'El registro volverá a estar <strong>activo</strong> y visible en todas las listas principales del sistema.' +
|
|
2295
|
+
(dataPreview ? `<br>${dataPreview}` : ''),
|
|
2296
|
+
icono: 'icon/documentation.png',
|
|
2297
|
+
confirmButtonText: 'Sí, restaurar',
|
|
2298
|
+
cancelButtonText: 'Dejar archivado',
|
|
2299
|
+
showConfirmButton: true,
|
|
2300
|
+
showCancelButton: true,
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
if (action === 'hardDelete') {
|
|
2305
|
+
return {
|
|
2306
|
+
...defaults,
|
|
2307
|
+
title: '¿Eliminar permanentemente?',
|
|
2308
|
+
message: 'Esta acción <strong>no se puede deshacer</strong>. Se perderán todos los datos asociados a este registro de forma inmediata.' +
|
|
2309
|
+
(dataPreview ? `<br>${dataPreview}` : ''),
|
|
2310
|
+
icono: 'icon/delete.png',
|
|
2311
|
+
confirmButtonText: 'Eliminar permanentemente',
|
|
2312
|
+
cancelButtonText: 'Cancelar',
|
|
2313
|
+
showConfirmButton: true,
|
|
2314
|
+
showCancelButton: true,
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
if (action === 'save') {
|
|
2318
|
+
return {
|
|
2319
|
+
...defaults,
|
|
2320
|
+
title: '¿Guardar registro?',
|
|
2321
|
+
message: 'El nuevo registro se guardará en el sistema.' +
|
|
2322
|
+
(dataPreview ? `<br>${dataPreview}` : ''),
|
|
2323
|
+
icono: 'icon2/save_2029637.png',
|
|
2324
|
+
confirmButtonText: 'Guardar',
|
|
2325
|
+
cancelButtonText: 'Cancelar',
|
|
2326
|
+
showConfirmButton: true,
|
|
2327
|
+
showCancelButton: true,
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
if (action === 'update') {
|
|
2331
|
+
return {
|
|
2332
|
+
...defaults,
|
|
2333
|
+
title: '¿Actualizar registro?',
|
|
2334
|
+
message: 'Los cambios realizados se actualizarán en el sistema.' +
|
|
2335
|
+
(dataPreview ? `<br>${dataPreview}` : ''),
|
|
2336
|
+
icono: 'icon2/save_2029637.png',
|
|
2337
|
+
confirmButtonText: 'Actualizar',
|
|
2338
|
+
cancelButtonText: 'Cancelar',
|
|
2339
|
+
showConfirmButton: true,
|
|
2340
|
+
showCancelButton: true,
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
if (action === 'restore') {
|
|
2344
|
+
return {
|
|
2345
|
+
...defaults,
|
|
2346
|
+
title: '¿Revertir cambios?',
|
|
2347
|
+
message: 'Se perderán todos los cambios que hayas realizado en el formulario y se volverán a cargar los datos originales.',
|
|
2348
|
+
icono: 'icon/data-recovery.png',
|
|
2349
|
+
confirmButtonText: 'Restaurar',
|
|
2350
|
+
cancelButtonText: 'Cancelar',
|
|
2351
|
+
showConfirmButton: true,
|
|
2352
|
+
showCancelButton: true,
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
|
2356
|
+
console.warn(`[getActionMessageConfig] Acción "${action}" existe pero no tiene mensaje configurado. ` +
|
|
2357
|
+
'Agrega la configuración correspondiente para evitar este warning.');
|
|
2358
|
+
}
|
|
2359
|
+
// Retorna los valores por defecto si la acción no es válida
|
|
2360
|
+
return { ...defaults };
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2187
2363
|
const INITIAL_PARAMETERS = new InjectionToken('InitialParameters');
|
|
2188
2364
|
|
|
2189
2365
|
/**
|
|
@@ -4103,222 +4279,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
4103
4279
|
}] });
|
|
4104
4280
|
|
|
4105
4281
|
/**
|
|
4106
|
-
*
|
|
4107
|
-
*
|
|
4108
|
-
* Centraliza los tipos de acción y la configuración de mensajes para acciones de botones y operaciones comunes en la UI.
|
|
4109
|
-
*
|
|
4110
|
-
* - Define los tipos de acción estándar usados en dsx-button y otros componentes.
|
|
4111
|
-
* - Permite mantener uniformidad y evitar errores por typos en los tipos de acción.
|
|
4112
|
-
* - Provee la función getActionMessageConfig para obtener los textos, títulos e íconos de confirmación según la acción y el estado.
|
|
4113
|
-
* - Si se usa una acción válida pero sin mensaje configurado, en modo desarrollo muestra un warning para facilitar el mantenimiento.
|
|
4114
|
-
*
|
|
4115
|
-
* Uso recomendado:
|
|
4116
|
-
* - Importa ACTION_TYPES y ActionType donde se requiera validar o tipar acciones de botones.
|
|
4117
|
-
* - Usa getActionMessageConfig para obtener la configuración de confirmación antes de ejecutar acciones destructivas o de restauración.
|
|
4118
|
-
*
|
|
4119
|
-
* Ejemplo:
|
|
4120
|
-
* import { ACTION_TYPES, ActionType, getActionMessageConfig } from '.../action-message.model';
|
|
4121
|
-
* const config = getActionMessageConfig('softDelete', true);
|
|
4122
|
-
* // config: { title, message, icon }
|
|
4123
|
-
*
|
|
4124
|
-
* Si agregas un nuevo tipo de acción, recuerda añadir su mensaje en getActionMessageConfig.
|
|
4125
|
-
*/
|
|
4126
|
-
/**
|
|
4127
|
-
* Tipos de acción disponibles para el componente `dsx-button`.
|
|
4128
|
-
*
|
|
4129
|
-
* Usa `ACTION_TYPES` en el consumidor para evitar typos.
|
|
4130
|
-
*
|
|
4131
|
-
* **Persistencia de datos (formulario)**
|
|
4132
|
-
* - `create` → Navega a la ruta de nuevo registro (azul)
|
|
4133
|
-
* - `saveOrUpdate` → Muestra "Guardar" si `id` es 0, "Actualizar" si `id` > 0 (amarillo)
|
|
4134
|
-
*
|
|
4135
|
-
* **Eliminar**
|
|
4136
|
-
* - `delete` → Eliminar registro permanentemente (rojo)
|
|
4137
|
-
* - `softDelete` → Borrar registro con borrado lógico (amarillo)
|
|
4138
|
-
*
|
|
4139
|
-
* **Formulario**
|
|
4140
|
-
* - `validate` → Validar formulario sin guardar (gris)
|
|
4141
|
-
* - `restore` → Restaurar datos originales (gris)
|
|
4282
|
+
* Paleta corporativa de colores para `dsx-button`.
|
|
4142
4283
|
*
|
|
4143
|
-
*
|
|
4144
|
-
*
|
|
4145
|
-
* - `refresh` → Recargar datos desde el servidor (morado)
|
|
4284
|
+
* Cada token tiene un significado semántico fijo. Úsala como referencia
|
|
4285
|
+
* para asignar `colorToken` al añadir nuevos tipos de botón.
|
|
4146
4286
|
*
|
|
4147
|
-
*
|
|
4148
|
-
*
|
|
4149
|
-
*
|
|
4150
|
-
*
|
|
4151
|
-
*
|
|
4152
|
-
*
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
create: 'create',
|
|
4163
|
-
save: 'save',
|
|
4164
|
-
update: 'update',
|
|
4165
|
-
refresh: 'refresh',
|
|
4166
|
-
details: 'details',
|
|
4167
|
-
view: 'view',
|
|
4168
|
-
export: 'export',
|
|
4169
|
-
download: 'download',
|
|
4170
|
-
import: 'import',
|
|
4171
|
-
};
|
|
4172
|
-
function getActionMessageConfig(action, secondArg, thirdArg) {
|
|
4173
|
-
const isActive = action === 'softDelete' || action === 'hardDelete'
|
|
4174
|
-
? typeof secondArg === 'boolean'
|
|
4175
|
-
? secondArg
|
|
4176
|
-
: true
|
|
4177
|
-
: true;
|
|
4178
|
-
const dataPreview = action === 'softDelete' || action === 'hardDelete'
|
|
4179
|
-
? thirdArg
|
|
4180
|
-
: typeof secondArg === 'string'
|
|
4181
|
-
? secondArg
|
|
4182
|
-
: undefined;
|
|
4183
|
-
// Valores por defecto para todas las propiedades
|
|
4184
|
-
const defaults = {
|
|
4185
|
-
title: '',
|
|
4186
|
-
message: '',
|
|
4187
|
-
icon: undefined,
|
|
4188
|
-
confirmButtonText: undefined,
|
|
4189
|
-
cancelButtonText: undefined,
|
|
4190
|
-
showConfirmButton: undefined,
|
|
4191
|
-
showCancelButton: undefined,
|
|
4192
|
-
};
|
|
4193
|
-
if (action === 'softDelete') {
|
|
4194
|
-
if (isActive) {
|
|
4195
|
-
return {
|
|
4196
|
-
...defaults,
|
|
4197
|
-
title: '¿Borrar registro?',
|
|
4198
|
-
message: 'El registro dejará de estar disponible en las listas principales.' +
|
|
4199
|
-
(dataPreview ? `<br>${dataPreview}` : ''),
|
|
4200
|
-
icono: 'icon/trash-bin.png',
|
|
4201
|
-
confirmButtonText: 'Eliminar',
|
|
4202
|
-
cancelButtonText: 'Cancelar',
|
|
4203
|
-
showConfirmButton: true,
|
|
4204
|
-
showCancelButton: true,
|
|
4205
|
-
};
|
|
4206
|
-
}
|
|
4207
|
-
else {
|
|
4208
|
-
return {
|
|
4209
|
-
...defaults,
|
|
4210
|
-
title: '¿Restaurar registro?',
|
|
4211
|
-
message: 'El registro volverá a estar <strong>activo</strong> y visible en todas las listas principales del sistema.' +
|
|
4212
|
-
(dataPreview ? `<br>${dataPreview}` : ''),
|
|
4213
|
-
icono: 'icon/documentation.png',
|
|
4214
|
-
confirmButtonText: 'Sí, restaurar',
|
|
4215
|
-
cancelButtonText: 'Dejar archivado',
|
|
4216
|
-
showConfirmButton: true,
|
|
4217
|
-
showCancelButton: true,
|
|
4218
|
-
};
|
|
4219
|
-
}
|
|
4220
|
-
}
|
|
4221
|
-
if (action === 'hardDelete') {
|
|
4222
|
-
return {
|
|
4223
|
-
...defaults,
|
|
4224
|
-
title: '¿Eliminar permanentemente?',
|
|
4225
|
-
message: 'Esta acción <strong>no se puede deshacer</strong>. Se perderán todos los datos asociados a este registro de forma inmediata.' +
|
|
4226
|
-
(dataPreview ? `<br>${dataPreview}` : ''),
|
|
4227
|
-
icono: 'icon/delete.png',
|
|
4228
|
-
confirmButtonText: 'Eliminar permanentemente',
|
|
4229
|
-
cancelButtonText: 'Cancelar',
|
|
4230
|
-
showConfirmButton: true,
|
|
4231
|
-
showCancelButton: true,
|
|
4232
|
-
};
|
|
4233
|
-
}
|
|
4234
|
-
if (action === 'save') {
|
|
4235
|
-
return {
|
|
4236
|
-
...defaults,
|
|
4237
|
-
title: '¿Guardar registro?',
|
|
4238
|
-
message: 'El nuevo registro se guardará en el sistema.' +
|
|
4239
|
-
(dataPreview ? `<br>${dataPreview}` : ''),
|
|
4240
|
-
icono: 'icon2/save_2029637.png',
|
|
4241
|
-
confirmButtonText: 'Guardar',
|
|
4242
|
-
cancelButtonText: 'Cancelar',
|
|
4243
|
-
showConfirmButton: true,
|
|
4244
|
-
showCancelButton: true,
|
|
4245
|
-
};
|
|
4246
|
-
}
|
|
4247
|
-
if (action === 'update') {
|
|
4248
|
-
return {
|
|
4249
|
-
...defaults,
|
|
4250
|
-
title: '¿Actualizar registro?',
|
|
4251
|
-
message: 'Los cambios realizados se actualizarán en el sistema.' +
|
|
4252
|
-
(dataPreview ? `<br>${dataPreview}` : ''),
|
|
4253
|
-
icono: 'icon2/save_2029637.png',
|
|
4254
|
-
confirmButtonText: 'Actualizar',
|
|
4255
|
-
cancelButtonText: 'Cancelar',
|
|
4256
|
-
showConfirmButton: true,
|
|
4257
|
-
showCancelButton: true,
|
|
4258
|
-
};
|
|
4259
|
-
}
|
|
4260
|
-
if (action === 'restore') {
|
|
4261
|
-
return {
|
|
4262
|
-
...defaults,
|
|
4263
|
-
title: '¿Revertir cambios?',
|
|
4264
|
-
message: 'Se perderán todos los cambios que hayas realizado en el formulario y se volverán a cargar los datos originales.',
|
|
4265
|
-
icono: 'icon/data-recovery.png',
|
|
4266
|
-
confirmButtonText: 'Restaurar',
|
|
4267
|
-
cancelButtonText: 'Cancelar',
|
|
4268
|
-
showConfirmButton: true,
|
|
4269
|
-
showCancelButton: true,
|
|
4270
|
-
};
|
|
4271
|
-
}
|
|
4272
|
-
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
|
4273
|
-
console.warn(`[getActionMessageConfig] Acción "${action}" existe pero no tiene mensaje configurado. ` +
|
|
4274
|
-
'Agrega la configuración correspondiente para evitar este warning.');
|
|
4275
|
-
}
|
|
4276
|
-
// Retorna los valores por defecto si la acción no es válida
|
|
4277
|
-
return { ...defaults };
|
|
4278
|
-
}
|
|
4279
|
-
|
|
4280
|
-
/**
|
|
4281
|
-
* Paleta corporativa de colores para `dsx-button`.
|
|
4282
|
-
*
|
|
4283
|
-
* Cada token tiene un significado semántico fijo. Úsala como referencia
|
|
4284
|
-
* para asignar `colorToken` al añadir nuevos tipos de botón.
|
|
4285
|
-
*
|
|
4286
|
-
* | Token | Color | Uso semántico |
|
|
4287
|
-
* |-----------|----------------|---------------------------------------------------|
|
|
4288
|
-
* | danger | Coral rojo | Acción destructiva e irreversible (eliminar) |
|
|
4289
|
-
* | warning | Naranja cálido | Acción con riesgo moderado / borrado lógico |
|
|
4290
|
-
* | caution | Ámbar dorado | Requiere verificación del usuario (actualizar) |
|
|
4291
|
-
* | success | Verde menta | Guardar nuevo registro / agregar |
|
|
4292
|
-
* | primary | Azul claro | Editar / navegar con contexto de registro |
|
|
4293
|
-
* | info | Cian suave | Recargar datos / refrescar vista |
|
|
4294
|
-
* | neutral | Gris azulado | Acción secundaria / navegación sin contexto |
|
|
4295
|
-
* | restore | Violeta suave | Deshacer / restaurar estado anterior |
|
|
4296
|
-
* | view | Teal profundo | Ver / previsualizar detalle |
|
|
4297
|
-
* | excel | Verde Excel | Exportar información a hoja de cálculo |
|
|
4298
|
-
* | download | Naranja quemado| Descargar archivo o recurso |
|
|
4299
|
-
* | import | Azul acero | Importar información desde archivo |
|
|
4300
|
-
* | details | Azul índigo | Abrir detalle de registro |
|
|
4287
|
+
* | Token | Color | Uso semántico |
|
|
4288
|
+
* |-----------|----------------|---------------------------------------------------|
|
|
4289
|
+
* | danger | Coral rojo | Acción destructiva e irreversible (eliminar) |
|
|
4290
|
+
* | warning | Naranja cálido | Acción con riesgo moderado / borrado lógico |
|
|
4291
|
+
* | caution | Ámbar dorado | Requiere verificación del usuario (actualizar) |
|
|
4292
|
+
* | success | Verde menta | Guardar nuevo registro / agregar |
|
|
4293
|
+
* | primary | Azul claro | Editar / navegar con contexto de registro |
|
|
4294
|
+
* | info | Cian suave | Recargar datos / refrescar vista |
|
|
4295
|
+
* | neutral | Gris azulado | Acción secundaria / navegación sin contexto |
|
|
4296
|
+
* | restore | Violeta suave | Deshacer / restaurar estado anterior |
|
|
4297
|
+
* | view | Teal profundo | Ver / previsualizar detalle |
|
|
4298
|
+
* | excel | Verde Excel | Exportar información a hoja de cálculo |
|
|
4299
|
+
* | download | Naranja quemado| Descargar archivo o recurso |
|
|
4300
|
+
* | import | Azul acero | Importar información desde archivo |
|
|
4301
|
+
* | details | Azul índigo | Abrir detalle de registro |
|
|
4301
4302
|
*/
|
|
4302
4303
|
const DSX_PALETTE = {
|
|
4303
4304
|
danger: '#EF5350',
|
|
4304
4305
|
warning: '#FF7043',
|
|
4305
4306
|
caution: '#FFB300',
|
|
4306
|
-
success: '#0ba055',
|
|
4307
|
-
primary: '#0066cc',
|
|
4307
|
+
success: '#0ba055',
|
|
4308
|
+
primary: '#0066cc',
|
|
4308
4309
|
info: '#26C6DA',
|
|
4309
4310
|
neutral: '#90A4AE',
|
|
4310
|
-
restore: '#B0BEC5',
|
|
4311
|
-
return: '#11438d',
|
|
4311
|
+
restore: '#B0BEC5',
|
|
4312
|
+
return: '#11438d',
|
|
4312
4313
|
view: '#00838F',
|
|
4313
4314
|
excel: '#217346',
|
|
4314
4315
|
download: '#E65100',
|
|
4315
4316
|
import: '#546E7A',
|
|
4316
4317
|
details: '#3949AB',
|
|
4317
|
-
magenta: '#D81B60',
|
|
4318
|
-
cyan: '#00BCD4',
|
|
4319
|
-
olive: '#808000',
|
|
4320
|
-
brown: '#8D6E63',
|
|
4321
|
-
slate: '#607D8B',
|
|
4318
|
+
magenta: '#D81B60',
|
|
4319
|
+
cyan: '#00BCD4',
|
|
4320
|
+
olive: '#808000',
|
|
4321
|
+
brown: '#8D6E63',
|
|
4322
|
+
slate: '#607D8B',
|
|
4322
4323
|
};
|
|
4323
4324
|
/** Tipos que necesitan un `[id]` mayor a 0 para poder mostrarse. */
|
|
4324
4325
|
const TYPES_REQUIRING_ID = [
|
|
@@ -4331,23 +4332,56 @@ class DsxButtonComponent {
|
|
|
4331
4332
|
renderCount = 0;
|
|
4332
4333
|
_paramService = inject((ParameterValuesService));
|
|
4333
4334
|
_router = inject(Router);
|
|
4334
|
-
|
|
4335
|
+
_isDev = isDevMode();
|
|
4336
|
+
lastValidationState = null;
|
|
4335
4337
|
size = input(undefined, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
4336
4338
|
_environment = inject(ENVIRONMENT, {
|
|
4337
4339
|
optional: true,
|
|
4338
4340
|
});
|
|
4339
4341
|
constructor() {
|
|
4340
|
-
|
|
4342
|
+
// Solo activar efectos de debug en desarrollo
|
|
4343
|
+
if (this._isDev) {
|
|
4344
|
+
// Efecto principal que captura todo el estado
|
|
4341
4345
|
effect(() => {
|
|
4342
4346
|
if (!this.debug())
|
|
4343
4347
|
return;
|
|
4344
4348
|
this.renderCount++;
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4349
|
+
this.logFullState();
|
|
4350
|
+
});
|
|
4351
|
+
// Efecto específico para validaciones
|
|
4352
|
+
effect(() => {
|
|
4353
|
+
if (!this.debug())
|
|
4354
|
+
return;
|
|
4355
|
+
const visible = this.visible();
|
|
4356
|
+
const validations = {
|
|
4357
|
+
global: this.passesGlobalVisibilityPolicy(),
|
|
4358
|
+
parameter: this.passesParameterValidation(),
|
|
4359
|
+
id: this.passesIdValidation(),
|
|
4360
|
+
visible,
|
|
4361
|
+
};
|
|
4362
|
+
const stateKey = JSON.stringify(validations);
|
|
4363
|
+
if (stateKey !== this.lastValidationState) {
|
|
4364
|
+
this.lastValidationState = stateKey;
|
|
4365
|
+
this.logValidationIssues(validations);
|
|
4366
|
+
}
|
|
4367
|
+
});
|
|
4368
|
+
// Efecto para detectar problemas con IDs
|
|
4369
|
+
effect(() => {
|
|
4370
|
+
if (!this.debug())
|
|
4371
|
+
return;
|
|
4372
|
+
const type = this.type();
|
|
4373
|
+
if (TYPES_REQUIRING_ID.includes(type)) {
|
|
4374
|
+
const id = this.getNormalizedId();
|
|
4375
|
+
const routeId = this.getIdFromNavigationTarget(this.effectiveRouterLink());
|
|
4376
|
+
if (!id && !routeId) {
|
|
4377
|
+
console.warn(`%c⚠️ [dsx-button:${type}] ID no disponible para acción que lo requiere`, 'color:#ed6c02;font-weight:bold', {
|
|
4378
|
+
type,
|
|
4379
|
+
explicitId: this.id(),
|
|
4380
|
+
routeId,
|
|
4381
|
+
requireIdInput: this.requireIdInput(),
|
|
4382
|
+
});
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4351
4385
|
});
|
|
4352
4386
|
}
|
|
4353
4387
|
}
|
|
@@ -4357,17 +4391,7 @@ class DsxButtonComponent {
|
|
|
4357
4391
|
type = input(ACTION_TYPES.hardDelete, ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
4358
4392
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
4359
4393
|
routerLink = input(undefined, ...(ngDevMode ? [{ debugName: "routerLink" }] : /* istanbul ignore next */ []));
|
|
4360
|
-
/**
|
|
4361
|
-
* Ruta de navegación simplificada. La `/` inicial se agrega automáticamente.
|
|
4362
|
-
* Cada segmento solo admite: letras, números, `-`, `_`, `.` y `:` (params de ruta).
|
|
4363
|
-
* Ejemplos válidos: `'home'`, `'container/nuevo'`, `'item/123'`
|
|
4364
|
-
* Ejemplos inválidos: `'item/*'`, `'item?foo=bar'`, `'item/../other'`
|
|
4365
|
-
* Para rutas dinámicas complejas usa `[routerLink]` directamente.
|
|
4366
|
-
*/
|
|
4367
4394
|
routerPath = input(undefined, ...(ngDevMode ? [{ debugName: "routerPath" }] : /* istanbul ignore next */ []));
|
|
4368
|
-
/** Ancho del botón. Usa `rem` para que escale con la preferencia de fuente del usuario.
|
|
4369
|
-
* Ejemplo: `'8rem'`, `'12rem'`. Valor por defecto: `'9rem'`.
|
|
4370
|
-
*/
|
|
4371
4395
|
width = input('9rem', ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
|
|
4372
4396
|
iconOnly = input(false, ...(ngDevMode ? [{ debugName: "iconOnly" }] : /* istanbul ignore next */ []));
|
|
4373
4397
|
iconOnlyWidth = input('2.5rem', ...(ngDevMode ? [{ debugName: "iconOnlyWidth" }] : /* istanbul ignore next */ []));
|
|
@@ -4383,24 +4407,12 @@ class DsxButtonComponent {
|
|
|
4383
4407
|
tooltipOverride = input(undefined, ...(ngDevMode ? [{ debugName: "tooltipOverride" }] : /* istanbul ignore next */ []));
|
|
4384
4408
|
iconOverride = input(undefined, ...(ngDevMode ? [{ debugName: "iconOverride" }] : /* istanbul ignore next */ []));
|
|
4385
4409
|
primeIconOverride = input(undefined, ...(ngDevMode ? [{ debugName: "primeIconOverride" }] : /* istanbul ignore next */ []));
|
|
4386
|
-
/** Permite personalizar el color usando únicamente tokens de la paleta DSX. */
|
|
4387
4410
|
colorOverride = input(undefined, ...(ngDevMode ? [{ debugName: "colorOverride" }] : /* istanbul ignore next */ []));
|
|
4388
|
-
/** Permite forzar estilo outlined o sólido por instancia. */
|
|
4389
4411
|
outlinedOverride = input(undefined, ...(ngDevMode ? [{ debugName: "outlinedOverride" }] : /* istanbul ignore next */ []));
|
|
4390
|
-
/**
|
|
4391
|
-
* Override por instancia para el botón `validate`.
|
|
4392
|
-
* Tiene prioridad sobre `environment.showValidateButton`.
|
|
4393
|
-
*/
|
|
4394
4412
|
showValidateButtonOverride = input(undefined, ...(ngDevMode ? [{ debugName: "showValidateButtonOverride" }] : /* istanbul ignore next */ []));
|
|
4395
|
-
/**
|
|
4396
|
-
* Override por instancia para el botón `restore`.
|
|
4397
|
-
* Tiene prioridad sobre `environment.showRestoreButton`.
|
|
4398
|
-
*/
|
|
4399
4413
|
showRestoreButtonOverride = input(undefined, ...(ngDevMode ? [{ debugName: "showRestoreButtonOverride" }] : /* istanbul ignore next */ []));
|
|
4400
4414
|
action = output();
|
|
4401
|
-
/** Valida y normaliza `routerPath`: agrega `/` inicial y rechaza caracteres inválidos. */
|
|
4402
4415
|
resolvedRouterLink = computed(() => {
|
|
4403
|
-
this.log('computed resolvedRouterLink');
|
|
4404
4416
|
const raw = this.routerPath();
|
|
4405
4417
|
if (!raw)
|
|
4406
4418
|
return undefined;
|
|
@@ -4409,23 +4421,22 @@ class DsxButtonComponent {
|
|
|
4409
4421
|
const rawSegments = normalizedRaw.split('/');
|
|
4410
4422
|
const hasEmptySegments = normalizedRaw.length > 0 && rawSegments.some((s) => s.length === 0);
|
|
4411
4423
|
if (hasEmptySegments) {
|
|
4412
|
-
|
|
4413
|
-
`
|
|
4424
|
+
if (this._isDev && this.debug()) {
|
|
4425
|
+
console.warn(`[dsx-button] routerPath contiene segmentos vacíos: "${raw}"`);
|
|
4426
|
+
}
|
|
4414
4427
|
return undefined;
|
|
4415
4428
|
}
|
|
4416
4429
|
const segments = rawSegments.filter(Boolean);
|
|
4417
4430
|
const invalid = segments.filter((s) => !VALID_SEGMENT.test(s));
|
|
4418
4431
|
if (invalid.length > 0) {
|
|
4419
|
-
|
|
4420
|
-
`
|
|
4421
|
-
|
|
4432
|
+
if (this._isDev && this.debug()) {
|
|
4433
|
+
console.warn(`[dsx-button] routerPath contiene segmentos inválidos: "${invalid.join('", "')}"`);
|
|
4434
|
+
}
|
|
4422
4435
|
return undefined;
|
|
4423
4436
|
}
|
|
4424
4437
|
return `/${segments.join('/')}`;
|
|
4425
4438
|
}, ...(ngDevMode ? [{ debugName: "resolvedRouterLink" }] : /* istanbul ignore next */ []));
|
|
4426
|
-
/** Prioriza routerPath (validado) sobre routerLink (libre). */
|
|
4427
4439
|
effectiveRouterLink = computed(() => {
|
|
4428
|
-
this.log('computed effectiveRouterLink');
|
|
4429
4440
|
const resolvedPath = this.resolvedRouterLink();
|
|
4430
4441
|
if (resolvedPath) {
|
|
4431
4442
|
return resolvedPath;
|
|
@@ -4438,7 +4449,6 @@ class DsxButtonComponent {
|
|
|
4438
4449
|
if (!trimmed) {
|
|
4439
4450
|
return link;
|
|
4440
4451
|
}
|
|
4441
|
-
// URLs externas no se normalizan ni validan como rutas internas.
|
|
4442
4452
|
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed)) {
|
|
4443
4453
|
return link;
|
|
4444
4454
|
}
|
|
@@ -4448,20 +4458,20 @@ class DsxButtonComponent {
|
|
|
4448
4458
|
const hasEmptySegments = pathForValidation.length > 0 &&
|
|
4449
4459
|
rawSegments.some((segment) => segment.length === 0);
|
|
4450
4460
|
if (hasEmptySegments) {
|
|
4451
|
-
|
|
4452
|
-
`
|
|
4461
|
+
if (this._isDev && this.debug()) {
|
|
4462
|
+
console.warn(`[dsx-button] routerLink contiene segmentos vacíos: "${link}"`);
|
|
4463
|
+
}
|
|
4453
4464
|
return link;
|
|
4454
4465
|
}
|
|
4455
4466
|
const invalid = rawSegments
|
|
4456
4467
|
.filter(Boolean)
|
|
4457
4468
|
.filter((segment) => !VALID_SEGMENT.test(segment));
|
|
4458
4469
|
if (invalid.length > 0) {
|
|
4459
|
-
|
|
4460
|
-
`
|
|
4461
|
-
|
|
4470
|
+
if (this._isDev && this.debug()) {
|
|
4471
|
+
console.warn(`[dsx-button] routerLink contiene segmentos inválidos: "${invalid.join('", "')}"`);
|
|
4472
|
+
}
|
|
4462
4473
|
return link;
|
|
4463
4474
|
}
|
|
4464
|
-
// Si llega una ruta simple sin prefijo, se normaliza a absoluta para evitar navegación relativa inesperada.
|
|
4465
4475
|
if (link.startsWith('/') ||
|
|
4466
4476
|
link.startsWith('./') ||
|
|
4467
4477
|
link.startsWith('../')) {
|
|
@@ -4472,23 +4482,6 @@ class DsxButtonComponent {
|
|
|
4472
4482
|
}
|
|
4473
4483
|
return `/${trimmed.split('/').filter(Boolean).join('/')}`;
|
|
4474
4484
|
}, ...(ngDevMode ? [{ debugName: "effectiveRouterLink" }] : /* istanbul ignore next */ []));
|
|
4475
|
-
// get buttonStyle(): Record<string, string> {
|
|
4476
|
-
// this.log('getter buttonStyle');
|
|
4477
|
-
// const color = DSX_PALETTE[this.config.colorToken];
|
|
4478
|
-
// const width = this.iconOnly() ? this.iconOnlyWidth() : this.width();
|
|
4479
|
-
// if (this.isCompactIconButton) {
|
|
4480
|
-
// return {
|
|
4481
|
-
// width,
|
|
4482
|
-
// background: 'transparent',
|
|
4483
|
-
// borderColor: 'transparent',
|
|
4484
|
-
// color,
|
|
4485
|
-
// };
|
|
4486
|
-
// }
|
|
4487
|
-
// if (this.config.outlined) {
|
|
4488
|
-
// return { width, background: 'transparent', borderColor: color, color };
|
|
4489
|
-
// }
|
|
4490
|
-
// return { width, background: color, borderColor: color, color: '#ffffff' };
|
|
4491
|
-
// }
|
|
4492
4485
|
buttonStyleComputed = computed(() => {
|
|
4493
4486
|
const color = DSX_PALETTE[this.config.colorToken];
|
|
4494
4487
|
const width = this.iconOnly() ? this.iconOnlyWidth() : this.width();
|
|
@@ -4518,17 +4511,13 @@ class DsxButtonComponent {
|
|
|
4518
4511
|
get buttonLabel() {
|
|
4519
4512
|
return this.iconOnly() ? undefined : this.label;
|
|
4520
4513
|
}
|
|
4521
|
-
// Y modifica el getter buttonSize
|
|
4522
4514
|
get buttonSize() {
|
|
4523
|
-
// Si el usuario especificó un tamaño, úsalo
|
|
4524
4515
|
if (this.size()) {
|
|
4525
4516
|
return this.size();
|
|
4526
4517
|
}
|
|
4527
|
-
// Si es compacto, siempre small
|
|
4528
4518
|
if (this.isCompactIconButton) {
|
|
4529
4519
|
return 'small';
|
|
4530
4520
|
}
|
|
4531
|
-
// Por defecto, small para todos los botones
|
|
4532
4521
|
return 'small';
|
|
4533
4522
|
}
|
|
4534
4523
|
get buttonStyleClass() {
|
|
@@ -4541,7 +4530,6 @@ class DsxButtonComponent {
|
|
|
4541
4530
|
const cfg = ACTION_CONFIG[this.type()];
|
|
4542
4531
|
const colorOverride = this.colorOverride();
|
|
4543
4532
|
const outlinedOverride = this.outlinedOverride();
|
|
4544
|
-
// Ya no se gestiona softDelete/return como modo, cada acción es independiente
|
|
4545
4533
|
return {
|
|
4546
4534
|
...cfg,
|
|
4547
4535
|
colorToken: colorOverride ?? cfg.colorToken,
|
|
@@ -4551,10 +4539,6 @@ class DsxButtonComponent {
|
|
|
4551
4539
|
get label() {
|
|
4552
4540
|
return this.labelOverride() ?? this.config.label;
|
|
4553
4541
|
}
|
|
4554
|
-
// get tooltip(): string {
|
|
4555
|
-
// this.log('getter tooltip');
|
|
4556
|
-
// return this.tooltipOverride() ?? this.config.tooltip;
|
|
4557
|
-
// }
|
|
4558
4542
|
tooltip = computed(() => {
|
|
4559
4543
|
return this.tooltipOverride() ?? this.config.tooltip;
|
|
4560
4544
|
}, ...(ngDevMode ? [{ debugName: "tooltip" }] : /* istanbul ignore next */ []));
|
|
@@ -4570,18 +4554,6 @@ class DsxButtonComponent {
|
|
|
4570
4554
|
}
|
|
4571
4555
|
return 'primeIcon' in this.config ? this.config.primeIcon : undefined;
|
|
4572
4556
|
}
|
|
4573
|
-
// showButton(): boolean {
|
|
4574
|
-
// this.time('showButton');
|
|
4575
|
-
// const result =
|
|
4576
|
-
// this.passesGlobalVisibilityPolicy() &&
|
|
4577
|
-
// this.passesParameterValidation() &&
|
|
4578
|
-
// this.passesIdValidation();
|
|
4579
|
-
// this.log('showButton()', {
|
|
4580
|
-
// result,
|
|
4581
|
-
// });
|
|
4582
|
-
// this.timeEnd('showButton');
|
|
4583
|
-
// return result;
|
|
4584
|
-
// }
|
|
4585
4557
|
visible = computed(() => {
|
|
4586
4558
|
return (this.passesGlobalVisibilityPolicy() &&
|
|
4587
4559
|
this.passesParameterValidation() &&
|
|
@@ -4606,45 +4578,44 @@ class DsxButtonComponent {
|
|
|
4606
4578
|
return true;
|
|
4607
4579
|
}
|
|
4608
4580
|
async onButtonClick() {
|
|
4609
|
-
this.
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4581
|
+
if (this._isDev && this.debug()) {
|
|
4582
|
+
console.group(`%c🖱️ [dsx-button:${this.type()}] Click`, 'color:#1976d2;font-weight:bold;font-size:13px');
|
|
4583
|
+
console.log('ID:', this.id());
|
|
4584
|
+
console.log('Target:', this.effectiveRouterLink());
|
|
4585
|
+
console.log('Disabled:', this.disabled());
|
|
4586
|
+
console.groupEnd();
|
|
4587
|
+
}
|
|
4613
4588
|
this.action.emit();
|
|
4614
4589
|
const target = this.effectiveRouterLink();
|
|
4615
4590
|
if (target == null) {
|
|
4616
|
-
if (this.type() === 'home') {
|
|
4617
|
-
console.warn(
|
|
4591
|
+
if (this.type() === 'home' && this._isDev && this.debug()) {
|
|
4592
|
+
console.warn(`[dsx-button:${this.type()}] No tiene routerLink ni routerPath configurado.`);
|
|
4618
4593
|
}
|
|
4619
4594
|
return;
|
|
4620
4595
|
}
|
|
4621
4596
|
try {
|
|
4622
4597
|
const start = performance.now();
|
|
4623
4598
|
const navigated = await this.navigateToTarget(target);
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
navigated
|
|
4627
|
-
|
|
4628
|
-
|
|
4599
|
+
const duration = Math.round(performance.now() - start);
|
|
4600
|
+
if (this._isDev && this.debug()) {
|
|
4601
|
+
const emoji = navigated ? '✅' : '⚠️';
|
|
4602
|
+
const color = navigated ? '#2e7d32' : '#ed6c02';
|
|
4603
|
+
console.log(`%c${emoji} Navegación ${navigated ? 'exitosa' : 'rechazada'} (${duration}ms)`, `color:${color};font-weight:bold`, target);
|
|
4604
|
+
}
|
|
4629
4605
|
if (!navigated) {
|
|
4630
|
-
// El router rechazó la navegación, lo más común durante hot-reload
|
|
4631
|
-
// es que un guard de autenticación aún no tiene el estado listo.
|
|
4632
|
-
// Se reintenta una vez tras un tick para darle tiempo al guard.
|
|
4633
4606
|
const retried = await new Promise((resolve) => setTimeout(async () => resolve(await this.navigateToTarget(target)), 0));
|
|
4634
|
-
if (!retried) {
|
|
4635
|
-
console.warn(
|
|
4636
|
-
'Si ocurre solo tras hot-reload, es normal que el guard rechace ' +
|
|
4637
|
-
'la primera navegación mientras los servicios de autenticación ' +
|
|
4638
|
-
'no han terminado de restaurar su estado.', target);
|
|
4607
|
+
if (!retried && this._isDev && this.debug()) {
|
|
4608
|
+
console.warn(`[dsx-button:${this.type()}] Navegación rechazada por route guard.`, target);
|
|
4639
4609
|
}
|
|
4640
4610
|
}
|
|
4641
4611
|
}
|
|
4642
4612
|
catch (error) {
|
|
4643
|
-
|
|
4613
|
+
if (this._isDev && this.debug()) {
|
|
4614
|
+
console.error(`[dsx-button:${this.type()}] Error al navegar:`, target, error);
|
|
4615
|
+
}
|
|
4644
4616
|
}
|
|
4645
4617
|
}
|
|
4646
4618
|
passesIdValidation() {
|
|
4647
|
-
this.log('passesIdValidation');
|
|
4648
4619
|
const explicitId = this.getNormalizedId();
|
|
4649
4620
|
const routeId = this.getIdFromNavigationTarget(this.effectiveRouterLink());
|
|
4650
4621
|
const id = explicitId ?? routeId;
|
|
@@ -4653,7 +4624,6 @@ class DsxButtonComponent {
|
|
|
4653
4624
|
this.type() === 'softDelete' ||
|
|
4654
4625
|
this.type() === 'restore' ||
|
|
4655
4626
|
this.type() === 'return';
|
|
4656
|
-
// Permite exigir que venga [id] cuando el caso de uso lo requiera.
|
|
4657
4627
|
if (this.requireIdInput() && typeof id !== 'number') {
|
|
4658
4628
|
return false;
|
|
4659
4629
|
}
|
|
@@ -4713,16 +4683,6 @@ class DsxButtonComponent {
|
|
|
4713
4683
|
}
|
|
4714
4684
|
return undefined;
|
|
4715
4685
|
}
|
|
4716
|
-
hasExplicitIdInput() {
|
|
4717
|
-
const value = this.id();
|
|
4718
|
-
if (value === null || value === undefined) {
|
|
4719
|
-
return false;
|
|
4720
|
-
}
|
|
4721
|
-
if (typeof value === 'string') {
|
|
4722
|
-
return value.trim() !== '';
|
|
4723
|
-
}
|
|
4724
|
-
return true;
|
|
4725
|
-
}
|
|
4726
4686
|
navigateToTarget(target) {
|
|
4727
4687
|
if (Array.isArray(target)) {
|
|
4728
4688
|
return this._router.navigate(target);
|
|
@@ -4734,41 +4694,154 @@ class DsxButtonComponent {
|
|
|
4734
4694
|
return this._router.navigateByUrl(target);
|
|
4735
4695
|
}
|
|
4736
4696
|
passesParameterValidation() {
|
|
4737
|
-
this.log('passesParameterValidation');
|
|
4738
4697
|
const parameterName = this.parameterName();
|
|
4739
|
-
// Si no se define un parámetro de seguridad, el botón se muestra sin restricción.
|
|
4740
4698
|
if (!parameterName) {
|
|
4741
4699
|
return true;
|
|
4742
4700
|
}
|
|
4743
|
-
// Mantiene una UX estable durante refresh de parámetros: evita parpadeo
|
|
4744
|
-
// y no evalúa permisos hasta que ParameterValuesService termine de cargar.
|
|
4745
4701
|
if (!this._paramService.loaded) {
|
|
4746
4702
|
return false;
|
|
4747
4703
|
}
|
|
4748
|
-
// Conserva el comportamiento original del servicio:
|
|
4749
|
-
// - retorna false si no cumple el valor esperado
|
|
4750
|
-
// - alerta si el parámetro no existe (ayuda a detectar typos)
|
|
4751
4704
|
return this._paramService.isParameterValue(parameterName, this.parameterExpectedValue(), this.parameterIndex());
|
|
4752
4705
|
}
|
|
4753
|
-
|
|
4754
|
-
|
|
4706
|
+
// ============ MÉTODOS DE DEBUG ============
|
|
4707
|
+
logFullState() {
|
|
4708
|
+
if (!this._isDev || !this.debug())
|
|
4755
4709
|
return;
|
|
4756
|
-
|
|
4710
|
+
const id = this.getNormalizedId();
|
|
4711
|
+
const routeId = this.getIdFromNavigationTarget(this.effectiveRouterLink());
|
|
4712
|
+
const paramName = this.parameterName();
|
|
4713
|
+
const paramLoaded = this._paramService.loaded;
|
|
4714
|
+
const paramValue = paramName && paramLoaded
|
|
4715
|
+
? this._paramService.getValue(paramName, this.parameterIndex())
|
|
4716
|
+
: undefined;
|
|
4717
|
+
console.group(`%c📊 [dsx-button:${this.type()}] Render #${this.renderCount}`, 'color:#1976d2;font-weight:bold;font-size:14px');
|
|
4718
|
+
// 1. Estado general
|
|
4719
|
+
console.log('🎯 Estado:', {
|
|
4720
|
+
visible: this.visible(),
|
|
4721
|
+
disabled: this.disabled(),
|
|
4722
|
+
type: this.type(),
|
|
4723
|
+
size: this.size(),
|
|
4724
|
+
iconOnly: this.iconOnly(),
|
|
4725
|
+
rounded: this.rounded(),
|
|
4726
|
+
isCompact: this.isCompactIconButton,
|
|
4727
|
+
});
|
|
4728
|
+
// 2. Configuración
|
|
4729
|
+
console.log('⚙️ Configuración:', {
|
|
4730
|
+
label: this.label,
|
|
4731
|
+
icon: this.icon,
|
|
4732
|
+
primeIcon: this.primeIcon,
|
|
4733
|
+
colorToken: this.config.colorToken,
|
|
4734
|
+
outlined: this.config.outlined,
|
|
4735
|
+
tooltip: this.tooltip(),
|
|
4736
|
+
});
|
|
4737
|
+
// 3. Router
|
|
4738
|
+
console.log('🔗 Router:', {
|
|
4739
|
+
routerLink: this.routerLink(),
|
|
4740
|
+
routerPath: this.routerPath(),
|
|
4741
|
+
resolvedRouterLink: this.resolvedRouterLink(),
|
|
4742
|
+
effectiveRouterLink: this.effectiveRouterLink(),
|
|
4743
|
+
});
|
|
4744
|
+
// 4. IDs
|
|
4745
|
+
console.log('🆔 IDs:', {
|
|
4746
|
+
inputId: this.id(),
|
|
4747
|
+
normalizedId: id,
|
|
4748
|
+
routeId: routeId,
|
|
4749
|
+
requireIdInput: this.requireIdInput(),
|
|
4750
|
+
requiresExistingRecord: this.requiresIdGreaterThanZero() ||
|
|
4751
|
+
['hardDelete', 'softDelete', 'restore', 'return'].includes(this.type()),
|
|
4752
|
+
});
|
|
4753
|
+
// 5. Parámetros
|
|
4754
|
+
if (paramName) {
|
|
4755
|
+
console.log('🔐 Parámetros:', {
|
|
4756
|
+
name: paramName,
|
|
4757
|
+
expected: this.parameterExpectedValue(),
|
|
4758
|
+
index: this.parameterIndex(),
|
|
4759
|
+
loaded: paramLoaded,
|
|
4760
|
+
actual: paramValue,
|
|
4761
|
+
matches: paramLoaded
|
|
4762
|
+
? paramValue === this.parameterExpectedValue()
|
|
4763
|
+
: 'No cargado',
|
|
4764
|
+
});
|
|
4765
|
+
}
|
|
4766
|
+
// 6. Overrides
|
|
4767
|
+
const overrides = {
|
|
4768
|
+
label: this.labelOverride(),
|
|
4769
|
+
tooltip: this.tooltipOverride(),
|
|
4770
|
+
icon: this.iconOverride(),
|
|
4771
|
+
primeIcon: this.primeIconOverride(),
|
|
4772
|
+
color: this.colorOverride(),
|
|
4773
|
+
outlined: this.outlinedOverride(),
|
|
4774
|
+
showValidate: this.showValidateButtonOverride(),
|
|
4775
|
+
showRestore: this.showRestoreButtonOverride(),
|
|
4776
|
+
};
|
|
4777
|
+
const activeOverrides = Object.entries(overrides)
|
|
4778
|
+
.filter(([_, value]) => value !== undefined)
|
|
4779
|
+
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
|
|
4780
|
+
if (Object.keys(activeOverrides).length > 0) {
|
|
4781
|
+
console.log('🔄 Overrides activos:', activeOverrides);
|
|
4782
|
+
}
|
|
4783
|
+
// 7. Validaciones
|
|
4784
|
+
console.log('✅ Validaciones:', {
|
|
4785
|
+
globalVisibility: this.passesGlobalVisibilityPolicy(),
|
|
4786
|
+
parameterValidation: this.passesParameterValidation(),
|
|
4787
|
+
idValidation: this.passesIdValidation(),
|
|
4788
|
+
visible: this.visible(),
|
|
4789
|
+
});
|
|
4790
|
+
// 8. Estilos
|
|
4791
|
+
console.log('🎨 Estilos:', {
|
|
4792
|
+
width: this.iconOnly() ? this.iconOnlyWidth() : this.width(),
|
|
4793
|
+
style: this.buttonStyleComputed(),
|
|
4794
|
+
styleClass: this.buttonStyleClass,
|
|
4795
|
+
size: this.buttonSize,
|
|
4796
|
+
});
|
|
4797
|
+
console.groupEnd();
|
|
4757
4798
|
}
|
|
4758
|
-
|
|
4759
|
-
if (!this.debug())
|
|
4799
|
+
logValidationIssues(validations) {
|
|
4800
|
+
if (!this._isDev || !this.debug())
|
|
4760
4801
|
return;
|
|
4761
|
-
|
|
4802
|
+
if (!validations.visible) {
|
|
4803
|
+
console.group(`%c❌ [dsx-button:${this.type()}] NO VISIBLE`, 'color:#d32f2f;font-weight:bold;font-size:13px');
|
|
4804
|
+
console.log('Validaciones fallidas:', {
|
|
4805
|
+
globalVisibility: validations.global,
|
|
4806
|
+
parameterValidation: validations.parameter,
|
|
4807
|
+
idValidation: validations.id,
|
|
4808
|
+
});
|
|
4809
|
+
if (!validations.id) {
|
|
4810
|
+
const id = this.getNormalizedId();
|
|
4811
|
+
const routeId = this.getIdFromNavigationTarget(this.effectiveRouterLink());
|
|
4812
|
+
console.warn('⚠️ Problema con ID:', {
|
|
4813
|
+
explicitId: this.id(),
|
|
4814
|
+
normalizedId: id,
|
|
4815
|
+
routeId: routeId,
|
|
4816
|
+
requireIdInput: this.requireIdInput(),
|
|
4817
|
+
requiresExistingRecord: this.requiresIdGreaterThanZero() ||
|
|
4818
|
+
['hardDelete', 'softDelete', 'restore', 'return'].includes(this.type()),
|
|
4819
|
+
});
|
|
4820
|
+
}
|
|
4821
|
+
if (!validations.parameter && this.parameterName()) {
|
|
4822
|
+
console.warn('⚠️ Problema con parámetro:', {
|
|
4823
|
+
name: this.parameterName(),
|
|
4824
|
+
expected: this.parameterExpectedValue(),
|
|
4825
|
+
index: this.parameterIndex(),
|
|
4826
|
+
loaded: this._paramService.loaded,
|
|
4827
|
+
actualValue: this._paramService.loaded
|
|
4828
|
+
? this._paramService.getValue(this.parameterName(), this.parameterIndex())
|
|
4829
|
+
: 'No cargado',
|
|
4830
|
+
});
|
|
4831
|
+
}
|
|
4832
|
+
console.groupEnd();
|
|
4833
|
+
}
|
|
4762
4834
|
}
|
|
4763
|
-
|
|
4764
|
-
|
|
4835
|
+
// Métodos de log existentes mejorados
|
|
4836
|
+
log(message, data) {
|
|
4837
|
+
if (!this._isDev || !this.debug())
|
|
4765
4838
|
return;
|
|
4766
|
-
console.
|
|
4839
|
+
console.log(`%c[dsx-button:${this.type()}] ${message}`, 'color:#1976d2;font-weight:bold', data ?? '');
|
|
4767
4840
|
}
|
|
4768
|
-
|
|
4769
|
-
if (!this.debug())
|
|
4841
|
+
warn(message, data) {
|
|
4842
|
+
if (!this._isDev || !this.debug())
|
|
4770
4843
|
return;
|
|
4771
|
-
console.
|
|
4844
|
+
console.warn(`[dsx-button:${this.type()}] ${message}`, data ?? '');
|
|
4772
4845
|
}
|
|
4773
4846
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4774
4847
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DsxButtonComponent, isStandalone: true, selector: "dsx-button", inputs: { debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, routerPath: { classPropertyName: "routerPath", publicName: "routerPath", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "iconOnly", isSignal: true, isRequired: false, transformFunction: null }, iconOnlyWidth: { classPropertyName: "iconOnlyWidth", publicName: "iconOnlyWidth", isSignal: true, isRequired: false, transformFunction: null }, raised: { classPropertyName: "raised", publicName: "raised", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, parameterName: { classPropertyName: "parameterName", publicName: "parameterName", isSignal: true, isRequired: false, transformFunction: null }, parameterExpectedValue: { classPropertyName: "parameterExpectedValue", publicName: "parameterExpectedValue", isSignal: true, isRequired: false, transformFunction: null }, parameterIndex: { classPropertyName: "parameterIndex", publicName: "parameterIndex", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, requireIdInput: { classPropertyName: "requireIdInput", publicName: "requireIdInput", isSignal: true, isRequired: false, transformFunction: null }, requiresIdGreaterThanZero: { classPropertyName: "requiresIdGreaterThanZero", publicName: "requiresIdGreaterThanZero", isSignal: true, isRequired: false, transformFunction: null }, labelOverride: { classPropertyName: "labelOverride", publicName: "labelOverride", isSignal: true, isRequired: false, transformFunction: null }, tooltipOverride: { classPropertyName: "tooltipOverride", publicName: "tooltipOverride", isSignal: true, isRequired: false, transformFunction: null }, iconOverride: { classPropertyName: "iconOverride", publicName: "iconOverride", isSignal: true, isRequired: false, transformFunction: null }, primeIconOverride: { classPropertyName: "primeIconOverride", publicName: "primeIconOverride", isSignal: true, isRequired: false, transformFunction: null }, colorOverride: { classPropertyName: "colorOverride", publicName: "colorOverride", isSignal: true, isRequired: false, transformFunction: null }, outlinedOverride: { classPropertyName: "outlinedOverride", publicName: "outlinedOverride", isSignal: true, isRequired: false, transformFunction: null }, showValidateButtonOverride: { classPropertyName: "showValidateButtonOverride", publicName: "showValidateButtonOverride", isSignal: true, isRequired: false, transformFunction: null }, showRestoreButtonOverride: { classPropertyName: "showRestoreButtonOverride", publicName: "showRestoreButtonOverride", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, host: { properties: { "class.dsx-button-compact-host": "this.compactHostClass" } }, ngImport: i0, template: "<!--Bot\u00F3n con las configuraciones din\u00E1micas para CRUD -->\r\n@if (visible()) {\r\n<p-button\r\n [label]=\"buttonLabel\"\r\n [style]=\"buttonStyleComputed()\"\r\n [styleClass]=\"buttonStyleClass\"\r\n [pTooltip]=\"tooltip()\"\r\n [tooltipDisabled]=\"disabled()\"\r\n tooltipPosition=\"top\"\r\n [rounded]=\"rounded()\"\r\n [text]=\"isCompactIconButton\"\r\n [size]=\"buttonSize\"\r\n severity=\"secondary\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"iconOnly() ? primeIcon : undefined\"\r\n (click)=\"onButtonClick()\"\r\n>\r\n @if (!iconOnly()) {\r\n <span class=\"material-symbols-outlined dsx-button-icon-small\"\r\n >{{ materialIcon }}</span\r\n >\r\n }\r\n</p-button>\r\n}\r\n", styles: [".dsx-button-icon-small{font-size:1.25rem;vertical-align:middle}:host{display:inline-flex;margin-bottom:.25rem}:host.dsx-button-compact-host{margin-bottom:0}:host ::ng-deep .p-button.dsx-button-compact{min-height:2rem;padding-block:.2rem}:host ::ng-deep .p-button .material-symbols-outlined.p-button-icon{line-height:1;vertical-align:middle}:host ::ng-deep .p-button.dsx-button-compact .p-button-icon{margin:0;font-size:1.2rem;line-height:1}:host ::ng-deep .p-button.dsx-button-compact .material-symbols-outlined.p-button-icon{font-variation-settings:\"FILL\" 1,\"wght\" 500,\"GRAD\" 0,\"opsz\" 24}:host ::ng-deep .p-button:disabled,:host ::ng-deep .p-button.p-disabled{cursor:not-allowed!important;opacity:.45!important;filter:grayscale(.2) saturate(.75)}:host ::ng-deep .p-button:disabled *,:host ::ng-deep .p-button.p-disabled *{cursor:not-allowed!important}.p-button.dsx-button-outlined-neutral,.p-button.dsx-button-outlined-info{background:#f5f7fa!important;border:1.5px solid #0066cc!important;color:#06c!important}.p-button.dsx-button-outlined-neutral:disabled,.p-button.dsx-button-outlined-info:disabled{background:#f5f7fa!important;border:1.5px solid #b0bec5!important;color:#b0bec5!important}\n"], dependencies: [{ 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: "ngmodule", type: TooltipModule }, { kind: "directive", type: i1$3.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"] }] });
|
|
@@ -4873,7 +4946,7 @@ const ACTION_CONFIG = {
|
|
|
4873
4946
|
view: {
|
|
4874
4947
|
label: 'Visualizar',
|
|
4875
4948
|
icon: 'visibility',
|
|
4876
|
-
primeIcon: '
|
|
4949
|
+
primeIcon: 'fa-solid fa-eye',
|
|
4877
4950
|
colorToken: 'view',
|
|
4878
4951
|
tooltip: 'Vista previa',
|
|
4879
4952
|
},
|
|
@@ -4906,7 +4979,6 @@ class FileComponent {
|
|
|
4906
4979
|
existingFileName = input(null, ...(ngDevMode ? [{ debugName: "existingFileName" }] : /* istanbul ignore next */ []));
|
|
4907
4980
|
overrideFileName = input(null, ...(ngDevMode ? [{ debugName: "overrideFileName" }] : /* istanbul ignore next */ []));
|
|
4908
4981
|
debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
|
|
4909
|
-
required = input(true, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
4910
4982
|
accept = input('.xls,.xlsx', ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
|
|
4911
4983
|
pTooltipOverride = input('Seleccione un archivo', ...(ngDevMode ? [{ debugName: "pTooltipOverride" }] : /* istanbul ignore next */ []));
|
|
4912
4984
|
tooltipPositionOverride = input('top', ...(ngDevMode ? [{ debugName: "tooltipPositionOverride" }] : /* istanbul ignore next */ []));
|
|
@@ -4918,6 +4990,7 @@ class FileComponent {
|
|
|
4918
4990
|
// Signals de estado
|
|
4919
4991
|
isReplacing = signal(false, ...(ngDevMode ? [{ debugName: "isReplacing" }] : /* istanbul ignore next */ []));
|
|
4920
4992
|
disabled = signal(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
4993
|
+
errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
|
|
4921
4994
|
isViewActive = signal(false, ...(ngDevMode ? [{ debugName: "isViewActive" }] : /* istanbul ignore next */ []));
|
|
4922
4995
|
value = null;
|
|
4923
4996
|
touched = false;
|
|
@@ -4925,7 +4998,31 @@ class FileComponent {
|
|
|
4925
4998
|
// Dependencias
|
|
4926
4999
|
cdr = inject(ChangeDetectorRef);
|
|
4927
5000
|
fileUpload = viewChild('fileUpload', ...(ngDevMode ? [{ debugName: "fileUpload" }] : /* istanbul ignore next */ []));
|
|
4928
|
-
//
|
|
5001
|
+
// --- Para el manejo de errores del formulario y debug ---
|
|
5002
|
+
_control = null;
|
|
5003
|
+
// Computed para el control de errores
|
|
5004
|
+
errorControl = computed(() => {
|
|
5005
|
+
return this._control;
|
|
5006
|
+
}, ...(ngDevMode ? [{ debugName: "errorControl" }] : /* istanbul ignore next */ []));
|
|
5007
|
+
// Signal para errores del control
|
|
5008
|
+
controlErrors = signal(null, ...(ngDevMode ? [{ debugName: "controlErrors" }] : /* istanbul ignore next */ []));
|
|
5009
|
+
// Estado del control
|
|
5010
|
+
controlValid = signal(true, ...(ngDevMode ? [{ debugName: "controlValid" }] : /* istanbul ignore next */ []));
|
|
5011
|
+
controlTouched = signal(false, ...(ngDevMode ? [{ debugName: "controlTouched" }] : /* istanbul ignore next */ []));
|
|
5012
|
+
controlDirty = signal(false, ...(ngDevMode ? [{ debugName: "controlDirty" }] : /* istanbul ignore next */ []));
|
|
5013
|
+
// --- Detección de modo desarrollo ---
|
|
5014
|
+
get isDevMode() {
|
|
5015
|
+
return (typeof ngDevMode !== 'undefined' &&
|
|
5016
|
+
ngDevMode !== null &&
|
|
5017
|
+
ngDevMode !== false);
|
|
5018
|
+
}
|
|
5019
|
+
// ✅ Método para detectar si el control es requerido
|
|
5020
|
+
isRequired() {
|
|
5021
|
+
if (!this._control)
|
|
5022
|
+
return false;
|
|
5023
|
+
return this._control.errors?.['required'] ?? false;
|
|
5024
|
+
}
|
|
5025
|
+
// Computed properties
|
|
4929
5026
|
hasExistingFile = computed(() => {
|
|
4930
5027
|
if (!this.isViewActive())
|
|
4931
5028
|
return false;
|
|
@@ -4961,14 +5058,27 @@ class FileComponent {
|
|
|
4961
5058
|
return true;
|
|
4962
5059
|
return false;
|
|
4963
5060
|
}, ...(ngDevMode ? [{ debugName: "isReplaceButtonDisabled" }] : /* istanbul ignore next */ []));
|
|
4964
|
-
|
|
5061
|
+
allowedFileTypeLabel = computed(() => {
|
|
5062
|
+
if (!this.isViewActive())
|
|
5063
|
+
return 'archivo';
|
|
5064
|
+
const acceptStr = this.accept();
|
|
5065
|
+
if (!acceptStr)
|
|
5066
|
+
return 'archivo';
|
|
5067
|
+
const extensions = acceptStr.split(',').map((ext) => ext.trim());
|
|
5068
|
+
const cleanExtensions = extensions
|
|
5069
|
+
.map((ext) => ext.replace('.', '').toUpperCase())
|
|
5070
|
+
.filter((ext) => ext.length > 0);
|
|
5071
|
+
if (cleanExtensions.length === 0)
|
|
5072
|
+
return 'archivo';
|
|
5073
|
+
if (cleanExtensions.length === 1)
|
|
5074
|
+
return cleanExtensions[0];
|
|
5075
|
+
return cleanExtensions.join(' o ');
|
|
5076
|
+
}, ...(ngDevMode ? [{ debugName: "allowedFileTypeLabel" }] : /* istanbul ignore next */ []));
|
|
5077
|
+
// --- Constructor ---
|
|
4965
5078
|
constructor() {
|
|
4966
|
-
// Efecto 1: Re-evaluar validación cuando cambie el estado relevante
|
|
4967
5079
|
effect(() => {
|
|
4968
|
-
// Solo ejecutar si la vista está activa
|
|
4969
5080
|
if (!this.isViewActive())
|
|
4970
5081
|
return;
|
|
4971
|
-
// "Tocar" estas signals para que el efecto dependa de ellas
|
|
4972
5082
|
const hasFile = this.hasExistingFile();
|
|
4973
5083
|
const replacing = this.isReplacing();
|
|
4974
5084
|
const fileName = this.existingFileName();
|
|
@@ -4979,14 +5089,12 @@ class FileComponent {
|
|
|
4979
5089
|
fileName,
|
|
4980
5090
|
});
|
|
4981
5091
|
if (this.control) {
|
|
4982
|
-
// Usar untracked para no crear bucles infinitos
|
|
4983
5092
|
untracked(() => {
|
|
4984
5093
|
this.control?.updateValueAndValidity({ emitEvent: false });
|
|
4985
5094
|
});
|
|
4986
5095
|
}
|
|
4987
5096
|
this.cdr.detectChanges();
|
|
4988
5097
|
});
|
|
4989
|
-
// Efecto 2: Cancelar reemplazo si el componente se deshabilita
|
|
4990
5098
|
effect(() => {
|
|
4991
5099
|
if (!this.isViewActive())
|
|
4992
5100
|
return;
|
|
@@ -5000,104 +5108,341 @@ class FileComponent {
|
|
|
5000
5108
|
});
|
|
5001
5109
|
}
|
|
5002
5110
|
ngAfterViewInit() {
|
|
5003
|
-
// Activar el componente después de que la vista esté lista
|
|
5004
5111
|
setTimeout(() => {
|
|
5005
5112
|
this.isViewActive.set(true);
|
|
5006
|
-
if (this.debug())
|
|
5007
|
-
console.log('[FileComponent] View activated');
|
|
5113
|
+
if (this.debug()) {
|
|
5114
|
+
console.log('[FileComponent] ✅ View activated');
|
|
5115
|
+
console.log('[FileComponent] 📋 Configuración actual:', {
|
|
5116
|
+
accept: this.accept(),
|
|
5117
|
+
maxFileSize: this.maxFileSize(),
|
|
5118
|
+
debug: this.debug(),
|
|
5119
|
+
isDevMode: this.isDevMode,
|
|
5120
|
+
});
|
|
5121
|
+
}
|
|
5008
5122
|
});
|
|
5009
5123
|
}
|
|
5010
5124
|
ngOnDestroy() {
|
|
5011
5125
|
this.isViewActive.set(false);
|
|
5012
5126
|
if (this.debug())
|
|
5013
|
-
console.log('[FileComponent] Component destroyed');
|
|
5127
|
+
console.log('[FileComponent] 🗑️ Component destroyed');
|
|
5128
|
+
}
|
|
5129
|
+
// --- Método para validar archivo manualmente ---
|
|
5130
|
+
validateFile(file) {
|
|
5131
|
+
if (this.debug()) {
|
|
5132
|
+
this.log('🔍 validateFile - Iniciando validación manual', {
|
|
5133
|
+
fileName: file.name,
|
|
5134
|
+
fileSize: file.size,
|
|
5135
|
+
fileType: file.type,
|
|
5136
|
+
accept: this.accept(),
|
|
5137
|
+
maxSize: this.maxFileSize(),
|
|
5138
|
+
});
|
|
5139
|
+
}
|
|
5140
|
+
// Validar tipo de archivo
|
|
5141
|
+
const acceptStr = this.accept();
|
|
5142
|
+
if (acceptStr) {
|
|
5143
|
+
const acceptedExtensions = acceptStr
|
|
5144
|
+
.split(',')
|
|
5145
|
+
.map((ext) => ext.trim().toLowerCase());
|
|
5146
|
+
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
|
|
5147
|
+
if (this.debug()) {
|
|
5148
|
+
this.log('🔍 validateFile - Verificando extensión', {
|
|
5149
|
+
fileExtension,
|
|
5150
|
+
acceptedExtensions,
|
|
5151
|
+
matches: acceptedExtensions.includes(fileExtension),
|
|
5152
|
+
});
|
|
5153
|
+
}
|
|
5154
|
+
if (!acceptedExtensions.includes(fileExtension)) {
|
|
5155
|
+
const detail = this.invalidDetail().replace('{0}', this.allowedFileTypeLabel());
|
|
5156
|
+
const message = `${this.invalidSummary()} ${detail}`;
|
|
5157
|
+
if (this.debug())
|
|
5158
|
+
this.log('❌ validateFile - Tipo de archivo no permitido', {
|
|
5159
|
+
message,
|
|
5160
|
+
});
|
|
5161
|
+
return { valid: false, error: message };
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
// Validar tamaño
|
|
5165
|
+
const maxSizeBytes = this.maxFileSize() * 1024 * 1024;
|
|
5166
|
+
if (this.debug()) {
|
|
5167
|
+
this.log('🔍 validateFile - Verificando tamaño', {
|
|
5168
|
+
fileSize: file.size,
|
|
5169
|
+
maxSizeBytes,
|
|
5170
|
+
maxSizeMB: this.maxFileSize(),
|
|
5171
|
+
});
|
|
5172
|
+
}
|
|
5173
|
+
if (file.size > maxSizeBytes) {
|
|
5174
|
+
const detail = this.invalidSizeDetail().replace('{0}', `${this.maxFileSize()} MB`);
|
|
5175
|
+
const message = `${this.invalidSizeSummary()} ${detail}`;
|
|
5176
|
+
if (this.debug())
|
|
5177
|
+
this.log('❌ validateFile - Tamaño excedido', { message });
|
|
5178
|
+
return { valid: false, error: message };
|
|
5179
|
+
}
|
|
5180
|
+
if (this.debug())
|
|
5181
|
+
this.log('✅ validateFile - Archivo válido');
|
|
5182
|
+
return { valid: true };
|
|
5183
|
+
}
|
|
5184
|
+
// --- Métodos para el manejo de errores ---
|
|
5185
|
+
showError() {
|
|
5186
|
+
return !!(this._control && this._control.invalid && this._control.touched);
|
|
5187
|
+
}
|
|
5188
|
+
// Actualizar estado del control
|
|
5189
|
+
updateControlState() {
|
|
5190
|
+
if (this._control) {
|
|
5191
|
+
this.controlValid.set(this._control.valid);
|
|
5192
|
+
this.controlTouched.set(this._control.touched);
|
|
5193
|
+
this.controlDirty.set(this._control.dirty);
|
|
5194
|
+
this.controlErrors.set(this._control.errors);
|
|
5195
|
+
}
|
|
5014
5196
|
}
|
|
5015
|
-
//
|
|
5197
|
+
// Forzar recarga (para debug)
|
|
5198
|
+
forceReload() {
|
|
5199
|
+
if (this.debug()) {
|
|
5200
|
+
console.log('[FileComponent] 🔄 Force reload');
|
|
5201
|
+
if (this._control) {
|
|
5202
|
+
this._control.updateValueAndValidity();
|
|
5203
|
+
this.updateControlState();
|
|
5204
|
+
}
|
|
5205
|
+
this.cdr.detectChanges();
|
|
5206
|
+
}
|
|
5207
|
+
}
|
|
5208
|
+
// --- Métodos públicos ---
|
|
5016
5209
|
onSelect(event) {
|
|
5017
|
-
|
|
5210
|
+
const startTime = performance.now();
|
|
5211
|
+
if (this.debug()) {
|
|
5212
|
+
this.log('📤 onSelect - Evento recibido', {
|
|
5213
|
+
files: event.files,
|
|
5214
|
+
currentFiles: event.currentFiles,
|
|
5215
|
+
isFileUploadEnabled: this.isFileUploadEnabled(),
|
|
5216
|
+
});
|
|
5217
|
+
}
|
|
5218
|
+
if (!this.isViewActive()) {
|
|
5219
|
+
if (this.debug())
|
|
5220
|
+
this.log('⚠️ Componente no activo, ignorando selección');
|
|
5018
5221
|
return;
|
|
5222
|
+
}
|
|
5019
5223
|
if (!this.isFileUploadEnabled()) {
|
|
5020
|
-
console.warn('File upload is disabled');
|
|
5224
|
+
console.warn('[FileComponent] ⚠️ File upload is disabled');
|
|
5021
5225
|
this.fileUpload()?.clear();
|
|
5022
5226
|
return;
|
|
5023
5227
|
}
|
|
5024
5228
|
const file = event.files?.[0] ?? null;
|
|
5025
|
-
if (
|
|
5026
|
-
this.
|
|
5027
|
-
|
|
5028
|
-
|
|
5229
|
+
if (!file) {
|
|
5230
|
+
if (this.debug())
|
|
5231
|
+
this.log('⚠️ No se seleccionó ningún archivo');
|
|
5232
|
+
return;
|
|
5233
|
+
}
|
|
5234
|
+
if (this.debug()) {
|
|
5235
|
+
this.log('📄 Archivo seleccionado', {
|
|
5236
|
+
name: file.name,
|
|
5237
|
+
size: file.size,
|
|
5238
|
+
type: file.type,
|
|
5239
|
+
sizeFormatted: this.formatFileSize(file.size),
|
|
5240
|
+
});
|
|
5241
|
+
}
|
|
5242
|
+
// Validar archivo manualmente
|
|
5243
|
+
const validation = this.validateFile(file);
|
|
5244
|
+
if (!validation.valid) {
|
|
5245
|
+
// Archivo inválido - mostrar error y limpiar
|
|
5246
|
+
this.errorMessage.set(validation.error || 'Archivo inválido');
|
|
5247
|
+
this.fileUpload()?.clear();
|
|
5248
|
+
this.value = null;
|
|
5249
|
+
this.onChange(null);
|
|
5250
|
+
if (this.debug()) {
|
|
5251
|
+
this.log('❌ Archivo inválido - Limpiando selección', {
|
|
5252
|
+
error: validation.error,
|
|
5253
|
+
});
|
|
5254
|
+
}
|
|
5255
|
+
// Auto-ocultar error después de 5 segundos
|
|
5256
|
+
setTimeout(() => {
|
|
5257
|
+
this.errorMessage.set(null);
|
|
5258
|
+
if (this.debug())
|
|
5259
|
+
this.log('⏰ Mensaje de error auto-ocultado después de 5s');
|
|
5260
|
+
}, 5000);
|
|
5261
|
+
// Actualizar estado del control
|
|
5262
|
+
this.updateControlState();
|
|
5263
|
+
return;
|
|
5264
|
+
}
|
|
5265
|
+
// Archivo válido
|
|
5266
|
+
this.errorMessage.set(null);
|
|
5267
|
+
this.value = file;
|
|
5268
|
+
this.onChange(file);
|
|
5029
5269
|
this.markAsTouched();
|
|
5030
5270
|
if (this.isReplacing() && file) {
|
|
5031
5271
|
this.isReplacing.set(false);
|
|
5272
|
+
if (this.debug())
|
|
5273
|
+
this.log('🔄 Reemplazo completado');
|
|
5032
5274
|
}
|
|
5033
5275
|
if (this.control) {
|
|
5034
5276
|
this.control.updateValueAndValidity();
|
|
5035
5277
|
}
|
|
5278
|
+
// Actualizar estado del control
|
|
5279
|
+
this.updateControlState();
|
|
5280
|
+
const endTime = performance.now();
|
|
5281
|
+
if (this.debug()) {
|
|
5282
|
+
this.log(`⏱️ onSelect completado en ${(endTime - startTime).toFixed(2)}ms`);
|
|
5283
|
+
}
|
|
5036
5284
|
}
|
|
5037
5285
|
clear() {
|
|
5286
|
+
if (this.debug())
|
|
5287
|
+
this.log('🧹 clear() - Limpiando componente');
|
|
5038
5288
|
if (!this.isViewActive())
|
|
5039
5289
|
return;
|
|
5040
5290
|
this.fileUpload()?.clear();
|
|
5041
5291
|
this.value = null;
|
|
5292
|
+
this.errorMessage.set(null);
|
|
5042
5293
|
this.onChange(null);
|
|
5043
5294
|
this.markAsTouched();
|
|
5044
5295
|
if (this.control)
|
|
5045
5296
|
this.control.updateValueAndValidity();
|
|
5297
|
+
this.updateControlState();
|
|
5298
|
+
if (this.debug())
|
|
5299
|
+
this.log('✅ Limpieza completada');
|
|
5046
5300
|
}
|
|
5047
|
-
|
|
5301
|
+
clearFile() {
|
|
5302
|
+
if (this.debug())
|
|
5303
|
+
this.log('🧹 clearFile() - Limpiando archivo seleccionado');
|
|
5048
5304
|
if (!this.isViewActive())
|
|
5049
5305
|
return;
|
|
5306
|
+
this.fileUpload()?.clear();
|
|
5307
|
+
this.value = null;
|
|
5308
|
+
this.errorMessage.set(null);
|
|
5309
|
+
this.onChange(null);
|
|
5310
|
+
this.markAsTouched();
|
|
5311
|
+
if (this.control)
|
|
5312
|
+
this.control.updateValueAndValidity();
|
|
5313
|
+
this.updateControlState();
|
|
5050
5314
|
if (this.debug())
|
|
5051
|
-
this.log('
|
|
5315
|
+
this.log('✅ Archivo limpiado');
|
|
5316
|
+
}
|
|
5317
|
+
writeValue(value) {
|
|
5318
|
+
if (!this.isViewActive())
|
|
5319
|
+
return;
|
|
5320
|
+
if (this.debug()) {
|
|
5321
|
+
this.log('✍️ writeValue', {
|
|
5322
|
+
value: value?.name || null,
|
|
5323
|
+
size: value?.size ? this.formatFileSize(value.size) : null,
|
|
5324
|
+
});
|
|
5325
|
+
}
|
|
5052
5326
|
this.value = value;
|
|
5053
5327
|
}
|
|
5054
5328
|
registerOnChange(fn) {
|
|
5329
|
+
if (this.debug())
|
|
5330
|
+
this.log('📝 registerOnChange');
|
|
5055
5331
|
this.onChange = fn;
|
|
5056
5332
|
}
|
|
5057
5333
|
registerOnTouched(fn) {
|
|
5334
|
+
if (this.debug())
|
|
5335
|
+
this.log('📝 registerOnTouched');
|
|
5058
5336
|
this.onTouched = fn;
|
|
5059
5337
|
}
|
|
5060
5338
|
setDisabledState(isDisabled) {
|
|
5061
5339
|
if (!this.isViewActive())
|
|
5062
5340
|
return;
|
|
5341
|
+
if (this.debug()) {
|
|
5342
|
+
this.log('🔒 setDisabledState', {
|
|
5343
|
+
isDisabled,
|
|
5344
|
+
previousState: this.disabled(),
|
|
5345
|
+
});
|
|
5346
|
+
}
|
|
5063
5347
|
this.disabled.set(isDisabled);
|
|
5064
5348
|
}
|
|
5065
5349
|
startReplace() {
|
|
5350
|
+
if (this.debug())
|
|
5351
|
+
this.log('🔄 startReplace - Iniciando reemplazo');
|
|
5066
5352
|
if (!this.isViewActive())
|
|
5067
5353
|
return;
|
|
5068
|
-
if (this.disabled())
|
|
5354
|
+
if (this.disabled()) {
|
|
5355
|
+
if (this.debug())
|
|
5356
|
+
this.log('⛔ Reemplazo cancelado - componente deshabilitado');
|
|
5069
5357
|
return;
|
|
5358
|
+
}
|
|
5070
5359
|
this.fileUpload()?.clear();
|
|
5071
5360
|
this.value = null;
|
|
5361
|
+
this.errorMessage.set(null);
|
|
5072
5362
|
this.onChange(null);
|
|
5073
5363
|
this.isReplacing.set(true);
|
|
5074
5364
|
if (this.debug())
|
|
5075
|
-
this.log('
|
|
5365
|
+
this.log('✅ Reemplazo iniciado');
|
|
5076
5366
|
}
|
|
5077
5367
|
cancelReplace() {
|
|
5368
|
+
if (this.debug())
|
|
5369
|
+
this.log('❌ cancelReplace - Cancelando reemplazo');
|
|
5078
5370
|
if (!this.isViewActive())
|
|
5079
5371
|
return;
|
|
5080
5372
|
this.isReplacing.set(false);
|
|
5081
5373
|
this.fileUpload()?.clear();
|
|
5082
5374
|
this.value = null;
|
|
5375
|
+
this.errorMessage.set(null);
|
|
5083
5376
|
this.onChange(null);
|
|
5084
5377
|
this.markAsTouched();
|
|
5085
5378
|
if (this.debug())
|
|
5086
|
-
this.log('
|
|
5379
|
+
this.log('✅ Reemplazo cancelado');
|
|
5380
|
+
}
|
|
5381
|
+
choose(event, chooseCallback) {
|
|
5382
|
+
if (this.debug())
|
|
5383
|
+
this.log('🔍 choose - Abriendo selector de archivos');
|
|
5384
|
+
chooseCallback();
|
|
5385
|
+
}
|
|
5386
|
+
formatFileSize(bytes) {
|
|
5387
|
+
if (!bytes)
|
|
5388
|
+
return '';
|
|
5389
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
5390
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
5391
|
+
const value = (bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0);
|
|
5392
|
+
return `${value} ${sizes[i]}`;
|
|
5087
5393
|
}
|
|
5088
5394
|
// --- Validación ---
|
|
5089
5395
|
validate(control) {
|
|
5396
|
+
const startTime = performance.now();
|
|
5397
|
+
this._control = control;
|
|
5090
5398
|
this.control = control;
|
|
5091
|
-
if (!this.isViewActive())
|
|
5399
|
+
if (!this.isViewActive()) {
|
|
5400
|
+
if (this.debug())
|
|
5401
|
+
this.log('⚠️ validate - Componente no activo');
|
|
5092
5402
|
return null;
|
|
5093
|
-
|
|
5403
|
+
}
|
|
5404
|
+
if (this.debug()) {
|
|
5405
|
+
this.log('🔍 validate - Ejecutando validación', {
|
|
5406
|
+
isRequired: this.isRequired(),
|
|
5407
|
+
hasExistingFile: this.hasExistingFile(),
|
|
5408
|
+
isReplacing: this.isReplacing(),
|
|
5409
|
+
hasValue: !!this.value,
|
|
5410
|
+
valueName: this.value?.name || null,
|
|
5411
|
+
errorMessage: this.errorMessage(),
|
|
5412
|
+
});
|
|
5413
|
+
}
|
|
5414
|
+
// Actualizar estado del control
|
|
5415
|
+
this.updateControlState();
|
|
5416
|
+
// Si hay un mensaje de error, consideramos que la validación falla
|
|
5417
|
+
if (this.errorMessage()) {
|
|
5418
|
+
if (this.debug())
|
|
5419
|
+
this.log('❌ validate - Error: Hay un mensaje de error activo');
|
|
5420
|
+
return { invalidFile: true };
|
|
5421
|
+
}
|
|
5422
|
+
if (!this.isRequired()) {
|
|
5423
|
+
if (this.debug())
|
|
5424
|
+
this.log('✅ validate - No requerido, pasa validación');
|
|
5094
5425
|
return null;
|
|
5095
|
-
|
|
5426
|
+
}
|
|
5427
|
+
if (this.hasExistingFile()) {
|
|
5428
|
+
if (this.debug())
|
|
5429
|
+
this.log('✅ validate - Archivo existente, pasa validación');
|
|
5096
5430
|
return null;
|
|
5097
|
-
|
|
5431
|
+
}
|
|
5432
|
+
if (this.isReplacing() && !this.value) {
|
|
5433
|
+
if (this.debug())
|
|
5434
|
+
this.log('❌ validate - Error: Reemplazando pero sin archivo');
|
|
5098
5435
|
return { required: true };
|
|
5099
|
-
|
|
5436
|
+
}
|
|
5437
|
+
if (!this.hasExistingFile() && !this.value) {
|
|
5438
|
+
if (this.debug())
|
|
5439
|
+
this.log('❌ validate - Error: Sin archivo seleccionado');
|
|
5100
5440
|
return { required: true };
|
|
5441
|
+
}
|
|
5442
|
+
const endTime = performance.now();
|
|
5443
|
+
if (this.debug()) {
|
|
5444
|
+
this.log(`✅ validate - Validación exitosa en ${(endTime - startTime).toFixed(2)}ms`);
|
|
5445
|
+
}
|
|
5101
5446
|
return null;
|
|
5102
5447
|
}
|
|
5103
5448
|
// --- Privados ---
|
|
@@ -5107,13 +5452,15 @@ class FileComponent {
|
|
|
5107
5452
|
if (!this.touched) {
|
|
5108
5453
|
this.touched = true;
|
|
5109
5454
|
this.onTouched();
|
|
5455
|
+
if (this.debug())
|
|
5456
|
+
this.log('👆 markAsTouched - Componente marcado como touched');
|
|
5110
5457
|
}
|
|
5111
5458
|
}
|
|
5112
5459
|
log(method, data) {
|
|
5113
5460
|
console.log(`[FileComponent] ${method}`, data || '');
|
|
5114
5461
|
}
|
|
5115
5462
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5116
|
-
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 },
|
|
5463
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: FileComponent, isStandalone: true, selector: "dsx-file-upload", inputs: { existingFile: { classPropertyName: "existingFile", publicName: "existingFile", isSignal: true, isRequired: false, transformFunction: null }, existingFileName: { classPropertyName: "existingFileName", publicName: "existingFileName", isSignal: true, isRequired: false, transformFunction: null }, overrideFileName: { classPropertyName: "overrideFileName", publicName: "overrideFileName", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, pTooltipOverride: { classPropertyName: "pTooltipOverride", publicName: "pTooltipOverride", isSignal: true, isRequired: false, transformFunction: null }, tooltipPositionOverride: { classPropertyName: "tooltipPositionOverride", publicName: "tooltipPositionOverride", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, invalidSummary: { classPropertyName: "invalidSummary", publicName: "invalidSummary", isSignal: true, isRequired: false, transformFunction: null }, invalidDetail: { classPropertyName: "invalidDetail", publicName: "invalidDetail", isSignal: true, isRequired: false, transformFunction: null }, invalidSizeSummary: { classPropertyName: "invalidSizeSummary", publicName: "invalidSizeSummary", isSignal: true, isRequired: false, transformFunction: null }, invalidSizeDetail: { classPropertyName: "invalidSizeDetail", publicName: "invalidSizeDetail", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
5117
5464
|
{
|
|
5118
5465
|
provide: NG_VALUE_ACCESSOR,
|
|
5119
5466
|
useExisting: forwardRef(() => FileComponent),
|
|
@@ -5124,11 +5471,11 @@ class FileComponent {
|
|
|
5124
5471
|
useExisting: forwardRef(() => FileComponent),
|
|
5125
5472
|
multi: true,
|
|
5126
5473
|
},
|
|
5127
|
-
], viewQueries: [{ propertyName: "fileUpload", first: true, predicate: ["fileUpload"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (showExistingFile() && !existingFile()) {\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"file-name\">\r\n \uD83D\uDCC4 {{ overrideFileName() ?? existingFileName() }}\r\n </span>\r\n\r\n <p-button\r\n icon=\"pi pi-cloud-upload\"\r\n [rounded]=\"true\"\r\n severity=\"info\"\r\n (click)=\"startReplace()\"\r\n [disabled]=\"isReplaceButtonDisabled()\"\r\n pTooltip=\"Reemplazar archivo\"\r\n tooltipPosition=\"top\"\r\n />\r\n </div>\r\n} @else {\r\n <div class=\"flex items-center gap-2\">\r\n <p-fileUpload\r\n #fileUpload\r\n mode=\"basic\"\r\n [accept]=\"accept()\"\r\n [maxFileSize]=\"maxFileSize() * 1024 * 1024\"\r\n [invalidFileTypeMessageSummary]=\"invalidSummary()\"\r\n [invalidFileTypeMessageDetail]=\"invalidDetail()\"\r\n [invalidFileSizeMessageSummary]=\"invalidSizeSummary()\"\r\n [invalidFileSizeMessageDetail]=\"invalidSizeDetail()\"\r\n (onSelect)=\"onSelect($event)\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n [pTooltip]=\"pTooltipOverride()\"\r\n [tooltipPosition]=\"tooltipPositionOverride()\"\r\n />\r\n\r\n @if (isReplacing() || existingFile()) {\r\n <p-button\r\n icon=\"pi pi-times\"\r\n [rounded]=\"true\"\r\n severity=\"secondary\"\r\n (click)=\"cancelReplace()\"\r\n [disabled]=\"disabled()\"\r\n pTooltip=\"Cancelar reemplazo\"\r\n tooltipPosition=\"top\"\r\n />\r\n }\r\n </div>\r\n}\r\n", styles: [".file-name{display:inline-block;font-size:clamp(.85rem,1vw + .5rem,1.4rem);font-weight:500;color:#2c3e50;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}\n"], dependencies: [{ kind: "component", type: FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }] });
|
|
5474
|
+
], viewQueries: [{ propertyName: "fileUpload", first: true, predicate: ["fileUpload"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (showExistingFile() && !existingFile()) {\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"file-name\">\r\n \uD83D\uDCC4 {{ overrideFileName() ?? existingFileName() }}\r\n </span>\r\n\r\n <p-button\r\n icon=\"pi pi-cloud-upload\"\r\n [rounded]=\"true\"\r\n severity=\"info\"\r\n (click)=\"startReplace()\"\r\n [disabled]=\"isReplaceButtonDisabled()\"\r\n pTooltip=\"Reemplazar archivo\"\r\n tooltipPosition=\"top\"\r\n />\r\n </div>\r\n} @else {\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"flex items-center gap-2\">\r\n <p-fileUpload\r\n fluid\r\n #fileUpload\r\n chooseIcon=\"fa-solid fa-file-arrow-up\"\r\n [accept]=\"accept()\"\r\n [maxFileSize]=\"maxFileSize() * 1024 * 1024\"\r\n [invalidFileTypeMessageSummary]=\"invalidSummary()\"\r\n [invalidFileTypeMessageDetail]=\"invalidDetail()\"\r\n [invalidFileSizeMessageSummary]=\"invalidSizeSummary()\"\r\n [invalidFileSizeMessageDetail]=\"invalidSizeDetail()\"\r\n (onSelect)=\"onSelect($event)\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n [pTooltip]=\"`${pTooltipOverride()} ${allowedFileTypeLabel()}`\"\r\n [tooltipPosition]=\"tooltipPositionOverride()\"\r\n >\r\n <ng-template\r\n #header\r\n let-files\r\n let-chooseCallback=\"chooseCallback\"\r\n let-clearCallback=\"clearCallback\"\r\n >\r\n <!-- Mostrar el bot\u00F3n de upload SOLO si NO hay archivo seleccionado -->\r\n @if (!files || files.length === 0) {\r\n <p-button\r\n (onClick)=\"choose($event, chooseCallback)\"\r\n icon=\"fa-solid fa-cloud-arrow-up\"\r\n [rounded]=\"true\"\r\n [outlined]=\"true\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n />\r\n }\r\n\r\n <span class=\"file-upload-placeholder\">\r\n <i class=\"fa-regular fa-file-lines placeholder-icon\"></i>\r\n <span class=\"placeholder-text\">\r\n @if (files && files.length > 0) {\r\n <span class=\"file-selected\">\r\n <i class=\"fa-regular fa-file-pdf file-icon\"></i>\r\n {{ files[0]?.name }}\r\n <span class=\"file-size\"\r\n >({{ formatFileSize(files[0]?.size) }})</span\r\n >\r\n </span>\r\n } @else if (allowedFileTypeLabel() !== \"archivo\") {\r\n <span class=\"empty-warning\">Ning\u00FAn</span>\r\n <span class=\"file-type empty-warning-type\">{{\r\n allowedFileTypeLabel()\r\n }}</span>\r\n <span class=\"empty-warning\">seleccionado</span>\r\n } @else {\r\n <span class=\"empty-warning\">Ning\u00FAn archivo seleccionado</span>\r\n }\r\n </span>\r\n </span>\r\n\r\n <!-- Mostrar bot\u00F3n de limpiar SOLO si hay archivo seleccionado -->\r\n @if (files && files.length > 0) {\r\n <p-button\r\n icon=\"fa-solid fa-xmark\"\r\n [rounded]=\"true\"\r\n [outlined]=\"true\"\r\n (onClick)=\"clearFile()\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n styleClass=\"clear-button\"\r\n />\r\n }\r\n </ng-template>\r\n <!-- Ocultar la vista previa por defecto -->\r\n <ng-template #content></ng-template>\r\n <ng-template #file></ng-template>\r\n </p-fileUpload>\r\n\r\n @if (isReplacing() || existingFile()) {\r\n <p-button\r\n icon=\"pi pi-times\"\r\n [rounded]=\"true\"\r\n severity=\"secondary\"\r\n (click)=\"cancelReplace()\"\r\n [disabled]=\"disabled()\"\r\n pTooltip=\"Cancelar reemplazo\"\r\n tooltipPosition=\"top\"\r\n />\r\n }\r\n </div>\r\n\r\n <!-- Mensajes de error (validaci\u00F3n de PrimeNG) -->\r\n @if (errorMessage()) {\r\n <div class=\"file-error-message\">\r\n <i class=\"pi pi-exclamation-circle\"></i>\r\n {{ errorMessage() }}\r\n </div>\r\n }\r\n\r\n <!-- Mensaje de error del formulario (dsx-message-error) -->\r\n @if (showError() && errorControl()) {\r\n <dsx-message-error [control]=\"errorControl()\" />\r\n }\r\n </div>\r\n}\r\n\r\n<!-- Panel de Debug -->\r\n@if (debug() && isDevMode) {\r\n <div class=\"debug-panel\">\r\n <div class=\"debug-header\">\r\n <div class=\"debug-info\">\r\n <strong>\uD83D\uDD0D Debug File Upload:</strong>\r\n <span class=\"debug-item\"\r\n >Archivo: {{ value ? value.name : \"Ninguno\" }}</span\r\n >\r\n <span class=\"debug-item\"\r\n >Tama\u00F1o: {{ value ? formatFileSize(value.size) : \"-\" }}</span\r\n >\r\n <span class=\"debug-item\">Tipo: {{ value ? value.type : \"-\" }}</span>\r\n <span\r\n class=\"debug-item\"\r\n [style.color]=\"controlValid() ? '#10b981' : '#ef4444'\"\r\n >\r\n {{ controlValid() ? \"\u2705 V\u00E1lido\" : \"\u274C Inv\u00E1lido\" }}\r\n </span>\r\n <!-- Cambiar esta l\u00EDnea en el panel de debug -->\r\n <span class=\"debug-item\"\r\n >Required: {{ isRequired() ? \"\u2705\" : \"\u274C\" }}</span\r\n >\r\n <span class=\"debug-item\"\r\n >Reemplazando: {{ isReplacing() ? \"\u2705\" : \"\u274C\" }}</span\r\n >\r\n </div>\r\n <div class=\"debug-actions\">\r\n <button (click)=\"forceReload()\" class=\"debug-btn\" type=\"button\">\r\n \uD83D\uDD04 Recargar\r\n </button>\r\n <button (click)=\"clearFile()\" class=\"debug-btn\" type=\"button\">\r\n \uD83E\uDDF9 Limpiar\r\n </button>\r\n </div>\r\n </div>\r\n @if (errorMessage()) {\r\n <div class=\"debug-error\">\r\n <strong>\u26A0\uFE0F Error activo:</strong> {{ errorMessage() }}\r\n </div>\r\n }\r\n @if (controlErrors()) {\r\n <div class=\"debug-errors\">\r\n <strong>\u274C Errores del formulario:</strong>\r\n <pre>{{ controlErrors() | json }}</pre>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".file-name{display:inline-block;font-size:clamp(.85rem,1vw + .5rem,1.4rem);font-weight:500;color:#2c3e50;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}::ng-deep .p-fileupload .p-fileupload-header{display:flex!important;align-items:center!important;gap:.75rem!important;padding:0!important;border:none!important;background:transparent!important}::ng-deep .p-fileupload .p-fileupload-header .p-button{background:transparent!important;border:2px dashed #d1d5db!important;border-radius:8px!important;padding:.5rem 1rem!important;margin:.5rem!important;min-height:40px!important;min-width:40px!important;transition:all .25s ease!important;color:#6b7280!important}::ng-deep .p-fileupload .p-fileupload-header .p-button:not(:disabled):hover{border-color:#3b82f6!important;background:#eff6ff!important;box-shadow:0 0 0 3px #3b82f61a!important;color:#3b82f6!important}::ng-deep .p-fileupload .p-fileupload-header .p-button:disabled{opacity:.5!important;cursor:not-allowed!important;border-color:#e5e7eb!important;color:#9ca3af!important}::ng-deep .p-fileupload .p-fileupload-header .p-button .p-button-icon{font-size:1.2rem!important;transition:color .25s ease!important}.file-upload-placeholder{display:flex;align-items:center;gap:.75rem;pointer-events:none;white-space:nowrap;padding:0 .5rem}.placeholder-icon{font-size:1.35rem;color:#94a3b8;transition:all .3s ease}.placeholder-text{color:#94a3b8;font-size:.875rem;font-weight:400;transition:all .3s ease;letter-spacing:.01em}.placeholder-text .file-type{color:#64748b;font-size:.8rem;font-weight:600;transition:all .3s ease;letter-spacing:.03em;text-transform:uppercase;background:#f1f5f9;padding:.1rem .4rem;border-radius:4px;margin:0 .1rem}.placeholder-text .empty-warning{color:#d97706;font-weight:500;transition:color .3s ease}.placeholder-text .file-type.empty-warning-type{color:#b45309;background:#fef3c7;font-weight:600}.file-selected{display:flex;align-items:center;gap:.5rem;color:#1f2937;font-weight:500;font-size:.85rem}.file-selected .file-icon{font-size:1.2rem;color:#bb1589}.file-selected .file-size{color:#9ca3af;font-weight:400;font-size:.75rem;margin-left:.25rem}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-icon{color:#3b82f6}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-text{color:#1f2937}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .file-type{color:#2563eb;background:#dbeafe}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-text .empty-warning{color:#2563eb}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .file-type.empty-warning-type{color:#2563eb;background:#dbeafe}::ng-deep .p-fileupload .p-fileupload-buttonbar{display:none!important}::ng-deep .p-fileupload .p-fileupload-content{padding:0!important;border:none!important;background:transparent!important}::ng-deep .p-button.clear-button{background:transparent!important;border:none!important;border-radius:8px!important;padding:.5rem!important;min-width:40px!important;height:40px!important;transition:all .25s ease!important;display:flex!important;align-items:center!important;justify-content:center!important;box-shadow:none!important}::ng-deep .p-fileupload .p-fileupload-header .p-button.clear-button:not(:disabled):hover{background:none!important;border:none!important;box-shadow:none!important;transform:scale(1.05)!important}::ng-deep .p-button.clear-button .p-button-icon{color:#6b7280!important;font-size:1.1rem!important;transition:all .25s ease!important}::ng-deep .p-button.clear-button:not(:disabled):hover .p-button-icon{color:#ef4444!important}::ng-deep .p-button.clear-button:disabled{opacity:.5!important;cursor:not-allowed!important}.file-error-message{display:flex;align-items:center;gap:.5rem;color:#dc2626;font-size:.875rem;font-weight:400;padding:.25rem .75rem;background:#fef2f2;border-radius:6px;border:1px solid #fecaca;animation:slideDown .3s ease}.file-error-message i{font-size:1rem;color:#dc2626;flex-shrink:0}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.debug-panel{margin-top:8px;font-size:11px;color:#374151;background:#f8fafc;padding:8px 12px;border-radius:6px;font-family:monospace;border:1px solid #e2e8f0;width:100%}.debug-header{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap}.debug-info{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.debug-item{font-size:11px;color:#475569}.debug-item strong{color:#1e293b}.debug-actions{display:flex;gap:6px}.debug-btn{font-size:10px;padding:2px 10px;border-radius:4px;border:1px solid #cbd5e1;background:#fff;cursor:pointer;transition:all .2s ease;color:#475569;font-family:monospace}.debug-btn:hover{background:#f1f5f9;border-color:#94a3b8}.debug-error,.debug-errors{margin-top:6px;padding:4px 8px;background:#fef2f2;border-radius:4px;color:#dc2626;font-size:11px;border:1px solid #fecaca}.debug-errors pre{margin:4px 0 0;font-size:10px;color:#991b1b;white-space:pre-wrap;word-break:break-all}@media(max-width:640px){.placeholder-icon{font-size:1.1rem!important}.placeholder-text{font-size:.75rem!important}.placeholder-text .file-type{font-size:.7rem!important;padding:.05rem .3rem!important}.file-selected{font-size:.75rem!important}.file-selected .file-icon{font-size:1rem!important}.file-selected .file-size{font-size:.65rem!important}::ng-deep .p-fileupload .p-fileupload-header .p-button{padding:.4rem .75rem!important;min-height:34px!important;min-width:34px!important}::ng-deep .p-button.clear-button{min-width:34px!important;height:34px!important}.file-error-message{font-size:.75rem!important;padding:.2rem .5rem!important}.debug-panel{font-size:10px;padding:6px 8px}.debug-item{font-size:10px}.debug-btn{font-size:9px;padding:2px 6px}}\n"], dependencies: [{ kind: "component", type: FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "directive", type: Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: AppMessageErrorComponent, selector: "dsx-message-error", inputs: ["control", "form", "debugMode"] }, { kind: "pipe", type: JsonPipe, name: "json" }] });
|
|
5128
5475
|
}
|
|
5129
5476
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FileComponent, decorators: [{
|
|
5130
5477
|
type: Component,
|
|
5131
|
-
args: [{ selector: 'dsx-file-upload', imports: [FileUpload, Tooltip, Button], providers: [
|
|
5478
|
+
args: [{ selector: 'dsx-file-upload', imports: [FileUpload, Tooltip, Button, JsonPipe, AppMessageErrorComponent], providers: [
|
|
5132
5479
|
{
|
|
5133
5480
|
provide: NG_VALUE_ACCESSOR,
|
|
5134
5481
|
useExisting: forwardRef(() => FileComponent),
|
|
@@ -5139,119 +5486,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
5139
5486
|
useExisting: forwardRef(() => FileComponent),
|
|
5140
5487
|
multi: true,
|
|
5141
5488
|
},
|
|
5142
|
-
], 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"] }]
|
|
5143
|
-
}], 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 }] }],
|
|
5144
|
-
|
|
5145
|
-
class JsonHighlightPipe {
|
|
5146
|
-
/**
|
|
5147
|
-
* Stringifica el JSON manteniendo arrays en una línea para una vista más compacta
|
|
5148
|
-
*/
|
|
5149
|
-
compactStringify(obj, indent = 0) {
|
|
5150
|
-
const spaces = ' '.repeat(indent);
|
|
5151
|
-
if (obj === null) {
|
|
5152
|
-
return 'null';
|
|
5153
|
-
}
|
|
5154
|
-
if (typeof obj !== 'object') {
|
|
5155
|
-
if (typeof obj === 'string') {
|
|
5156
|
-
return JSON.stringify(obj);
|
|
5157
|
-
}
|
|
5158
|
-
return String(obj);
|
|
5159
|
-
}
|
|
5160
|
-
if (obj instanceof Date) {
|
|
5161
|
-
return JSON.stringify(obj.toISOString());
|
|
5162
|
-
}
|
|
5163
|
-
if (Array.isArray(obj)) {
|
|
5164
|
-
// Mantener arrays en una línea
|
|
5165
|
-
const items = obj.map((item) => {
|
|
5166
|
-
if (item instanceof Date) {
|
|
5167
|
-
return JSON.stringify(item.toISOString());
|
|
5168
|
-
}
|
|
5169
|
-
if (typeof item === 'object' && item !== null) {
|
|
5170
|
-
return this.compactStringify(item, indent);
|
|
5171
|
-
}
|
|
5172
|
-
return typeof item === 'string'
|
|
5173
|
-
? JSON.stringify(item)
|
|
5174
|
-
: item === null
|
|
5175
|
-
? 'null'
|
|
5176
|
-
: String(item);
|
|
5177
|
-
});
|
|
5178
|
-
return `[${items.join(', ')}]`;
|
|
5179
|
-
}
|
|
5180
|
-
// Para objetos, mantener la indentación
|
|
5181
|
-
const keys = Object.keys(obj);
|
|
5182
|
-
if (keys.length === 0) {
|
|
5183
|
-
return '{}';
|
|
5184
|
-
}
|
|
5185
|
-
const innerIndent = indent + 2;
|
|
5186
|
-
const innerSpaces = ' '.repeat(innerIndent);
|
|
5187
|
-
const lines = keys.map((key) => {
|
|
5188
|
-
const value = obj[key];
|
|
5189
|
-
const stringified = value instanceof Date
|
|
5190
|
-
? JSON.stringify(value.toISOString())
|
|
5191
|
-
: typeof value === 'object' && value !== null
|
|
5192
|
-
? this.compactStringify(value, innerIndent)
|
|
5193
|
-
: typeof value === 'string'
|
|
5194
|
-
? JSON.stringify(value)
|
|
5195
|
-
: value === null
|
|
5196
|
-
? 'null'
|
|
5197
|
-
: String(value);
|
|
5198
|
-
return `${innerSpaces}"${key}": ${stringified}`;
|
|
5199
|
-
});
|
|
5200
|
-
return `{\n${lines.join(',\n')}\n${spaces}}`;
|
|
5201
|
-
}
|
|
5202
|
-
instanceId = Math.random().toString(36).slice(2, 8);
|
|
5203
|
-
executions = 0;
|
|
5204
|
-
transform(value) {
|
|
5205
|
-
if (isDevMode()) {
|
|
5206
|
-
this.executions++;
|
|
5207
|
-
if (this.executions % 10 === 0) {
|
|
5208
|
-
console.log(`[jsonHighlight:${this.instanceId}] ${this.executions}`);
|
|
5209
|
-
}
|
|
5210
|
-
}
|
|
5211
|
-
if (!value) {
|
|
5212
|
-
return '';
|
|
5213
|
-
}
|
|
5214
|
-
const json = this.compactStringify(value);
|
|
5215
|
-
// Detecta fechas ISO: "YYYY-MM-DDTHH:MM:SS.mmmZ"
|
|
5216
|
-
const ISO_DATE_RE = /^"(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}\.\d{3}Z)"$/;
|
|
5217
|
-
// Aplica estilo y colores para diferenciar claves, valores, booleanos, números, etc.
|
|
5218
|
-
return json
|
|
5219
|
-
.replace(/&/g, '&')
|
|
5220
|
-
.replace(/</g, '<')
|
|
5221
|
-
.replace(/>/g, '>')
|
|
5222
|
-
.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?)/g, (match) => {
|
|
5223
|
-
// Si es una clave (tiene dos puntos al final)
|
|
5224
|
-
if (/:$/.test(match)) {
|
|
5225
|
-
return `<span class="json-key">${match}</span>`;
|
|
5226
|
-
}
|
|
5227
|
-
// Detectar fechas ISO (YYYY-MM-DDTHH:MM:SS.mmmZ)
|
|
5228
|
-
const isoMatch = match.match(ISO_DATE_RE);
|
|
5229
|
-
if (isoMatch) {
|
|
5230
|
-
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>`;
|
|
5231
|
-
}
|
|
5232
|
-
// Si es un string (no tiene dos puntos)
|
|
5233
|
-
return `<span class="json-string">${match}</span>`;
|
|
5234
|
-
})
|
|
5235
|
-
.replace(/\b(true|false|null)\b/g, (match) => {
|
|
5236
|
-
if (match === 'null') {
|
|
5237
|
-
return '<span class="json-null">null</span>';
|
|
5238
|
-
}
|
|
5239
|
-
return `<span class="json-boolean">${match}</span>`;
|
|
5240
|
-
})
|
|
5241
|
-
.replace(/\b(\d+\.?\d*)\b/g, '<span class="json-number">$1</span>')
|
|
5242
|
-
.replace(/[{}\[\]]/g, '<span class="json-bracket">$&</span>')
|
|
5243
|
-
.replace(/,/g, '<span class="json-comma">$&</span>')
|
|
5244
|
-
.replace(/:/g, '<span class="json-colon">$&</span>');
|
|
5245
|
-
}
|
|
5246
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
5247
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, isStandalone: true, name: "jsonHighlight" });
|
|
5248
|
-
}
|
|
5249
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, decorators: [{
|
|
5250
|
-
type: Pipe,
|
|
5251
|
-
args: [{
|
|
5252
|
-
name: 'jsonHighlight',
|
|
5253
|
-
}]
|
|
5254
|
-
}] });
|
|
5489
|
+
], template: "@if (showExistingFile() && !existingFile()) {\r\n <div class=\"flex items-center gap-2\">\r\n <span class=\"file-name\">\r\n \uD83D\uDCC4 {{ overrideFileName() ?? existingFileName() }}\r\n </span>\r\n\r\n <p-button\r\n icon=\"pi pi-cloud-upload\"\r\n [rounded]=\"true\"\r\n severity=\"info\"\r\n (click)=\"startReplace()\"\r\n [disabled]=\"isReplaceButtonDisabled()\"\r\n pTooltip=\"Reemplazar archivo\"\r\n tooltipPosition=\"top\"\r\n />\r\n </div>\r\n} @else {\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"flex items-center gap-2\">\r\n <p-fileUpload\r\n fluid\r\n #fileUpload\r\n chooseIcon=\"fa-solid fa-file-arrow-up\"\r\n [accept]=\"accept()\"\r\n [maxFileSize]=\"maxFileSize() * 1024 * 1024\"\r\n [invalidFileTypeMessageSummary]=\"invalidSummary()\"\r\n [invalidFileTypeMessageDetail]=\"invalidDetail()\"\r\n [invalidFileSizeMessageSummary]=\"invalidSizeSummary()\"\r\n [invalidFileSizeMessageDetail]=\"invalidSizeDetail()\"\r\n (onSelect)=\"onSelect($event)\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n [pTooltip]=\"`${pTooltipOverride()} ${allowedFileTypeLabel()}`\"\r\n [tooltipPosition]=\"tooltipPositionOverride()\"\r\n >\r\n <ng-template\r\n #header\r\n let-files\r\n let-chooseCallback=\"chooseCallback\"\r\n let-clearCallback=\"clearCallback\"\r\n >\r\n <!-- Mostrar el bot\u00F3n de upload SOLO si NO hay archivo seleccionado -->\r\n @if (!files || files.length === 0) {\r\n <p-button\r\n (onClick)=\"choose($event, chooseCallback)\"\r\n icon=\"fa-solid fa-cloud-arrow-up\"\r\n [rounded]=\"true\"\r\n [outlined]=\"true\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n />\r\n }\r\n\r\n <span class=\"file-upload-placeholder\">\r\n <i class=\"fa-regular fa-file-lines placeholder-icon\"></i>\r\n <span class=\"placeholder-text\">\r\n @if (files && files.length > 0) {\r\n <span class=\"file-selected\">\r\n <i class=\"fa-regular fa-file-pdf file-icon\"></i>\r\n {{ files[0]?.name }}\r\n <span class=\"file-size\"\r\n >({{ formatFileSize(files[0]?.size) }})</span\r\n >\r\n </span>\r\n } @else if (allowedFileTypeLabel() !== \"archivo\") {\r\n <span class=\"empty-warning\">Ning\u00FAn</span>\r\n <span class=\"file-type empty-warning-type\">{{\r\n allowedFileTypeLabel()\r\n }}</span>\r\n <span class=\"empty-warning\">seleccionado</span>\r\n } @else {\r\n <span class=\"empty-warning\">Ning\u00FAn archivo seleccionado</span>\r\n }\r\n </span>\r\n </span>\r\n\r\n <!-- Mostrar bot\u00F3n de limpiar SOLO si hay archivo seleccionado -->\r\n @if (files && files.length > 0) {\r\n <p-button\r\n icon=\"fa-solid fa-xmark\"\r\n [rounded]=\"true\"\r\n [outlined]=\"true\"\r\n (onClick)=\"clearFile()\"\r\n [disabled]=\"!isFileUploadEnabled()\"\r\n styleClass=\"clear-button\"\r\n />\r\n }\r\n </ng-template>\r\n <!-- Ocultar la vista previa por defecto -->\r\n <ng-template #content></ng-template>\r\n <ng-template #file></ng-template>\r\n </p-fileUpload>\r\n\r\n @if (isReplacing() || existingFile()) {\r\n <p-button\r\n icon=\"pi pi-times\"\r\n [rounded]=\"true\"\r\n severity=\"secondary\"\r\n (click)=\"cancelReplace()\"\r\n [disabled]=\"disabled()\"\r\n pTooltip=\"Cancelar reemplazo\"\r\n tooltipPosition=\"top\"\r\n />\r\n }\r\n </div>\r\n\r\n <!-- Mensajes de error (validaci\u00F3n de PrimeNG) -->\r\n @if (errorMessage()) {\r\n <div class=\"file-error-message\">\r\n <i class=\"pi pi-exclamation-circle\"></i>\r\n {{ errorMessage() }}\r\n </div>\r\n }\r\n\r\n <!-- Mensaje de error del formulario (dsx-message-error) -->\r\n @if (showError() && errorControl()) {\r\n <dsx-message-error [control]=\"errorControl()\" />\r\n }\r\n </div>\r\n}\r\n\r\n<!-- Panel de Debug -->\r\n@if (debug() && isDevMode) {\r\n <div class=\"debug-panel\">\r\n <div class=\"debug-header\">\r\n <div class=\"debug-info\">\r\n <strong>\uD83D\uDD0D Debug File Upload:</strong>\r\n <span class=\"debug-item\"\r\n >Archivo: {{ value ? value.name : \"Ninguno\" }}</span\r\n >\r\n <span class=\"debug-item\"\r\n >Tama\u00F1o: {{ value ? formatFileSize(value.size) : \"-\" }}</span\r\n >\r\n <span class=\"debug-item\">Tipo: {{ value ? value.type : \"-\" }}</span>\r\n <span\r\n class=\"debug-item\"\r\n [style.color]=\"controlValid() ? '#10b981' : '#ef4444'\"\r\n >\r\n {{ controlValid() ? \"\u2705 V\u00E1lido\" : \"\u274C Inv\u00E1lido\" }}\r\n </span>\r\n <!-- Cambiar esta l\u00EDnea en el panel de debug -->\r\n <span class=\"debug-item\"\r\n >Required: {{ isRequired() ? \"\u2705\" : \"\u274C\" }}</span\r\n >\r\n <span class=\"debug-item\"\r\n >Reemplazando: {{ isReplacing() ? \"\u2705\" : \"\u274C\" }}</span\r\n >\r\n </div>\r\n <div class=\"debug-actions\">\r\n <button (click)=\"forceReload()\" class=\"debug-btn\" type=\"button\">\r\n \uD83D\uDD04 Recargar\r\n </button>\r\n <button (click)=\"clearFile()\" class=\"debug-btn\" type=\"button\">\r\n \uD83E\uDDF9 Limpiar\r\n </button>\r\n </div>\r\n </div>\r\n @if (errorMessage()) {\r\n <div class=\"debug-error\">\r\n <strong>\u26A0\uFE0F Error activo:</strong> {{ errorMessage() }}\r\n </div>\r\n }\r\n @if (controlErrors()) {\r\n <div class=\"debug-errors\">\r\n <strong>\u274C Errores del formulario:</strong>\r\n <pre>{{ controlErrors() | json }}</pre>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".file-name{display:inline-block;font-size:clamp(.85rem,1vw + .5rem,1.4rem);font-weight:500;color:#2c3e50;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}::ng-deep .p-fileupload .p-fileupload-header{display:flex!important;align-items:center!important;gap:.75rem!important;padding:0!important;border:none!important;background:transparent!important}::ng-deep .p-fileupload .p-fileupload-header .p-button{background:transparent!important;border:2px dashed #d1d5db!important;border-radius:8px!important;padding:.5rem 1rem!important;margin:.5rem!important;min-height:40px!important;min-width:40px!important;transition:all .25s ease!important;color:#6b7280!important}::ng-deep .p-fileupload .p-fileupload-header .p-button:not(:disabled):hover{border-color:#3b82f6!important;background:#eff6ff!important;box-shadow:0 0 0 3px #3b82f61a!important;color:#3b82f6!important}::ng-deep .p-fileupload .p-fileupload-header .p-button:disabled{opacity:.5!important;cursor:not-allowed!important;border-color:#e5e7eb!important;color:#9ca3af!important}::ng-deep .p-fileupload .p-fileupload-header .p-button .p-button-icon{font-size:1.2rem!important;transition:color .25s ease!important}.file-upload-placeholder{display:flex;align-items:center;gap:.75rem;pointer-events:none;white-space:nowrap;padding:0 .5rem}.placeholder-icon{font-size:1.35rem;color:#94a3b8;transition:all .3s ease}.placeholder-text{color:#94a3b8;font-size:.875rem;font-weight:400;transition:all .3s ease;letter-spacing:.01em}.placeholder-text .file-type{color:#64748b;font-size:.8rem;font-weight:600;transition:all .3s ease;letter-spacing:.03em;text-transform:uppercase;background:#f1f5f9;padding:.1rem .4rem;border-radius:4px;margin:0 .1rem}.placeholder-text .empty-warning{color:#d97706;font-weight:500;transition:color .3s ease}.placeholder-text .file-type.empty-warning-type{color:#b45309;background:#fef3c7;font-weight:600}.file-selected{display:flex;align-items:center;gap:.5rem;color:#1f2937;font-weight:500;font-size:.85rem}.file-selected .file-icon{font-size:1.2rem;color:#bb1589}.file-selected .file-size{color:#9ca3af;font-weight:400;font-size:.75rem;margin-left:.25rem}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-icon{color:#3b82f6}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-text{color:#1f2937}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .file-type{color:#2563eb;background:#dbeafe}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .placeholder-text .empty-warning{color:#2563eb}::ng-deep .p-fileupload .p-fileupload-header:has(.p-button:not(:disabled):hover) .file-type.empty-warning-type{color:#2563eb;background:#dbeafe}::ng-deep .p-fileupload .p-fileupload-buttonbar{display:none!important}::ng-deep .p-fileupload .p-fileupload-content{padding:0!important;border:none!important;background:transparent!important}::ng-deep .p-button.clear-button{background:transparent!important;border:none!important;border-radius:8px!important;padding:.5rem!important;min-width:40px!important;height:40px!important;transition:all .25s ease!important;display:flex!important;align-items:center!important;justify-content:center!important;box-shadow:none!important}::ng-deep .p-fileupload .p-fileupload-header .p-button.clear-button:not(:disabled):hover{background:none!important;border:none!important;box-shadow:none!important;transform:scale(1.05)!important}::ng-deep .p-button.clear-button .p-button-icon{color:#6b7280!important;font-size:1.1rem!important;transition:all .25s ease!important}::ng-deep .p-button.clear-button:not(:disabled):hover .p-button-icon{color:#ef4444!important}::ng-deep .p-button.clear-button:disabled{opacity:.5!important;cursor:not-allowed!important}.file-error-message{display:flex;align-items:center;gap:.5rem;color:#dc2626;font-size:.875rem;font-weight:400;padding:.25rem .75rem;background:#fef2f2;border-radius:6px;border:1px solid #fecaca;animation:slideDown .3s ease}.file-error-message i{font-size:1rem;color:#dc2626;flex-shrink:0}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.debug-panel{margin-top:8px;font-size:11px;color:#374151;background:#f8fafc;padding:8px 12px;border-radius:6px;font-family:monospace;border:1px solid #e2e8f0;width:100%}.debug-header{display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap}.debug-info{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.debug-item{font-size:11px;color:#475569}.debug-item strong{color:#1e293b}.debug-actions{display:flex;gap:6px}.debug-btn{font-size:10px;padding:2px 10px;border-radius:4px;border:1px solid #cbd5e1;background:#fff;cursor:pointer;transition:all .2s ease;color:#475569;font-family:monospace}.debug-btn:hover{background:#f1f5f9;border-color:#94a3b8}.debug-error,.debug-errors{margin-top:6px;padding:4px 8px;background:#fef2f2;border-radius:4px;color:#dc2626;font-size:11px;border:1px solid #fecaca}.debug-errors pre{margin:4px 0 0;font-size:10px;color:#991b1b;white-space:pre-wrap;word-break:break-all}@media(max-width:640px){.placeholder-icon{font-size:1.1rem!important}.placeholder-text{font-size:.75rem!important}.placeholder-text .file-type{font-size:.7rem!important;padding:.05rem .3rem!important}.file-selected{font-size:.75rem!important}.file-selected .file-icon{font-size:1rem!important}.file-selected .file-size{font-size:.65rem!important}::ng-deep .p-fileupload .p-fileupload-header .p-button{padding:.4rem .75rem!important;min-height:34px!important;min-width:34px!important}::ng-deep .p-button.clear-button{min-width:34px!important;height:34px!important}.file-error-message{font-size:.75rem!important;padding:.2rem .5rem!important}.debug-panel{font-size:10px;padding:6px 8px}.debug-item{font-size:10px}.debug-btn{font-size:9px;padding:2px 6px}}\n"] }]
|
|
5490
|
+
}], ctorParameters: () => [], propDecorators: { existingFile: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFile", required: false }] }], existingFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFileName", required: false }] }], overrideFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "overrideFileName", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], pTooltipOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "pTooltipOverride", required: false }] }], tooltipPositionOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipPositionOverride", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], invalidSummary: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidSummary", required: false }] }], invalidDetail: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidDetail", required: false }] }], invalidSizeSummary: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidSizeSummary", required: false }] }], invalidSizeDetail: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidSizeDetail", required: false }] }], fileUpload: [{ type: i0.ViewChild, args: ['fileUpload', { isSignal: true }] }] } });
|
|
5255
5491
|
|
|
5256
5492
|
function uid() {
|
|
5257
5493
|
return Math.random().toString(36).substring(2, 8);
|
|
@@ -5259,67 +5495,245 @@ function uid() {
|
|
|
5259
5495
|
class JsonValuesDebujComponent {
|
|
5260
5496
|
form = input(null, ...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
5261
5497
|
debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
|
|
5498
|
+
debounceTime = input(300, ...(ngDevMode ? [{ debugName: "debounceTime" }] : /* istanbul ignore next */ [])); // ✅ Configurable
|
|
5262
5499
|
debugValue = signal(null, ...(ngDevMode ? [{ debugName: "debugValue" }] : /* istanbul ignore next */ []));
|
|
5500
|
+
serializedValue = signal('', ...(ngDevMode ? [{ debugName: "serializedValue" }] : /* istanbul ignore next */ []));
|
|
5501
|
+
fileInfo = signal(null, ...(ngDevMode ? [{ debugName: "fileInfo" }] : /* istanbul ignore next */ []));
|
|
5502
|
+
isDevModeSignal = computed(() => isDevMode(), ...(ngDevMode ? [{ debugName: "isDevModeSignal" }] : /* istanbul ignore next */ []));
|
|
5503
|
+
isDebugEnabledSignal = computed(() => {
|
|
5504
|
+
const isDev = this.isDevModeSignal();
|
|
5505
|
+
const debug = this.debug();
|
|
5506
|
+
const result = isDev && debug;
|
|
5507
|
+
if (this._lastDebugState !== result) {
|
|
5508
|
+
this._lastDebugState = result;
|
|
5509
|
+
if (result) {
|
|
5510
|
+
console.log(`[JsonDebug:${this.id}] 📝 Logs activados`, {
|
|
5511
|
+
isDevMode: isDev,
|
|
5512
|
+
debug: debug,
|
|
5513
|
+
});
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
return result;
|
|
5517
|
+
}, ...(ngDevMode ? [{ debugName: "isDebugEnabledSignal" }] : /* istanbul ignore next */ []));
|
|
5518
|
+
_lastDebugState = null;
|
|
5519
|
+
_lastVisualState = null;
|
|
5263
5520
|
id = uid();
|
|
5264
5521
|
subscription;
|
|
5265
5522
|
changesCount = 0;
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5523
|
+
isInitialized = false;
|
|
5524
|
+
lastLogTime = 0;
|
|
5525
|
+
shouldShowVisual = computed(() => {
|
|
5526
|
+
const show = this.isDevModeSignal();
|
|
5527
|
+
if (this._lastVisualState !== show) {
|
|
5528
|
+
this._lastVisualState = show;
|
|
5529
|
+
if (this.shouldShowLogs()) {
|
|
5530
|
+
console.log(`[JsonDebug:${this.id}] 👁️ Visual: ${show}`, {
|
|
5531
|
+
isDevMode: isDevMode(),
|
|
5532
|
+
reason: show ? 'Modo desarrollo activo' : 'Modo producción',
|
|
5533
|
+
});
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
return show;
|
|
5537
|
+
}, ...(ngDevMode ? [{ debugName: "shouldShowVisual" }] : /* istanbul ignore next */ []));
|
|
5538
|
+
shouldShowLogs = computed(() => {
|
|
5539
|
+
const isDev = this.isDevModeSignal();
|
|
5540
|
+
const debug = this.debug();
|
|
5541
|
+
const show = isDev && debug;
|
|
5542
|
+
if (this._lastDebugState !== show) {
|
|
5543
|
+
this._lastDebugState = show;
|
|
5544
|
+
if (show) {
|
|
5545
|
+
console.log(`[JsonDebug:${this.id}] 📝 Logs activados`, {
|
|
5546
|
+
isDevMode: isDev,
|
|
5547
|
+
debug: debug,
|
|
5548
|
+
});
|
|
5549
|
+
}
|
|
5550
|
+
}
|
|
5551
|
+
return show;
|
|
5552
|
+
}, ...(ngDevMode ? [{ debugName: "shouldShowLogs" }] : /* istanbul ignore next */ []));
|
|
5276
5553
|
constructor() {
|
|
5277
|
-
if (this.shouldShowLogs) {
|
|
5278
|
-
console.log(
|
|
5554
|
+
if (this.shouldShowLogs()) {
|
|
5555
|
+
console.log(`[JsonDebug:${this.id}] 🏗️ Constructor`, {
|
|
5556
|
+
id: this.id,
|
|
5557
|
+
isDevMode: isDevMode(),
|
|
5558
|
+
debug: this.debug(),
|
|
5559
|
+
debounceTime: this.debounceTime(),
|
|
5560
|
+
});
|
|
5279
5561
|
}
|
|
5280
5562
|
effect((onCleanup) => {
|
|
5281
5563
|
const form = this.form();
|
|
5282
|
-
if (!form)
|
|
5564
|
+
if (!form) {
|
|
5565
|
+
if (this.shouldShowLogs() && this.isInitialized) {
|
|
5566
|
+
console.log(`[JsonDebug:${this.id}] ⚠️ Formulario eliminado`);
|
|
5567
|
+
}
|
|
5568
|
+
if (this.subscription) {
|
|
5569
|
+
this.subscription.unsubscribe();
|
|
5570
|
+
this.subscription = undefined;
|
|
5571
|
+
}
|
|
5572
|
+
this.isInitialized = false;
|
|
5573
|
+
return;
|
|
5574
|
+
}
|
|
5575
|
+
if (this.isInitialized && this.subscription) {
|
|
5576
|
+
if (this.shouldShowLogs()) {
|
|
5577
|
+
console.log(`[JsonDebug:${this.id}] 🔄 Formulario ya inicializado, saltando...`);
|
|
5578
|
+
}
|
|
5283
5579
|
return;
|
|
5284
|
-
|
|
5285
|
-
|
|
5580
|
+
}
|
|
5581
|
+
if (this.shouldShowLogs()) {
|
|
5582
|
+
console.log(`[JsonDebug:${this.id}] 🔄 Effect iniciado`, {
|
|
5286
5583
|
controls: Object.keys(form.controls).length,
|
|
5584
|
+
timestamp: new Date().toISOString(),
|
|
5585
|
+
debounceTime: this.debounceTime(),
|
|
5586
|
+
});
|
|
5587
|
+
}
|
|
5588
|
+
if (this.subscription) {
|
|
5589
|
+
this.subscription.unsubscribe();
|
|
5590
|
+
this.subscription = undefined;
|
|
5591
|
+
}
|
|
5592
|
+
const rawValue = form.getRawValue();
|
|
5593
|
+
this.debugValue.set(rawValue);
|
|
5594
|
+
this.updateSerializedValue(rawValue);
|
|
5595
|
+
this.updateFileInfo(rawValue);
|
|
5596
|
+
if (this.shouldShowLogs()) {
|
|
5597
|
+
console.log(`[JsonDebug:${this.id}] 📊 Procesamiento inicial`, {
|
|
5598
|
+
hasFile: !!rawValue?.file,
|
|
5599
|
+
fileType: rawValue?.file ? typeof rawValue.file : 'N/A',
|
|
5600
|
+
serializedLength: this.serializedValue().length,
|
|
5287
5601
|
});
|
|
5288
5602
|
}
|
|
5289
|
-
|
|
5290
|
-
this.
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5603
|
+
// ✅ Aplicar debounce y distinctUntilChanged para reducir logs
|
|
5604
|
+
this.subscription = form.valueChanges
|
|
5605
|
+
.pipe(debounceTime(this.debounceTime()), // ✅ Esperar 300ms antes de emitir
|
|
5606
|
+
distinctUntilChanged((prev, curr) => {
|
|
5607
|
+
// ✅ Comparar si los valores son iguales
|
|
5608
|
+
return JSON.stringify(prev) === JSON.stringify(curr);
|
|
5609
|
+
}))
|
|
5610
|
+
.subscribe(() => {
|
|
5611
|
+
const startTime = performance.now();
|
|
5612
|
+
const rawValue = form.getRawValue();
|
|
5613
|
+
this.debugValue.set(rawValue);
|
|
5614
|
+
this.updateSerializedValue(rawValue);
|
|
5615
|
+
this.updateFileInfo(rawValue);
|
|
5616
|
+
this.changesCount++;
|
|
5617
|
+
const endTime = performance.now();
|
|
5618
|
+
if (this.shouldShowLogs()) {
|
|
5619
|
+
const elapsed = endTime - startTime;
|
|
5620
|
+
console.log(`[JsonDebug:${this.id}] 🔄 Cambio #${this.changesCount}`, {
|
|
5621
|
+
tiempoMs: elapsed.toFixed(2),
|
|
5622
|
+
hasFile: !!rawValue?.file,
|
|
5623
|
+
serializedLength: this.serializedValue().length,
|
|
5624
|
+
warning: elapsed > 10 ? '⚠️ Cambio lento' : '✅ OK',
|
|
5297
5625
|
});
|
|
5626
|
+
if (this.changesCount % 10 === 0) {
|
|
5627
|
+
console.log(`[JsonDebug:${this.id}] 📊 Estadísticas`, {
|
|
5628
|
+
cambios: this.changesCount,
|
|
5629
|
+
promedioMs: (elapsed / this.changesCount).toFixed(2),
|
|
5630
|
+
ultimoMs: elapsed.toFixed(2),
|
|
5631
|
+
});
|
|
5632
|
+
}
|
|
5298
5633
|
}
|
|
5299
5634
|
});
|
|
5635
|
+
this.isInitialized = true;
|
|
5300
5636
|
onCleanup(() => {
|
|
5301
|
-
this.
|
|
5637
|
+
if (this.shouldShowLogs()) {
|
|
5638
|
+
console.log(`[JsonDebug:${this.id}] 🧹 Limpieza effect`, {
|
|
5639
|
+
cambiosProcesados: this.changesCount,
|
|
5640
|
+
isInitialized: this.isInitialized,
|
|
5641
|
+
});
|
|
5642
|
+
}
|
|
5302
5643
|
});
|
|
5303
5644
|
});
|
|
5304
5645
|
}
|
|
5305
|
-
|
|
5306
|
-
if (
|
|
5307
|
-
|
|
5646
|
+
updateSerializedValue(value) {
|
|
5647
|
+
if (!value) {
|
|
5648
|
+
this.serializedValue.set('');
|
|
5649
|
+
return;
|
|
5650
|
+
}
|
|
5651
|
+
try {
|
|
5652
|
+
const copy = { ...value };
|
|
5653
|
+
if (copy.file && typeof copy.file === 'object') {
|
|
5654
|
+
const fileProps = {};
|
|
5655
|
+
if ('name' in copy.file && copy.file.name !== undefined) {
|
|
5656
|
+
fileProps.name = copy.file.name;
|
|
5657
|
+
}
|
|
5658
|
+
if ('size' in copy.file && copy.file.size !== undefined) {
|
|
5659
|
+
fileProps.size = copy.file.size;
|
|
5660
|
+
}
|
|
5661
|
+
if ('type' in copy.file && copy.file.type !== undefined) {
|
|
5662
|
+
fileProps.type = copy.file.type;
|
|
5663
|
+
}
|
|
5664
|
+
if ('lastModified' in copy.file &&
|
|
5665
|
+
copy.file.lastModified !== undefined) {
|
|
5666
|
+
fileProps.lastModified = copy.file.lastModified;
|
|
5667
|
+
}
|
|
5668
|
+
const otherProps = Object.keys(copy.file).filter((key) => !['name', 'size', 'type', 'lastModified'].includes(key));
|
|
5669
|
+
otherProps.forEach((key) => {
|
|
5670
|
+
fileProps[key] = copy.file[key];
|
|
5671
|
+
});
|
|
5672
|
+
copy.file = fileProps;
|
|
5673
|
+
}
|
|
5674
|
+
this.serializedValue.set(JSON.stringify(copy, null, 2));
|
|
5675
|
+
}
|
|
5676
|
+
catch (error) {
|
|
5677
|
+
this.serializedValue.set('Error al serializar: ' + error);
|
|
5678
|
+
if (this.shouldShowLogs()) {
|
|
5679
|
+
console.error('[JsonDebug] Error serializing value:', error);
|
|
5680
|
+
}
|
|
5681
|
+
}
|
|
5682
|
+
}
|
|
5683
|
+
updateFileInfo(value) {
|
|
5684
|
+
if (!value || !value.file) {
|
|
5685
|
+
this.fileInfo.set(null);
|
|
5686
|
+
return;
|
|
5687
|
+
}
|
|
5688
|
+
const file = value.file;
|
|
5689
|
+
const info = {
|
|
5690
|
+
exists: true,
|
|
5691
|
+
isFile: file instanceof File,
|
|
5692
|
+
isBlob: file instanceof Blob,
|
|
5693
|
+
hasName: 'name' in file,
|
|
5694
|
+
hasSize: 'size' in file,
|
|
5695
|
+
hasType: 'type' in file,
|
|
5696
|
+
hasLastModified: 'lastModified' in file,
|
|
5697
|
+
};
|
|
5698
|
+
if ('name' in file && file.name !== undefined) {
|
|
5699
|
+
info.name = file.name;
|
|
5700
|
+
}
|
|
5701
|
+
if ('size' in file && file.size !== undefined) {
|
|
5702
|
+
info.size = file.size;
|
|
5308
5703
|
}
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5704
|
+
if ('type' in file && file.type !== undefined) {
|
|
5705
|
+
info.mimeType = file.type;
|
|
5706
|
+
}
|
|
5707
|
+
if ('lastModified' in file && file.lastModified !== undefined) {
|
|
5708
|
+
info.lastModified = file.lastModified;
|
|
5709
|
+
}
|
|
5710
|
+
this.fileInfo.set(info);
|
|
5711
|
+
}
|
|
5712
|
+
hasFile() {
|
|
5713
|
+
return !!this.fileInfo();
|
|
5714
|
+
}
|
|
5715
|
+
getFileInfo() {
|
|
5716
|
+
return this.fileInfo();
|
|
5717
|
+
}
|
|
5718
|
+
ngOnDestroy() {
|
|
5719
|
+
if (this.shouldShowLogs()) {
|
|
5720
|
+
console.log(`[JsonDebug:${this.id}] 💀 Destroy`, {
|
|
5312
5721
|
totalChanges: this.changesCount,
|
|
5722
|
+
tiempoVida: `${(performance.now() / 1000).toFixed(2)}s`,
|
|
5313
5723
|
});
|
|
5314
5724
|
}
|
|
5725
|
+
if (this.subscription) {
|
|
5726
|
+
this.subscription.unsubscribe();
|
|
5727
|
+
this.subscription = undefined;
|
|
5728
|
+
}
|
|
5315
5729
|
}
|
|
5316
5730
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonValuesDebujComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5317
|
-
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 (
|
|
5731
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: JsonValuesDebujComponent, isStandalone: true, selector: "app-json-values-debuj", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!-- json-values-debuj.component.html -->\r\n@if (shouldShowVisual()) {\r\n <div class=\"json-debug-container\">\r\n <div class=\"json-debug-header\">\r\n <span class=\"json-debug-id\">\uD83D\uDD0D Debug #{{ id }}</span>\r\n <span class=\"json-debug-info\">\r\n @if (debugValue()) {\r\n <span class=\"badge badge-success\">\u2713 Formulario activo</span>\r\n } @else {\r\n <span class=\"badge badge-danger\">\u2717 Sin formulario</span>\r\n }\r\n </span>\r\n </div>\r\n\r\n @if (debugValue()) {\r\n <div class=\"json-debug-content\">\r\n <!-- Informaci\u00F3n del archivo (si existe) -->\r\n @if (hasFile()) {\r\n <div class=\"file-info-box\">\r\n <div class=\"file-info-header\">\uD83D\uDCC1 Archivo seleccionado</div>\r\n <div class=\"file-info-grid\">\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Nombre:</span>\r\n <span class=\"value\">{{ getFileInfo()?.name || \"N/A\" }}</span>\r\n </div>\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Tama\u00F1o:</span>\r\n <span class=\"value\"\r\n >{{ getFileInfo()?.size | number }} bytes</span\r\n >\r\n </div>\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Tipo:</span>\r\n <span class=\"value\">{{\r\n getFileInfo()?.mimeType || \"N/A\"\r\n }}</span>\r\n </div>\r\n @if (getFileInfo()?.lastModified) {\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Modificado:</span>\r\n <span class=\"value\">{{\r\n getFileInfo()?.lastModified | date: \"medium\"\r\n }}</span>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- JSON -->\r\n <div class=\"json-display\">\r\n <pre class=\"json-colored\">{{ serializedValue() }}</pre>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".json-debug-container{margin:10px 0;border:1px solid #dee2e6;border-radius:8px;background-color:#f8f9fa;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;box-shadow:0 2px 8px #0000001a}.json-debug-header{display:flex;justify-content:space-between;align-items:center;padding:12px 20px;background:linear-gradient(135deg,#667eea,#764ba2);border-radius:8px 8px 0 0;color:#fff}.json-debug-id{font-weight:700;font-size:14px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.2)}.badge{padding:4px 14px;border-radius:20px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.badge-success{background-color:#10b981;color:#fff;box-shadow:0 2px 4px #10b9814d}.badge-danger{background-color:#ef4444;color:#fff;box-shadow:0 2px 4px #ef44444d}.json-debug-content{padding:16px 20px}.file-info-box{background:linear-gradient(135deg,#f0f9ff,#e0f2fe);border:1px solid #bae6fd;border-radius:6px;padding:12px 16px;margin-bottom:16px}.file-info-header{font-weight:600;color:#0369a1;font-size:13px;margin-bottom:8px}.file-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:6px}.file-info-item{padding:4px 8px;background-color:#fff;border-radius:4px;border:1px solid #e5e7eb}.file-info-item .label{font-weight:600;color:#6b7280;margin-right:8px;font-size:12px}.file-info-item .value{color:#0369a1;font-family:Consolas,Monaco,Courier New,monospace;font-size:12px}.json-display{margin-top:0}.json-colored{margin:0;padding:16px 20px;background-color:#1e1e1e;border-radius:6px;overflow:auto;font-size:13px;line-height:1.7;color:#d4d4d4;min-height:50px;max-height:500px;font-family:Consolas,Monaco,Courier New,monospace;white-space:pre}.json-colored::-webkit-scrollbar{width:10px;height:10px}.json-colored::-webkit-scrollbar-track{background:#2d2d2d;border-radius:4px}.json-colored::-webkit-scrollbar-thumb{background:#666;border-radius:4px}.json-colored::-webkit-scrollbar-thumb:hover{background:#888}@media(max-width:768px){.json-debug-header{flex-direction:column;gap:8px;padding:10px 16px}.json-debug-content{padding:12px 16px}.file-info-grid{grid-template-columns:1fr}.json-colored{font-size:11px;padding:12px 14px;max-height:300px}}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: DecimalPipe, name: "number" }], encapsulation: i0.ViewEncapsulation.None });
|
|
5318
5732
|
}
|
|
5319
5733
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonValuesDebujComponent, decorators: [{
|
|
5320
5734
|
type: Component,
|
|
5321
|
-
args: [{ selector: 'app-json-values-debuj', standalone: true, imports: [
|
|
5322
|
-
}], ctorParameters: () => [], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }] } });
|
|
5735
|
+
args: [{ selector: 'app-json-values-debuj', standalone: true, imports: [DatePipe, DecimalPipe], encapsulation: ViewEncapsulation.None, template: "<!-- json-values-debuj.component.html -->\r\n@if (shouldShowVisual()) {\r\n <div class=\"json-debug-container\">\r\n <div class=\"json-debug-header\">\r\n <span class=\"json-debug-id\">\uD83D\uDD0D Debug #{{ id }}</span>\r\n <span class=\"json-debug-info\">\r\n @if (debugValue()) {\r\n <span class=\"badge badge-success\">\u2713 Formulario activo</span>\r\n } @else {\r\n <span class=\"badge badge-danger\">\u2717 Sin formulario</span>\r\n }\r\n </span>\r\n </div>\r\n\r\n @if (debugValue()) {\r\n <div class=\"json-debug-content\">\r\n <!-- Informaci\u00F3n del archivo (si existe) -->\r\n @if (hasFile()) {\r\n <div class=\"file-info-box\">\r\n <div class=\"file-info-header\">\uD83D\uDCC1 Archivo seleccionado</div>\r\n <div class=\"file-info-grid\">\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Nombre:</span>\r\n <span class=\"value\">{{ getFileInfo()?.name || \"N/A\" }}</span>\r\n </div>\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Tama\u00F1o:</span>\r\n <span class=\"value\"\r\n >{{ getFileInfo()?.size | number }} bytes</span\r\n >\r\n </div>\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Tipo:</span>\r\n <span class=\"value\">{{\r\n getFileInfo()?.mimeType || \"N/A\"\r\n }}</span>\r\n </div>\r\n @if (getFileInfo()?.lastModified) {\r\n <div class=\"file-info-item\">\r\n <span class=\"label\">Modificado:</span>\r\n <span class=\"value\">{{\r\n getFileInfo()?.lastModified | date: \"medium\"\r\n }}</span>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- JSON -->\r\n <div class=\"json-display\">\r\n <pre class=\"json-colored\">{{ serializedValue() }}</pre>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [".json-debug-container{margin:10px 0;border:1px solid #dee2e6;border-radius:8px;background-color:#f8f9fa;font-family:Consolas,Monaco,Courier New,monospace;font-size:13px;box-shadow:0 2px 8px #0000001a}.json-debug-header{display:flex;justify-content:space-between;align-items:center;padding:12px 20px;background:linear-gradient(135deg,#667eea,#764ba2);border-radius:8px 8px 0 0;color:#fff}.json-debug-id{font-weight:700;font-size:14px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.2)}.badge{padding:4px 14px;border-radius:20px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.badge-success{background-color:#10b981;color:#fff;box-shadow:0 2px 4px #10b9814d}.badge-danger{background-color:#ef4444;color:#fff;box-shadow:0 2px 4px #ef44444d}.json-debug-content{padding:16px 20px}.file-info-box{background:linear-gradient(135deg,#f0f9ff,#e0f2fe);border:1px solid #bae6fd;border-radius:6px;padding:12px 16px;margin-bottom:16px}.file-info-header{font-weight:600;color:#0369a1;font-size:13px;margin-bottom:8px}.file-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:6px}.file-info-item{padding:4px 8px;background-color:#fff;border-radius:4px;border:1px solid #e5e7eb}.file-info-item .label{font-weight:600;color:#6b7280;margin-right:8px;font-size:12px}.file-info-item .value{color:#0369a1;font-family:Consolas,Monaco,Courier New,monospace;font-size:12px}.json-display{margin-top:0}.json-colored{margin:0;padding:16px 20px;background-color:#1e1e1e;border-radius:6px;overflow:auto;font-size:13px;line-height:1.7;color:#d4d4d4;min-height:50px;max-height:500px;font-family:Consolas,Monaco,Courier New,monospace;white-space:pre}.json-colored::-webkit-scrollbar{width:10px;height:10px}.json-colored::-webkit-scrollbar-track{background:#2d2d2d;border-radius:4px}.json-colored::-webkit-scrollbar-thumb{background:#666;border-radius:4px}.json-colored::-webkit-scrollbar-thumb:hover{background:#888}@media(max-width:768px){.json-debug-header{flex-direction:column;gap:8px;padding:10px 16px}.json-debug-content{padding:12px 16px}.file-info-grid{grid-template-columns:1fr}.json-colored{font-size:11px;padding:12px 14px;max-height:300px}}\n"] }]
|
|
5736
|
+
}], ctorParameters: () => [], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }], debounceTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceTime", required: false }] }] } });
|
|
5323
5737
|
|
|
5324
5738
|
class IcoLabel {
|
|
5325
5739
|
ico = input.required(...(ngDevMode ? [{ debugName: "ico" }] : /* istanbul ignore next */ []));
|
|
@@ -8197,67 +8611,395 @@ class JoinByPipe {
|
|
|
8197
8611
|
.filter((value) => value.length > 0)
|
|
8198
8612
|
.join(separator);
|
|
8199
8613
|
}
|
|
8200
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JoinByPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
8201
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: JoinByPipe, isStandalone: true, name: "joinBy" });
|
|
8202
|
-
}
|
|
8203
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JoinByPipe, decorators: [{
|
|
8204
|
-
type: Pipe,
|
|
8205
|
-
args: [{
|
|
8206
|
-
name: 'joinBy',
|
|
8207
|
-
standalone: true,
|
|
8208
|
-
}]
|
|
8209
|
-
}] });
|
|
8210
|
-
|
|
8211
|
-
class
|
|
8614
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JoinByPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
8615
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: JoinByPipe, isStandalone: true, name: "joinBy" });
|
|
8616
|
+
}
|
|
8617
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JoinByPipe, decorators: [{
|
|
8618
|
+
type: Pipe,
|
|
8619
|
+
args: [{
|
|
8620
|
+
name: 'joinBy',
|
|
8621
|
+
standalone: true,
|
|
8622
|
+
}]
|
|
8623
|
+
}] });
|
|
8624
|
+
|
|
8625
|
+
class JsonHighlightPipe {
|
|
8626
|
+
/**
|
|
8627
|
+
* Stringifica el JSON manteniendo arrays en una línea para una vista más compacta
|
|
8628
|
+
*/
|
|
8629
|
+
compactStringify(obj, indent = 0) {
|
|
8630
|
+
const spaces = ' '.repeat(indent);
|
|
8631
|
+
if (obj === null) {
|
|
8632
|
+
return 'null';
|
|
8633
|
+
}
|
|
8634
|
+
if (typeof obj !== 'object') {
|
|
8635
|
+
if (typeof obj === 'string') {
|
|
8636
|
+
return JSON.stringify(obj);
|
|
8637
|
+
}
|
|
8638
|
+
return String(obj);
|
|
8639
|
+
}
|
|
8640
|
+
if (obj instanceof Date) {
|
|
8641
|
+
return JSON.stringify(obj.toISOString());
|
|
8642
|
+
}
|
|
8643
|
+
if (Array.isArray(obj)) {
|
|
8644
|
+
// Mantener arrays en una línea
|
|
8645
|
+
const items = obj.map((item) => {
|
|
8646
|
+
if (item instanceof Date) {
|
|
8647
|
+
return JSON.stringify(item.toISOString());
|
|
8648
|
+
}
|
|
8649
|
+
if (typeof item === 'object' && item !== null) {
|
|
8650
|
+
return this.compactStringify(item, indent);
|
|
8651
|
+
}
|
|
8652
|
+
return typeof item === 'string'
|
|
8653
|
+
? JSON.stringify(item)
|
|
8654
|
+
: item === null
|
|
8655
|
+
? 'null'
|
|
8656
|
+
: String(item);
|
|
8657
|
+
});
|
|
8658
|
+
return `[${items.join(', ')}]`;
|
|
8659
|
+
}
|
|
8660
|
+
// Para objetos, mantener la indentación
|
|
8661
|
+
const keys = Object.keys(obj);
|
|
8662
|
+
if (keys.length === 0) {
|
|
8663
|
+
return '{}';
|
|
8664
|
+
}
|
|
8665
|
+
const innerIndent = indent + 2;
|
|
8666
|
+
const innerSpaces = ' '.repeat(innerIndent);
|
|
8667
|
+
const lines = keys.map((key) => {
|
|
8668
|
+
const value = obj[key];
|
|
8669
|
+
const stringified = value instanceof Date
|
|
8670
|
+
? JSON.stringify(value.toISOString())
|
|
8671
|
+
: typeof value === 'object' && value !== null
|
|
8672
|
+
? this.compactStringify(value, innerIndent)
|
|
8673
|
+
: typeof value === 'string'
|
|
8674
|
+
? JSON.stringify(value)
|
|
8675
|
+
: value === null
|
|
8676
|
+
? 'null'
|
|
8677
|
+
: String(value);
|
|
8678
|
+
return `${innerSpaces}"${key}": ${stringified}`;
|
|
8679
|
+
});
|
|
8680
|
+
return `{\n${lines.join(',\n')}\n${spaces}}`;
|
|
8681
|
+
}
|
|
8682
|
+
instanceId = Math.random().toString(36).slice(2, 8);
|
|
8683
|
+
executions = 0;
|
|
8684
|
+
transform(value) {
|
|
8685
|
+
if (isDevMode()) {
|
|
8686
|
+
this.executions++;
|
|
8687
|
+
if (this.executions % 10 === 0) {
|
|
8688
|
+
console.log(`[jsonHighlight:${this.instanceId}] ${this.executions}`);
|
|
8689
|
+
}
|
|
8690
|
+
}
|
|
8691
|
+
if (!value) {
|
|
8692
|
+
return '';
|
|
8693
|
+
}
|
|
8694
|
+
const json = this.compactStringify(value);
|
|
8695
|
+
// Detecta fechas ISO: "YYYY-MM-DDTHH:MM:SS.mmmZ"
|
|
8696
|
+
const ISO_DATE_RE = /^"(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}\.\d{3}Z)"$/;
|
|
8697
|
+
// Aplica estilo y colores para diferenciar claves, valores, booleanos, números, etc.
|
|
8698
|
+
return json
|
|
8699
|
+
.replace(/&/g, '&')
|
|
8700
|
+
.replace(/</g, '<')
|
|
8701
|
+
.replace(/>/g, '>')
|
|
8702
|
+
.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?)/g, (match) => {
|
|
8703
|
+
// Si es una clave (tiene dos puntos al final)
|
|
8704
|
+
if (/:$/.test(match)) {
|
|
8705
|
+
return `<span class="json-key">${match}</span>`;
|
|
8706
|
+
}
|
|
8707
|
+
// Detectar fechas ISO (YYYY-MM-DDTHH:MM:SS.mmmZ)
|
|
8708
|
+
const isoMatch = match.match(ISO_DATE_RE);
|
|
8709
|
+
if (isoMatch) {
|
|
8710
|
+
return `<span class="json-string json-date">"<span class="json-date-part">${isoMatch[1]}</span><span class="json-date-sep">T</span><span class="json-date-time">${isoMatch[2]}</span>"</span>`;
|
|
8711
|
+
}
|
|
8712
|
+
// Si es un string (no tiene dos puntos)
|
|
8713
|
+
return `<span class="json-string">${match}</span>`;
|
|
8714
|
+
})
|
|
8715
|
+
.replace(/\b(true|false|null)\b/g, (match) => {
|
|
8716
|
+
if (match === 'null') {
|
|
8717
|
+
return '<span class="json-null">null</span>';
|
|
8718
|
+
}
|
|
8719
|
+
return `<span class="json-boolean">${match}</span>`;
|
|
8720
|
+
})
|
|
8721
|
+
.replace(/\b(\d+\.?\d*)\b/g, '<span class="json-number">$1</span>')
|
|
8722
|
+
.replace(/[{}\[\]]/g, '<span class="json-bracket">$&</span>')
|
|
8723
|
+
.replace(/,/g, '<span class="json-comma">$&</span>')
|
|
8724
|
+
.replace(/:/g, '<span class="json-colon">$&</span>');
|
|
8725
|
+
}
|
|
8726
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
8727
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, isStandalone: true, name: "jsonHighlight" });
|
|
8728
|
+
}
|
|
8729
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: JsonHighlightPipe, decorators: [{
|
|
8730
|
+
type: Pipe,
|
|
8731
|
+
args: [{
|
|
8732
|
+
name: 'jsonHighlight',
|
|
8733
|
+
}]
|
|
8734
|
+
}] });
|
|
8735
|
+
|
|
8736
|
+
class TruncatePipe {
|
|
8737
|
+
/**
|
|
8738
|
+
* Transforma una cadena de texto truncándola según los parámetros proporcionados.
|
|
8739
|
+
*
|
|
8740
|
+
* @param value - La cadena de texto que se desea truncar.
|
|
8741
|
+
* @param limit - El número máximo de caracteres permitidos. Por defecto es 100. Si no se encuentra un espacio dentro del límite, usa el límite original (15).
|
|
8742
|
+
* @param completeWords - Si es true, evita cortar palabras a la mitad. Por defecto es false.
|
|
8743
|
+
* @param ellipsis - La cadena que se añadirá al final del texto truncado. Por defecto es '...'.
|
|
8744
|
+
* @returns La cadena truncada con el ellipsis añadido, si es necesario.
|
|
8745
|
+
*/
|
|
8746
|
+
transform(value, limit = 20, completeWords = false, ellipsis = '...') {
|
|
8747
|
+
// Si el valor es nulo o indefinido, devuelve una cadena vacía para evitar errores.
|
|
8748
|
+
if (!value) {
|
|
8749
|
+
return '';
|
|
8750
|
+
}
|
|
8751
|
+
// Si completeWords es true, ajusta el límite para no cortar palabras.
|
|
8752
|
+
if (completeWords) {
|
|
8753
|
+
// Encuentra el último espacio dentro del límite para evitar cortar palabras.
|
|
8754
|
+
limit = value.slice(0, limit).lastIndexOf(' ');
|
|
8755
|
+
// Si no se encuentra un espacio dentro del límite, usa el límite original.
|
|
8756
|
+
if (limit < 0) {
|
|
8757
|
+
limit = 15; // Valor por defecto si no hay espacios.
|
|
8758
|
+
}
|
|
8759
|
+
}
|
|
8760
|
+
// Trunca el texto y añade el ellipsis si la longitud del texto supera el límite.
|
|
8761
|
+
return value.length > limit ? value.slice(0, limit) + ellipsis : value;
|
|
8762
|
+
}
|
|
8763
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TruncatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
8764
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: TruncatePipe, isStandalone: true, name: "truncate" });
|
|
8765
|
+
}
|
|
8766
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: TruncatePipe, decorators: [{
|
|
8767
|
+
type: Pipe,
|
|
8768
|
+
args: [{
|
|
8769
|
+
name: 'truncate',
|
|
8770
|
+
standalone: true,
|
|
8771
|
+
}]
|
|
8772
|
+
}] });
|
|
8773
|
+
|
|
8774
|
+
// dsxdate-short.ts
|
|
8775
|
+
class DsxdateShort {
|
|
8776
|
+
injector = inject(Injector);
|
|
8777
|
+
// ✅ Exponer isDevMode como propiedad pública
|
|
8778
|
+
isDevMode = isDevMode();
|
|
8779
|
+
// === INPUTS CONFIGURABLES ===
|
|
8780
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
8781
|
+
placeholder = input('dd/mm/yyyy', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
8782
|
+
dateFormat = input('dd/mm/yy', ...(ngDevMode ? [{ debugName: "dateFormat" }] : /* istanbul ignore next */ []));
|
|
8783
|
+
showIcon = input(true, ...(ngDevMode ? [{ debugName: "showIcon" }] : /* istanbul ignore next */ []));
|
|
8784
|
+
showOnFocus = input(false, ...(ngDevMode ? [{ debugName: "showOnFocus" }] : /* istanbul ignore next */ []));
|
|
8785
|
+
showButtonBar = input(true, ...(ngDevMode ? [{ debugName: "showButtonBar" }] : /* istanbul ignore next */ []));
|
|
8786
|
+
debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
|
|
8787
|
+
// === STATE ===
|
|
8788
|
+
debugEnabled = signal(false, ...(ngDevMode ? [{ debugName: "debugEnabled" }] : /* istanbul ignore next */ []));
|
|
8789
|
+
// === OBTENER EL CONTROL PADRE ===
|
|
8790
|
+
ngControl = computed(() => {
|
|
8791
|
+
try {
|
|
8792
|
+
return this.injector.get(NgControl);
|
|
8793
|
+
}
|
|
8794
|
+
catch {
|
|
8795
|
+
return null;
|
|
8796
|
+
}
|
|
8797
|
+
}, ...(ngDevMode ? [{ debugName: "ngControl" }] : /* istanbul ignore next */ []));
|
|
8798
|
+
// === CONTROL PADRE PARA ERRORES ===
|
|
8799
|
+
parentControl = computed(() => {
|
|
8800
|
+
const control = this.ngControl();
|
|
8801
|
+
return control?.control || null;
|
|
8802
|
+
}, ...(ngDevMode ? [{ debugName: "parentControl" }] : /* istanbul ignore next */ []));
|
|
8803
|
+
// === CONTROL INTERNO (para el template) ===
|
|
8804
|
+
fechaControl = new FormControl(null);
|
|
8805
|
+
// === CONTROL VALUE ACCESSOR ===
|
|
8806
|
+
onChange = () => { };
|
|
8807
|
+
onTouched = () => { };
|
|
8808
|
+
constructor() {
|
|
8809
|
+
// Debug effect
|
|
8810
|
+
effect(() => {
|
|
8811
|
+
const isDebug = this.debug() && this.isDevMode;
|
|
8812
|
+
this.debugEnabled.set(isDebug);
|
|
8813
|
+
if (isDebug) {
|
|
8814
|
+
console.log('[dsxdate-short] === DEBUG MODE ACTIVADO ===');
|
|
8815
|
+
console.log('[dsxdate-short] Configuración:', {
|
|
8816
|
+
label: this.label(),
|
|
8817
|
+
placeholder: this.placeholder(),
|
|
8818
|
+
dateFormat: this.dateFormat(),
|
|
8819
|
+
showIcon: this.showIcon(),
|
|
8820
|
+
showOnFocus: this.showOnFocus(),
|
|
8821
|
+
showButtonBar: this.showButtonBar(),
|
|
8822
|
+
});
|
|
8823
|
+
console.log('[dsxdate-short] Control padre:', this.parentControl());
|
|
8824
|
+
}
|
|
8825
|
+
});
|
|
8826
|
+
// ✅ SINCRONIZAR VALIDADORES DEL PADRE AL CONTROL INTERNO
|
|
8827
|
+
effect(() => {
|
|
8828
|
+
const parentCtrl = this.parentControl();
|
|
8829
|
+
if (parentCtrl) {
|
|
8830
|
+
// Copiar validadores del padre al control interno
|
|
8831
|
+
const validators = parentCtrl.validator;
|
|
8832
|
+
const asyncValidators = parentCtrl.asyncValidator;
|
|
8833
|
+
if (this.debugEnabled()) {
|
|
8834
|
+
console.log('[dsxdate-short] 🔄 Sincronizando validadores del padre');
|
|
8835
|
+
console.log('[dsxdate-short] Validadores del padre:', validators);
|
|
8836
|
+
}
|
|
8837
|
+
this.fechaControl.setValidators(validators ? [validators] : []);
|
|
8838
|
+
this.fechaControl.setAsyncValidators(asyncValidators ? [asyncValidators] : []);
|
|
8839
|
+
this.fechaControl.updateValueAndValidity({ emitEvent: false });
|
|
8840
|
+
if (this.debugEnabled()) {
|
|
8841
|
+
console.log('[dsxdate-short] ✅ Validadores sincronizados');
|
|
8842
|
+
console.log('[dsxdate-short] Estado del control interno:', {
|
|
8843
|
+
valid: this.fechaControl.valid,
|
|
8844
|
+
invalid: this.fechaControl.invalid,
|
|
8845
|
+
errors: this.fechaControl.errors,
|
|
8846
|
+
});
|
|
8847
|
+
}
|
|
8848
|
+
}
|
|
8849
|
+
});
|
|
8850
|
+
// Monitorear cambios del control padre
|
|
8851
|
+
effect(() => {
|
|
8852
|
+
if (this.debugEnabled()) {
|
|
8853
|
+
const parentCtrl = this.parentControl();
|
|
8854
|
+
console.log('[dsxdate-short] 📊 Parent Control state:', {
|
|
8855
|
+
value: parentCtrl?.value,
|
|
8856
|
+
status: parentCtrl?.status,
|
|
8857
|
+
errors: parentCtrl?.errors,
|
|
8858
|
+
valid: parentCtrl?.valid,
|
|
8859
|
+
invalid: parentCtrl?.invalid,
|
|
8860
|
+
touched: parentCtrl?.touched,
|
|
8861
|
+
dirty: parentCtrl?.dirty,
|
|
8862
|
+
});
|
|
8863
|
+
}
|
|
8864
|
+
});
|
|
8865
|
+
// Sincronizar cambios del control interno al padre
|
|
8866
|
+
this.fechaControl.valueChanges.subscribe((value) => {
|
|
8867
|
+
if (this.debugEnabled()) {
|
|
8868
|
+
console.log('[dsxdate-short] 🔄 valueChanges - Nuevo valor:', value);
|
|
8869
|
+
}
|
|
8870
|
+
this.onChange(value);
|
|
8871
|
+
});
|
|
8872
|
+
// Sincronizar touched
|
|
8873
|
+
this.fechaControl.statusChanges.subscribe(() => {
|
|
8874
|
+
if (this.debugEnabled()) {
|
|
8875
|
+
console.log('[dsxdate-short] 📊 statusChanges - Estado:', this.fechaControl.status);
|
|
8876
|
+
}
|
|
8877
|
+
this.onTouched();
|
|
8878
|
+
});
|
|
8879
|
+
}
|
|
8880
|
+
// === MÉTODOS DE DEBUG ===
|
|
8881
|
+
log(message, data) {
|
|
8882
|
+
if (this.debugEnabled() && this.isDevMode) {
|
|
8883
|
+
if (data !== undefined) {
|
|
8884
|
+
console.log(`[dsxdate-short] ${message}`, data);
|
|
8885
|
+
}
|
|
8886
|
+
else {
|
|
8887
|
+
console.log(`[dsxdate-short] ${message}`);
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
}
|
|
8212
8891
|
/**
|
|
8213
|
-
*
|
|
8214
|
-
*
|
|
8215
|
-
* @param value - La cadena de texto que se desea truncar.
|
|
8216
|
-
* @param limit - El número máximo de caracteres permitidos. Por defecto es 100. Si no se encuentra un espacio dentro del límite, usa el límite original (15).
|
|
8217
|
-
* @param completeWords - Si es true, evita cortar palabras a la mitad. Por defecto es false.
|
|
8218
|
-
* @param ellipsis - La cadena que se añadirá al final del texto truncado. Por defecto es '...'.
|
|
8219
|
-
* @returns La cadena truncada con el ellipsis añadido, si es necesario.
|
|
8892
|
+
* Convierte cualquier fecha (string ISO, Date, moment) a Date válido
|
|
8220
8893
|
*/
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
return '';
|
|
8894
|
+
convertirFechaADate(fecha) {
|
|
8895
|
+
if (this.debugEnabled()) {
|
|
8896
|
+
this.log('🔄 convertirFechaADate - entrada:', fecha);
|
|
8225
8897
|
}
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8898
|
+
if (!fecha) {
|
|
8899
|
+
this.log('convertirFechaADate - fecha vacía, retornando null');
|
|
8900
|
+
return null;
|
|
8901
|
+
}
|
|
8902
|
+
if (fecha instanceof Date && !isNaN(fecha.getTime())) {
|
|
8903
|
+
this.log('✅ convertirFechaADate - es Date válido:', fecha);
|
|
8904
|
+
return fecha;
|
|
8905
|
+
}
|
|
8906
|
+
if (typeof fecha === 'string') {
|
|
8907
|
+
this.log('📝 convertirFechaADate - procesando string:', fecha);
|
|
8908
|
+
const momentDate = moment(fecha);
|
|
8909
|
+
if (momentDate.isValid()) {
|
|
8910
|
+
const result = momentDate.toDate();
|
|
8911
|
+
this.log('✅ convertirFechaADate - string convertido a Date:', result);
|
|
8912
|
+
return result;
|
|
8233
8913
|
}
|
|
8914
|
+
this.log('❌ convertirFechaADate - string inválido para moment');
|
|
8234
8915
|
}
|
|
8235
|
-
|
|
8236
|
-
|
|
8916
|
+
if (moment.isMoment(fecha)) {
|
|
8917
|
+
this.log('✅ convertirFechaADate - es objeto moment');
|
|
8918
|
+
return fecha.toDate();
|
|
8919
|
+
}
|
|
8920
|
+
const parsed = new Date(fecha);
|
|
8921
|
+
if (!isNaN(parsed.getTime())) {
|
|
8922
|
+
this.log('✅ convertirFechaADate - parseo directo exitoso:', parsed);
|
|
8923
|
+
return parsed;
|
|
8924
|
+
}
|
|
8925
|
+
this.log('❌ convertirFechaADate - no se pudo convertir, retornando null');
|
|
8926
|
+
return null;
|
|
8237
8927
|
}
|
|
8238
|
-
|
|
8239
|
-
|
|
8928
|
+
// === CONTROL VALUE ACCESSOR IMPLEMENTATION ===
|
|
8929
|
+
writeValue(value) {
|
|
8930
|
+
if (this.debugEnabled()) {
|
|
8931
|
+
this.log('📥 writeValue - Valor recibido:', value);
|
|
8932
|
+
}
|
|
8933
|
+
const fechaConvertida = this.convertirFechaADate(value);
|
|
8934
|
+
if (this.debugEnabled()) {
|
|
8935
|
+
this.log('📥 writeValue - Valor convertido:', fechaConvertida);
|
|
8936
|
+
}
|
|
8937
|
+
this.fechaControl.setValue(fechaConvertida, { emitEvent: false });
|
|
8938
|
+
}
|
|
8939
|
+
registerOnChange(fn) {
|
|
8940
|
+
if (this.debugEnabled()) {
|
|
8941
|
+
this.log('📞 registerOnChange - Callback registrado');
|
|
8942
|
+
}
|
|
8943
|
+
this.onChange = fn;
|
|
8944
|
+
}
|
|
8945
|
+
registerOnTouched(fn) {
|
|
8946
|
+
if (this.debugEnabled()) {
|
|
8947
|
+
this.log('📞 registerOnTouched - Callback registrado');
|
|
8948
|
+
}
|
|
8949
|
+
this.onTouched = fn;
|
|
8950
|
+
}
|
|
8951
|
+
setDisabledState(isDisabled) {
|
|
8952
|
+
if (this.debugEnabled()) {
|
|
8953
|
+
this.log(`🚫 setDisabledState - Deshabilitado: ${isDisabled}`);
|
|
8954
|
+
}
|
|
8955
|
+
if (isDisabled) {
|
|
8956
|
+
this.fechaControl.disable({ emitEvent: false });
|
|
8957
|
+
}
|
|
8958
|
+
else {
|
|
8959
|
+
this.fechaControl.enable({ emitEvent: false });
|
|
8960
|
+
}
|
|
8961
|
+
}
|
|
8962
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxdateShort, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8963
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DsxdateShort, isStandalone: true, selector: "dsxdate-short", 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 }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
8964
|
+
{
|
|
8965
|
+
provide: NG_VALUE_ACCESSOR,
|
|
8966
|
+
useExisting: forwardRef(() => DsxdateShort),
|
|
8967
|
+
multi: true,
|
|
8968
|
+
},
|
|
8969
|
+
], ngImport: i0, template: "<!-- dsxdate-short.html -->\r\n<p-floatLabel variant=\"on\">\r\n <p-datepicker\r\n fluid\r\n appendTo=\"body\"\r\n [formControl]=\"fechaControl\"\r\n [fluid]=\"true\"\r\n [showIcon]=\"showIcon()\"\r\n [showOnFocus]=\"showOnFocus()\"\r\n [showButtonBar]=\"showButtonBar()\"\r\n [dateFormat]=\"dateFormat()\"\r\n [placeholder]=\"placeholder()\"\r\n pInputMask=\"99/99/9999\"\r\n />\r\n <label>{{ label() }}</label>\r\n</p-floatLabel>\r\n\r\n<!-- \u2705 SIN CONDICI\u00D3N - IGUAL QUE EL ORIGINAL -->\r\n<dsx-message-error [control]=\"fechaControl\" />\r\n\r\n<!-- Debug info -->\r\n@if (debugEnabled() && isDevMode) {\r\n<div\r\n class=\"debug-info\"\r\n style=\"\r\n font-size: 12px;\r\n color: #666;\r\n margin-top: 4px;\r\n border-top: 1px dashed #ccc;\r\n padding-top: 4px;\r\n \"\r\n>\r\n <small>\r\n <strong>Debug:</strong>\r\n Value: {{ fechaControl.value | json }} | Valid: {{ fechaControl.valid }} |\r\n Invalid: {{ fechaControl.invalid }} | Errors: {{ fechaControl.errors | json\r\n }} | Dirty: {{ fechaControl.dirty }} | Touched: {{ fechaControl.touched }} |\r\n Status: {{ fechaControl.status }}\r\n </small>\r\n</div>\r\n}\r\n", styles: [""], dependencies: [{ kind: "component", type: AppMessageErrorComponent, selector: "dsx-message-error", inputs: ["control", "form", "debugMode"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "component", type: FloatLabel, selector: "p-floatlabel, p-floatLabel, p-float-label", inputs: ["variant"] }, { kind: "ngmodule", type: InputMaskModule }, { kind: "directive", type: i1$8.InputMaskDirective, selector: "[pInputMask]", inputs: ["pInputMaskPT", "pInputMaskUnstyled", "pInputMask", "slotChar", "autoClear", "characterPattern", "keepBuffer"], outputs: ["onComplete", "onUnmaskedChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] });
|
|
8240
8970
|
}
|
|
8241
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type:
|
|
8242
|
-
type:
|
|
8243
|
-
args: [{
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8971
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxdateShort, decorators: [{
|
|
8972
|
+
type: Component,
|
|
8973
|
+
args: [{ selector: 'dsxdate-short', imports: [
|
|
8974
|
+
AppMessageErrorComponent,
|
|
8975
|
+
CommonModule,
|
|
8976
|
+
DatePicker,
|
|
8977
|
+
FloatLabel,
|
|
8978
|
+
InputMaskModule,
|
|
8979
|
+
JsonPipe,
|
|
8980
|
+
ReactiveFormsModule,
|
|
8981
|
+
], providers: [
|
|
8982
|
+
{
|
|
8983
|
+
provide: NG_VALUE_ACCESSOR,
|
|
8984
|
+
useExisting: forwardRef(() => DsxdateShort),
|
|
8985
|
+
multi: true,
|
|
8986
|
+
},
|
|
8987
|
+
], template: "<!-- dsxdate-short.html -->\r\n<p-floatLabel variant=\"on\">\r\n <p-datepicker\r\n fluid\r\n appendTo=\"body\"\r\n [formControl]=\"fechaControl\"\r\n [fluid]=\"true\"\r\n [showIcon]=\"showIcon()\"\r\n [showOnFocus]=\"showOnFocus()\"\r\n [showButtonBar]=\"showButtonBar()\"\r\n [dateFormat]=\"dateFormat()\"\r\n [placeholder]=\"placeholder()\"\r\n pInputMask=\"99/99/9999\"\r\n />\r\n <label>{{ label() }}</label>\r\n</p-floatLabel>\r\n\r\n<!-- \u2705 SIN CONDICI\u00D3N - IGUAL QUE EL ORIGINAL -->\r\n<dsx-message-error [control]=\"fechaControl\" />\r\n\r\n<!-- Debug info -->\r\n@if (debugEnabled() && isDevMode) {\r\n<div\r\n class=\"debug-info\"\r\n style=\"\r\n font-size: 12px;\r\n color: #666;\r\n margin-top: 4px;\r\n border-top: 1px dashed #ccc;\r\n padding-top: 4px;\r\n \"\r\n>\r\n <small>\r\n <strong>Debug:</strong>\r\n Value: {{ fechaControl.value | json }} | Valid: {{ fechaControl.valid }} |\r\n Invalid: {{ fechaControl.invalid }} | Errors: {{ fechaControl.errors | json\r\n }} | Dirty: {{ fechaControl.dirty }} | Touched: {{ fechaControl.touched }} |\r\n Status: {{ fechaControl.status }}\r\n </small>\r\n</div>\r\n}\r\n" }]
|
|
8988
|
+
}], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], showIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcon", required: false }] }], showOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "showOnFocus", required: false }] }], showButtonBar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButtonBar", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }] } });
|
|
8248
8989
|
|
|
8249
|
-
|
|
8250
|
-
class DsxdateShort {
|
|
8990
|
+
class DsxdateTime {
|
|
8251
8991
|
injector = inject(Injector);
|
|
8252
8992
|
// ✅ Exponer isDevMode como propiedad pública
|
|
8253
8993
|
isDevMode = isDevMode();
|
|
8254
8994
|
// === INPUTS CONFIGURABLES ===
|
|
8255
8995
|
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
8256
|
-
placeholder = input('dd/mm/yyyy', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
8996
|
+
placeholder = input('dd/mm/yyyy 00:00', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
8257
8997
|
dateFormat = input('dd/mm/yy', ...(ngDevMode ? [{ debugName: "dateFormat" }] : /* istanbul ignore next */ []));
|
|
8258
8998
|
showIcon = input(true, ...(ngDevMode ? [{ debugName: "showIcon" }] : /* istanbul ignore next */ []));
|
|
8259
8999
|
showOnFocus = input(false, ...(ngDevMode ? [{ debugName: "showOnFocus" }] : /* istanbul ignore next */ []));
|
|
8260
9000
|
showButtonBar = input(true, ...(ngDevMode ? [{ debugName: "showButtonBar" }] : /* istanbul ignore next */ []));
|
|
9001
|
+
showTime = input(true, ...(ngDevMode ? [{ debugName: "showTime" }] : /* istanbul ignore next */ []));
|
|
9002
|
+
hourFormat = input('24', ...(ngDevMode ? [{ debugName: "hourFormat" }] : /* istanbul ignore next */ []));
|
|
8261
9003
|
debug = input(false, ...(ngDevMode ? [{ debugName: "debug" }] : /* istanbul ignore next */ []));
|
|
8262
9004
|
// === STATE ===
|
|
8263
9005
|
debugEnabled = signal(false, ...(ngDevMode ? [{ debugName: "debugEnabled" }] : /* istanbul ignore next */ []));
|
|
@@ -8286,16 +9028,18 @@ class DsxdateShort {
|
|
|
8286
9028
|
const isDebug = this.debug() && this.isDevMode;
|
|
8287
9029
|
this.debugEnabled.set(isDebug);
|
|
8288
9030
|
if (isDebug) {
|
|
8289
|
-
console.log('[dsxdate-
|
|
8290
|
-
console.log('[dsxdate-
|
|
9031
|
+
console.log('[dsxdate-datetime] === DEBUG MODE ACTIVADO ===');
|
|
9032
|
+
console.log('[dsxdate-datetime] Configuración:', {
|
|
8291
9033
|
label: this.label(),
|
|
8292
9034
|
placeholder: this.placeholder(),
|
|
8293
9035
|
dateFormat: this.dateFormat(),
|
|
8294
9036
|
showIcon: this.showIcon(),
|
|
8295
9037
|
showOnFocus: this.showOnFocus(),
|
|
8296
9038
|
showButtonBar: this.showButtonBar(),
|
|
9039
|
+
showTime: this.showTime(),
|
|
9040
|
+
hourFormat: this.hourFormat(),
|
|
8297
9041
|
});
|
|
8298
|
-
console.log('[dsxdate-
|
|
9042
|
+
console.log('[dsxdate-datetime] Control padre:', this.parentControl());
|
|
8299
9043
|
}
|
|
8300
9044
|
});
|
|
8301
9045
|
// ✅ SINCRONIZAR VALIDADORES DEL PADRE AL CONTROL INTERNO
|
|
@@ -8306,15 +9050,15 @@ class DsxdateShort {
|
|
|
8306
9050
|
const validators = parentCtrl.validator;
|
|
8307
9051
|
const asyncValidators = parentCtrl.asyncValidator;
|
|
8308
9052
|
if (this.debugEnabled()) {
|
|
8309
|
-
console.log('[dsxdate-
|
|
8310
|
-
console.log('[dsxdate-
|
|
9053
|
+
console.log('[dsxdate-datetime] 🔄 Sincronizando validadores del padre');
|
|
9054
|
+
console.log('[dsxdate-datetime] Validadores del padre:', validators);
|
|
8311
9055
|
}
|
|
8312
9056
|
this.fechaControl.setValidators(validators ? [validators] : []);
|
|
8313
9057
|
this.fechaControl.setAsyncValidators(asyncValidators ? [asyncValidators] : []);
|
|
8314
9058
|
this.fechaControl.updateValueAndValidity({ emitEvent: false });
|
|
8315
9059
|
if (this.debugEnabled()) {
|
|
8316
|
-
console.log('[dsxdate-
|
|
8317
|
-
console.log('[dsxdate-
|
|
9060
|
+
console.log('[dsxdate-datetime] ✅ Validadores sincronizados');
|
|
9061
|
+
console.log('[dsxdate-datetime] Estado del control interno:', {
|
|
8318
9062
|
valid: this.fechaControl.valid,
|
|
8319
9063
|
invalid: this.fechaControl.invalid,
|
|
8320
9064
|
errors: this.fechaControl.errors,
|
|
@@ -8326,7 +9070,7 @@ class DsxdateShort {
|
|
|
8326
9070
|
effect(() => {
|
|
8327
9071
|
if (this.debugEnabled()) {
|
|
8328
9072
|
const parentCtrl = this.parentControl();
|
|
8329
|
-
console.log('[dsxdate-
|
|
9073
|
+
console.log('[dsxdate-datetime] 📊 Parent Control state:', {
|
|
8330
9074
|
value: parentCtrl?.value,
|
|
8331
9075
|
status: parentCtrl?.status,
|
|
8332
9076
|
errors: parentCtrl?.errors,
|
|
@@ -8340,14 +9084,14 @@ class DsxdateShort {
|
|
|
8340
9084
|
// Sincronizar cambios del control interno al padre
|
|
8341
9085
|
this.fechaControl.valueChanges.subscribe((value) => {
|
|
8342
9086
|
if (this.debugEnabled()) {
|
|
8343
|
-
console.log('[dsxdate-
|
|
9087
|
+
console.log('[dsxdate-datetime] 🔄 valueChanges - Nuevo valor:', value);
|
|
8344
9088
|
}
|
|
8345
9089
|
this.onChange(value);
|
|
8346
9090
|
});
|
|
8347
9091
|
// Sincronizar touched
|
|
8348
9092
|
this.fechaControl.statusChanges.subscribe(() => {
|
|
8349
9093
|
if (this.debugEnabled()) {
|
|
8350
|
-
console.log('[dsxdate-
|
|
9094
|
+
console.log('[dsxdate-datetime] 📊 statusChanges - Estado:', this.fechaControl.status);
|
|
8351
9095
|
}
|
|
8352
9096
|
this.onTouched();
|
|
8353
9097
|
});
|
|
@@ -8356,10 +9100,10 @@ class DsxdateShort {
|
|
|
8356
9100
|
log(message, data) {
|
|
8357
9101
|
if (this.debugEnabled() && this.isDevMode) {
|
|
8358
9102
|
if (data !== undefined) {
|
|
8359
|
-
console.log(`[dsxdate-
|
|
9103
|
+
console.log(`[dsxdate-datetime] ${message}`, data);
|
|
8360
9104
|
}
|
|
8361
9105
|
else {
|
|
8362
|
-
console.log(`[dsxdate-
|
|
9106
|
+
console.log(`[dsxdate-datetime] ${message}`);
|
|
8363
9107
|
}
|
|
8364
9108
|
}
|
|
8365
9109
|
}
|
|
@@ -8434,32 +9178,33 @@ class DsxdateShort {
|
|
|
8434
9178
|
this.fechaControl.enable({ emitEvent: false });
|
|
8435
9179
|
}
|
|
8436
9180
|
}
|
|
8437
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type:
|
|
8438
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type:
|
|
9181
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxdateTime, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9182
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DsxdateTime, isStandalone: true, selector: "dsxdate-time", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, dateFormat: { classPropertyName: "dateFormat", publicName: "dateFormat", isSignal: true, isRequired: false, transformFunction: null }, showIcon: { classPropertyName: "showIcon", publicName: "showIcon", isSignal: true, isRequired: false, transformFunction: null }, showOnFocus: { classPropertyName: "showOnFocus", publicName: "showOnFocus", isSignal: true, isRequired: false, transformFunction: null }, showButtonBar: { classPropertyName: "showButtonBar", publicName: "showButtonBar", isSignal: true, isRequired: false, transformFunction: null }, showTime: { classPropertyName: "showTime", publicName: "showTime", isSignal: true, isRequired: false, transformFunction: null }, hourFormat: { classPropertyName: "hourFormat", publicName: "hourFormat", isSignal: true, isRequired: false, transformFunction: null }, debug: { classPropertyName: "debug", publicName: "debug", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
8439
9183
|
{
|
|
8440
9184
|
provide: NG_VALUE_ACCESSOR,
|
|
8441
|
-
useExisting: forwardRef(() =>
|
|
9185
|
+
useExisting: forwardRef(() => DsxdateTime),
|
|
8442
9186
|
multi: true,
|
|
8443
9187
|
},
|
|
8444
|
-
], ngImport: i0, template: "
|
|
9188
|
+
], ngImport: i0, template: "<p-floatLabel variant=\"on\">\r\n <p-datepicker\r\n fluid\r\n appendTo=\"body\"\r\n [formControl]=\"fechaControl\"\r\n [fluid]=\"true\"\r\n [showIcon]=\"showIcon()\"\r\n [showOnFocus]=\"showOnFocus()\"\r\n [showButtonBar]=\"showButtonBar()\"\r\n [showTime]=\"showTime()\"\r\n [hourFormat]=\"hourFormat()\"\r\n [dateFormat]=\"dateFormat()\"\r\n [placeholder]=\"placeholder()\"\r\n pInputMask=\"99/99/9999 99:99\"\r\n />\r\n <label>{{ label() }}</label>\r\n</p-floatLabel>\r\n\r\n<!-- \u2705 SIN CONDICI\u00D3N - IGUAL QUE EL ORIGINAL -->\r\n<dsx-message-error [control]=\"fechaControl\" />\r\n\r\n<!-- Debug info -->\r\n@if (debugEnabled() && isDevMode) {\r\n<div\r\n class=\"debug-info\"\r\n style=\"\r\n font-size: 12px;\r\n color: #666;\r\n margin-top: 4px;\r\n border-top: 1px dashed #ccc;\r\n padding-top: 4px;\r\n \"\r\n>\r\n <small>\r\n <strong>Debug:</strong>\r\n Value: {{ fechaControl.value | json }} | Valid: {{ fechaControl.valid }} |\r\n Invalid: {{ fechaControl.invalid }} | Errors: {{ fechaControl.errors | json\r\n }} | Dirty: {{ fechaControl.dirty }} | Touched: {{ fechaControl.touched }} |\r\n Status: {{ fechaControl.status }}\r\n </small>\r\n</div>\r\n}\r\n", styles: [""], dependencies: [{ kind: "component", type: AppMessageErrorComponent, selector: "dsx-message-error", inputs: ["control", "form", "debugMode"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "component", type: FloatLabel, selector: "p-floatlabel, p-floatLabel, p-float-label", inputs: ["variant"] }, { kind: "ngmodule", type: InputMaskModule }, { kind: "directive", type: i1$8.InputMaskDirective, selector: "[pInputMask]", inputs: ["pInputMaskPT", "pInputMaskUnstyled", "pInputMask", "slotChar", "autoClear", "characterPattern", "keepBuffer"], outputs: ["onComplete", "onUnmaskedChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] });
|
|
8445
9189
|
}
|
|
8446
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type:
|
|
9190
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxdateTime, decorators: [{
|
|
8447
9191
|
type: Component,
|
|
8448
|
-
args: [{ selector: 'dsxdate-
|
|
9192
|
+
args: [{ selector: 'dsxdate-time', imports: [
|
|
9193
|
+
AppMessageErrorComponent,
|
|
8449
9194
|
CommonModule,
|
|
8450
|
-
FloatLabel,
|
|
8451
9195
|
DatePicker,
|
|
8452
|
-
|
|
8453
|
-
|
|
9196
|
+
FloatLabel,
|
|
9197
|
+
InputMaskModule,
|
|
8454
9198
|
JsonPipe,
|
|
9199
|
+
ReactiveFormsModule,
|
|
8455
9200
|
], providers: [
|
|
8456
9201
|
{
|
|
8457
9202
|
provide: NG_VALUE_ACCESSOR,
|
|
8458
|
-
useExisting: forwardRef(() =>
|
|
9203
|
+
useExisting: forwardRef(() => DsxdateTime),
|
|
8459
9204
|
multi: true,
|
|
8460
9205
|
},
|
|
8461
|
-
], template: "
|
|
8462
|
-
}], 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 }] }] } });
|
|
9206
|
+
], template: "<p-floatLabel variant=\"on\">\r\n <p-datepicker\r\n fluid\r\n appendTo=\"body\"\r\n [formControl]=\"fechaControl\"\r\n [fluid]=\"true\"\r\n [showIcon]=\"showIcon()\"\r\n [showOnFocus]=\"showOnFocus()\"\r\n [showButtonBar]=\"showButtonBar()\"\r\n [showTime]=\"showTime()\"\r\n [hourFormat]=\"hourFormat()\"\r\n [dateFormat]=\"dateFormat()\"\r\n [placeholder]=\"placeholder()\"\r\n pInputMask=\"99/99/9999 99:99\"\r\n />\r\n <label>{{ label() }}</label>\r\n</p-floatLabel>\r\n\r\n<!-- \u2705 SIN CONDICI\u00D3N - IGUAL QUE EL ORIGINAL -->\r\n<dsx-message-error [control]=\"fechaControl\" />\r\n\r\n<!-- Debug info -->\r\n@if (debugEnabled() && isDevMode) {\r\n<div\r\n class=\"debug-info\"\r\n style=\"\r\n font-size: 12px;\r\n color: #666;\r\n margin-top: 4px;\r\n border-top: 1px dashed #ccc;\r\n padding-top: 4px;\r\n \"\r\n>\r\n <small>\r\n <strong>Debug:</strong>\r\n Value: {{ fechaControl.value | json }} | Valid: {{ fechaControl.valid }} |\r\n Invalid: {{ fechaControl.invalid }} | Errors: {{ fechaControl.errors | json\r\n }} | Dirty: {{ fechaControl.dirty }} | Touched: {{ fechaControl.touched }} |\r\n Status: {{ fechaControl.status }}\r\n </small>\r\n</div>\r\n}\r\n" }]
|
|
9207
|
+
}], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], showIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcon", required: false }] }], showOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "showOnFocus", required: false }] }], showButtonBar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButtonBar", required: false }] }], showTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTime", required: false }] }], hourFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "hourFormat", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }] } });
|
|
8463
9208
|
|
|
8464
9209
|
// dsx-input-currency.ts
|
|
8465
9210
|
class DsxInputCurrency {
|
|
@@ -8702,6 +9447,10 @@ class DsxAutocomplete {
|
|
|
8702
9447
|
ngControl = null;
|
|
8703
9448
|
isDevModeInternal = isDevMode();
|
|
8704
9449
|
controlSignal = signal(null, ...(ngDevMode ? [{ debugName: "controlSignal" }] : /* istanbul ignore next */ []));
|
|
9450
|
+
// ✅ Almacenar IDs pendientes (privado)
|
|
9451
|
+
pendingIdsInternal = [];
|
|
9452
|
+
pendingValue = null;
|
|
9453
|
+
pendingLogs = [];
|
|
8705
9454
|
datasource = input.required(...(ngDevMode ? [{ debugName: "datasource" }] : /* istanbul ignore next */ []));
|
|
8706
9455
|
optionLabel = input.required(...(ngDevMode ? [{ debugName: "optionLabel" }] : /* istanbul ignore next */ []));
|
|
8707
9456
|
label = input('Seleccionar', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
@@ -8723,6 +9472,14 @@ class DsxAutocomplete {
|
|
|
8723
9472
|
get isDevMode() {
|
|
8724
9473
|
return this.isDevModeInternal;
|
|
8725
9474
|
}
|
|
9475
|
+
// ✅ Getter público para IDs pendientes (solo para debug)
|
|
9476
|
+
get pendingIds() {
|
|
9477
|
+
return this.pendingIdsInternal;
|
|
9478
|
+
}
|
|
9479
|
+
// ✅ Getter para logs de pendientes
|
|
9480
|
+
get pendingLogsInfo() {
|
|
9481
|
+
return this.pendingLogs.join(' | ');
|
|
9482
|
+
}
|
|
8726
9483
|
get formValue() {
|
|
8727
9484
|
return this.ngControl?.control?.value;
|
|
8728
9485
|
}
|
|
@@ -8773,11 +9530,19 @@ class DsxAutocomplete {
|
|
|
8773
9530
|
});
|
|
8774
9531
|
}
|
|
8775
9532
|
});
|
|
9533
|
+
// ✅ Effect mejorado para recargar cuando cambia el datasource
|
|
8776
9534
|
effect(() => {
|
|
8777
9535
|
const ds = this.datasource();
|
|
8778
|
-
if (ds.length > 0
|
|
9536
|
+
if (ds.length > 0) {
|
|
8779
9537
|
this.logDebug('📚 Datasource actualizado:', ds.length);
|
|
8780
|
-
|
|
9538
|
+
// ✅ Si hay IDs pendientes, intentar cargarlos
|
|
9539
|
+
if (this.pendingIdsInternal.length > 0) {
|
|
9540
|
+
this.logDebug('🔄 Procesando IDs pendientes:', this.pendingIdsInternal);
|
|
9541
|
+
this.procesarIdsPendientes(this.pendingIdsInternal);
|
|
9542
|
+
// ✅ No limpiamos inmediatamente para poder ver el estado en debug
|
|
9543
|
+
}
|
|
9544
|
+
// ✅ Si ya se inicializó y hay valor en el formulario, recargar
|
|
9545
|
+
if (this.initialValueSet && this.ngControl?.control?.value) {
|
|
8781
9546
|
this.logDebug('🔄 Recargando valor con nuevo datasource');
|
|
8782
9547
|
this.writeValue(this.ngControl.control.value);
|
|
8783
9548
|
}
|
|
@@ -8824,6 +9589,70 @@ class DsxAutocomplete {
|
|
|
8824
9589
|
this.lastLogTime = now;
|
|
8825
9590
|
console.log(`[DsxAutocomplete] ${message}`, data || '');
|
|
8826
9591
|
}
|
|
9592
|
+
// ✅ Método para registrar eventos de pendientes
|
|
9593
|
+
logPending(message, ids) {
|
|
9594
|
+
if (!this.shouldDebug())
|
|
9595
|
+
return;
|
|
9596
|
+
const timestamp = new Date().toISOString().substring(11, 19);
|
|
9597
|
+
const logEntry = `[${timestamp}] ${message} ${ids ? `IDs: [${ids.join(', ')}]` : ''}`;
|
|
9598
|
+
this.pendingLogs.push(logEntry);
|
|
9599
|
+
// Mantener solo los últimos 10 logs
|
|
9600
|
+
if (this.pendingLogs.length > 10) {
|
|
9601
|
+
this.pendingLogs.shift();
|
|
9602
|
+
}
|
|
9603
|
+
console.log(`[DsxAutocomplete] ⏳ PENDING: ${logEntry}`, {
|
|
9604
|
+
pendingIds: this.pendingIdsInternal,
|
|
9605
|
+
totalPending: this.pendingIdsInternal.length,
|
|
9606
|
+
});
|
|
9607
|
+
}
|
|
9608
|
+
// ✅ Nuevo método: Procesar IDs pendientes con logs mejorados
|
|
9609
|
+
procesarIdsPendientes(ids) {
|
|
9610
|
+
if (ids.length === 0) {
|
|
9611
|
+
this.logDebug('✅ No hay IDs pendientes para procesar');
|
|
9612
|
+
return;
|
|
9613
|
+
}
|
|
9614
|
+
const sourceData = this.datasource();
|
|
9615
|
+
if (sourceData.length === 0) {
|
|
9616
|
+
this.logPending('⚠️ Datasource vacío, no se pueden procesar IDs pendientes', ids);
|
|
9617
|
+
return;
|
|
9618
|
+
}
|
|
9619
|
+
this.logPending(`🔍 Procesando ${ids.length} IDs pendientes`, ids);
|
|
9620
|
+
const objetos = sourceData.filter((item) => ids.includes(Number(item[this.idKey()])));
|
|
9621
|
+
const objetosOrdenados = ids
|
|
9622
|
+
.map((id) => objetos.find((item) => Number(item[this.idKey()]) === id))
|
|
9623
|
+
.filter((item) => item !== undefined);
|
|
9624
|
+
const encontrados = objetosOrdenados.length;
|
|
9625
|
+
const noEncontrados = ids.length - encontrados;
|
|
9626
|
+
this.logPending(`✅ Procesados: ${encontrados} encontrados, ${noEncontrados} no encontrados`, ids);
|
|
9627
|
+
if (noEncontrados > 0) {
|
|
9628
|
+
const idsNoEncontrados = ids.filter((id) => !objetos.some((item) => Number(item[this.idKey()]) === id));
|
|
9629
|
+
this.logPending(`⚠️ IDs no encontrados en datasource:`, idsNoEncontrados);
|
|
9630
|
+
}
|
|
9631
|
+
// Si se encontraron todos los objetos, actualizar la vista
|
|
9632
|
+
if (encontrados > 0) {
|
|
9633
|
+
if (this.isMultiple()) {
|
|
9634
|
+
this.value = objetosOrdenados;
|
|
9635
|
+
}
|
|
9636
|
+
else {
|
|
9637
|
+
this.value = objetosOrdenados.length > 0 ? objetosOrdenados[0] : null;
|
|
9638
|
+
}
|
|
9639
|
+
this.cdr.detectChanges();
|
|
9640
|
+
// ✅ Limpiar pendientes solo si se encontraron todos
|
|
9641
|
+
if (noEncontrados === 0) {
|
|
9642
|
+
this.pendingIdsInternal = [];
|
|
9643
|
+
this.logPending('✅ Todos los IDs pendientes fueron resueltos', ids);
|
|
9644
|
+
}
|
|
9645
|
+
else {
|
|
9646
|
+
// Mantener solo los no encontrados
|
|
9647
|
+
const idsNoEncontrados = ids.filter((id) => !objetos.some((item) => Number(item[this.idKey()]) === id));
|
|
9648
|
+
this.pendingIdsInternal = idsNoEncontrados;
|
|
9649
|
+
this.logPending(`⏳ ${idsNoEncontrados.length} IDs pendientes aún no resueltos`, idsNoEncontrados);
|
|
9650
|
+
}
|
|
9651
|
+
}
|
|
9652
|
+
else {
|
|
9653
|
+
this.logPending('❌ Ningún ID pendiente pudo ser resuelto', ids);
|
|
9654
|
+
}
|
|
9655
|
+
}
|
|
8827
9656
|
searchRequisitos(event) {
|
|
8828
9657
|
const inicio = performance.now();
|
|
8829
9658
|
const label = this.optionLabel();
|
|
@@ -8862,13 +9691,10 @@ class DsxAutocomplete {
|
|
|
8862
9691
|
.replace(/\s+/g, ' ')
|
|
8863
9692
|
.trim();
|
|
8864
9693
|
}
|
|
8865
|
-
// ✨ CORREGIDO: evaluarYAgregar para manejar valores no encontrados
|
|
8866
9694
|
evaluarYAgregar(event) {
|
|
8867
9695
|
const valorInput = event.target.value?.trim();
|
|
8868
9696
|
if (!valorInput) {
|
|
8869
9697
|
this.dataFiltrada.set([]);
|
|
8870
|
-
// Si el input está vacío y hay valor seleccionado, mantenerlo
|
|
8871
|
-
// Si no hay valor, asegurar que el formulario tenga null o []
|
|
8872
9698
|
if (this.obtenerArrayValue().length === 0) {
|
|
8873
9699
|
this.actualizarFormulario([]);
|
|
8874
9700
|
}
|
|
@@ -8929,11 +9755,8 @@ class DsxAutocomplete {
|
|
|
8929
9755
|
this.logDebug('🆕 Nuevo elemento creado', valorInput);
|
|
8930
9756
|
}
|
|
8931
9757
|
else {
|
|
8932
|
-
// ✅ NO permite crear: limpiar el valor a null o []
|
|
8933
9758
|
this.logDebug('⚠️ No se encontraron coincidencias y no se permite crear', valorInput);
|
|
8934
|
-
// ✨ IMPORTANTE: Limpiar la selección a null o []
|
|
8935
9759
|
this.actualizarFormulario([]);
|
|
8936
|
-
// Marcar como touched para mostrar validación
|
|
8937
9760
|
if (this.ngControl?.control) {
|
|
8938
9761
|
this.ngControl.control.markAsTouched();
|
|
8939
9762
|
this.onTouched();
|
|
@@ -9059,9 +9882,7 @@ class DsxAutocomplete {
|
|
|
9059
9882
|
this.timeoutLimpieza = null;
|
|
9060
9883
|
}
|
|
9061
9884
|
}
|
|
9062
|
-
// ✨ MEJORADO: actualizarFormulario para manejar correctamente valores vacíos
|
|
9063
9885
|
actualizarFormulario(nuevaLista) {
|
|
9064
|
-
// Si la lista está vacía, siempre enviar null o [] según el modo
|
|
9065
9886
|
if (nuevaLista.length === 0) {
|
|
9066
9887
|
if (this.isMultiple()) {
|
|
9067
9888
|
this.value = [];
|
|
@@ -9077,7 +9898,6 @@ class DsxAutocomplete {
|
|
|
9077
9898
|
});
|
|
9078
9899
|
return;
|
|
9079
9900
|
}
|
|
9080
|
-
// Si hay elementos, procesar normalmente
|
|
9081
9901
|
if (this.isMultiple()) {
|
|
9082
9902
|
this.value = nuevaLista;
|
|
9083
9903
|
}
|
|
@@ -9096,6 +9916,7 @@ class DsxAutocomplete {
|
|
|
9096
9916
|
getFormControlName() {
|
|
9097
9917
|
return this.ngControl?.name || null;
|
|
9098
9918
|
}
|
|
9919
|
+
// ✨ MEJORADO: writeValue con manejo de IDs pendientes y logs
|
|
9099
9920
|
writeValue(value) {
|
|
9100
9921
|
const timestamp = new Date().toISOString();
|
|
9101
9922
|
this.logDebug(`🔄 writeValue INVOCADO ${timestamp}`, {
|
|
@@ -9113,10 +9934,16 @@ class DsxAutocomplete {
|
|
|
9113
9934
|
isMultiple: this.isMultiple(),
|
|
9114
9935
|
hasDatasource: this.datasource().length > 0,
|
|
9115
9936
|
datasourceSize: this.datasource().length,
|
|
9937
|
+
pendingIdsActuales: this.pendingIdsInternal,
|
|
9116
9938
|
});
|
|
9117
9939
|
if (value === null || value === undefined) {
|
|
9118
9940
|
this.logDebug(`⚠️ Valor null/undefined, limpiando selección`);
|
|
9119
9941
|
this.value = this.isMultiple() ? [] : null;
|
|
9942
|
+
if (this.pendingIdsInternal.length > 0) {
|
|
9943
|
+
this.logPending('🧹 Limpiando IDs pendientes por null/undefined', this.pendingIdsInternal);
|
|
9944
|
+
this.pendingIdsInternal = [];
|
|
9945
|
+
}
|
|
9946
|
+
this.pendingValue = null;
|
|
9120
9947
|
this.initialValueSet = true;
|
|
9121
9948
|
this.cdr.markForCheck();
|
|
9122
9949
|
this.cdr.detectChanges();
|
|
@@ -9127,6 +9954,11 @@ class DsxAutocomplete {
|
|
|
9127
9954
|
if (Array.isArray(value)) {
|
|
9128
9955
|
if (value.length === 0) {
|
|
9129
9956
|
this.value = this.isMultiple() ? [] : null;
|
|
9957
|
+
if (this.pendingIdsInternal.length > 0) {
|
|
9958
|
+
this.logPending('🧹 Limpiando IDs pendientes por array vacío', this.pendingIdsInternal);
|
|
9959
|
+
this.pendingIdsInternal = [];
|
|
9960
|
+
}
|
|
9961
|
+
this.pendingValue = null;
|
|
9130
9962
|
this.initialValueSet = true;
|
|
9131
9963
|
this.cdr.markForCheck();
|
|
9132
9964
|
this.cdr.detectChanges();
|
|
@@ -9134,41 +9966,73 @@ class DsxAutocomplete {
|
|
|
9134
9966
|
}
|
|
9135
9967
|
if (typeof value[0] === 'number') {
|
|
9136
9968
|
ids = value;
|
|
9969
|
+
this.logDebug(`📊 Recibidos ${ids.length} IDs:`, ids);
|
|
9137
9970
|
}
|
|
9138
9971
|
else if (typeof value[0] === 'object' && value[0] !== null) {
|
|
9139
9972
|
items = value;
|
|
9973
|
+
this.logDebug(`📊 Recibidos ${items.length} objetos:`, items.map((i) => i[this.optionLabel()]));
|
|
9140
9974
|
}
|
|
9141
9975
|
}
|
|
9142
9976
|
else if (typeof value === 'number') {
|
|
9143
9977
|
ids = [value];
|
|
9978
|
+
this.logDebug(`📊 Recibido 1 ID:`, [value]);
|
|
9144
9979
|
}
|
|
9145
9980
|
else if (typeof value === 'object' && value !== null) {
|
|
9146
9981
|
items = [value];
|
|
9982
|
+
this.logDebug(`📊 Recibido 1 objeto:`, value[this.optionLabel()]);
|
|
9147
9983
|
}
|
|
9148
9984
|
else {
|
|
9149
9985
|
this.value = this.isMultiple() ? [] : null;
|
|
9986
|
+
if (this.pendingIdsInternal.length > 0) {
|
|
9987
|
+
this.logPending('🧹 Limpiando IDs pendientes por tipo no soportado', this.pendingIdsInternal);
|
|
9988
|
+
this.pendingIdsInternal = [];
|
|
9989
|
+
}
|
|
9990
|
+
this.pendingValue = null;
|
|
9150
9991
|
this.initialValueSet = true;
|
|
9151
9992
|
this.cdr.markForCheck();
|
|
9152
9993
|
this.cdr.detectChanges();
|
|
9153
9994
|
return;
|
|
9154
9995
|
}
|
|
9155
9996
|
const sourceData = this.datasource();
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9997
|
+
// ✅ Si tenemos IDs y el datasource está vacío, guardarlos para después
|
|
9998
|
+
if (ids.length > 0 && sourceData.length === 0) {
|
|
9999
|
+
this.logPending(`⏳ Datasource vacío (${sourceData.length}), guardando ${ids.length} IDs para procesar después`, ids);
|
|
10000
|
+
this.pendingIdsInternal = ids;
|
|
10001
|
+
this.pendingValue = value;
|
|
10002
|
+
this.initialValueSet = true;
|
|
10003
|
+
// ✅ Mostrar un placeholder visual mientras se cargan los datos
|
|
10004
|
+
this.value = this.isMultiple() ? [] : null;
|
|
10005
|
+
this.cdr.markForCheck();
|
|
10006
|
+
this.cdr.detectChanges();
|
|
10007
|
+
this.logDebug(`⏳ IDs guardados como pendientes:`, this.pendingIdsInternal);
|
|
10008
|
+
return;
|
|
10009
|
+
}
|
|
10010
|
+
// ✅ Si tenemos IDs y el datasource existe, buscar los objetos
|
|
10011
|
+
if (ids.length > 0 && sourceData.length > 0) {
|
|
10012
|
+
this.logDebug(`🔍 Buscando ${ids.length} IDs en datasource de ${sourceData.length} elementos`);
|
|
9165
10013
|
const objetos = sourceData.filter((item) => ids.includes(Number(item[this.idKey()])));
|
|
9166
10014
|
const objetosOrdenados = ids
|
|
9167
10015
|
.map((id) => objetos.find((item) => Number(item[this.idKey()]) === id))
|
|
9168
10016
|
.filter((item) => item !== undefined);
|
|
9169
10017
|
items = objetosOrdenados;
|
|
10018
|
+
// ✅ Si no se encontraron algunos IDs, guardarlos como pendientes
|
|
10019
|
+
const idsNoEncontrados = ids.filter((id) => !objetos.some((item) => Number(item[this.idKey()]) === id));
|
|
10020
|
+
if (idsNoEncontrados.length > 0) {
|
|
10021
|
+
this.logPending(`⚠️ ${idsNoEncontrados.length} IDs no encontrados en datasource`, idsNoEncontrados);
|
|
10022
|
+
this.pendingIdsInternal = idsNoEncontrados;
|
|
10023
|
+
this.pendingValue = value;
|
|
10024
|
+
}
|
|
10025
|
+
else {
|
|
10026
|
+
// ✅ Todos los IDs fueron encontrados
|
|
10027
|
+
if (this.pendingIdsInternal.length > 0) {
|
|
10028
|
+
this.logPending('✅ Todos los IDs pendientes fueron resueltos', this.pendingIdsInternal);
|
|
10029
|
+
this.pendingIdsInternal = [];
|
|
10030
|
+
this.pendingValue = null;
|
|
10031
|
+
}
|
|
10032
|
+
}
|
|
9170
10033
|
this.logDebug(`🔍 Encontrados ${items.length} de ${ids.length} IDs`);
|
|
9171
10034
|
}
|
|
10035
|
+
// ✅ Verificar que los items existan en el datasource
|
|
9172
10036
|
if (items.length > 0 && sourceData.length > 0) {
|
|
9173
10037
|
const itemsValidos = items.filter((item) => sourceData.some((s) => s[this.idKey()] === item[this.idKey()]));
|
|
9174
10038
|
if (itemsValidos.length !== items.length) {
|
|
@@ -9179,6 +10043,7 @@ class DsxAutocomplete {
|
|
|
9179
10043
|
items = itemsValidos;
|
|
9180
10044
|
}
|
|
9181
10045
|
}
|
|
10046
|
+
// ✅ Asignar el valor
|
|
9182
10047
|
if (this.isMultiple()) {
|
|
9183
10048
|
this.value = items;
|
|
9184
10049
|
}
|
|
@@ -9188,6 +10053,9 @@ class DsxAutocomplete {
|
|
|
9188
10053
|
this.initialValueSet = true;
|
|
9189
10054
|
this.cdr.detectChanges();
|
|
9190
10055
|
this.logDebug(`✅ Vista actualizada con ${items.length} elementos (${this.isMultiple() ? 'multiple' : 'single'})`);
|
|
10056
|
+
if (this.pendingIdsInternal.length > 0) {
|
|
10057
|
+
this.logDebug(`⏳ IDs pendientes actuales:`, this.pendingIdsInternal);
|
|
10058
|
+
}
|
|
9191
10059
|
}
|
|
9192
10060
|
forceReload() {
|
|
9193
10061
|
this.logDebug('🔄 Forzando recarga');
|
|
@@ -9214,7 +10082,7 @@ class DsxAutocomplete {
|
|
|
9214
10082
|
useExisting: forwardRef(() => DsxAutocomplete),
|
|
9215
10083
|
multi: true,
|
|
9216
10084
|
},
|
|
9217
|
-
], ngImport: i0, template: "<p-floatlabel variant=\"on\">\r\n <p-autoComplete\r\n fluid\r\n
|
|
10085
|
+
], ngImport: i0, template: "<p-floatlabel variant=\"on\">\r\n <p-autoComplete\r\n fluid\r\n [multiple]=\"isMultiple()\"\r\n [dropdown]=\"true\"\r\n [showClear]=\"true\"\r\n [suggestions]=\"dataFiltrada()\"\r\n (completeMethod)=\"searchRequisitos($event)\"\r\n [(ngModel)]=\"value\"\r\n (ngModelChange)=\"onNgModelChange($event)\"\r\n (onBlur)=\"onTouched()\"\r\n (onShow)=\"onShow()\"\r\n (onHide)=\"onHide()\"\r\n [disabled]=\"disabled\"\r\n [optionLabel]=\"optionLabel()\"\r\n [dataKey]=\"dataKey() || idKey()\"\r\n (keyup.enter)=\"evaluarYAgregar($event)\"\r\n [delay]=\"delay()\"\r\n placeholder=\"Ingresa un texto....\"\r\n [inputStyle]=\"{'text-transform': 'uppercase'}\"\r\n >\r\n <ng-template let-item pTemplate=\"selectedItem\">\r\n {{ item[optionLabel()] }}\r\n </ng-template>\r\n\r\n <ng-template let-item pTemplate=\"item\">\r\n {{ item[optionLabel()] }}\r\n </ng-template>\r\n </p-autoComplete>\r\n <label>{{ label() }}</label>\r\n</p-floatlabel>\r\n\r\n<!-- \u2728 Indicador de carga (cuando hay IDs pendientes) -->\r\n@if (pendingIds.length > 0 && debug() && isDevMode) {\r\n<div\r\n style=\"\r\n font-size: 10px;\r\n color: #f59e0b;\r\n margin-top: 2px;\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n \"\r\n>\r\n <span>\u23F3</span>\r\n <span\r\n >Cargando datos pendientes ({{ pendingIds.length }} IDs: [{{\r\n pendingIds.join(', ') }}])</span\r\n >\r\n</div>\r\n}\r\n\r\n<!-- \u2728 Bot\u00F3n de limpiar para modo single -->\r\n@if (!isMultiple() && value) {\r\n<button\r\n type=\"button\"\r\n (click)=\"clearSelection()\"\r\n style=\"\r\n position: absolute;\r\n right: 10px;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n background: transparent;\r\n border: none;\r\n cursor: pointer;\r\n color: #999;\r\n font-size: 14px;\r\n z-index: 1;\r\n \"\r\n aria-label=\"Limpiar selecci\u00F3n\"\r\n>\r\n \u2715\r\n</button>\r\n}\r\n\r\n<!-- Mensaje de error -->\r\n@if (showError() && errorControl()) {\r\n<dsx-message-error [control]=\"errorControl()\" />\r\n}\r\n\r\n<!-- Panel de Debug -->\r\n@if (debug() && isDevMode) {\r\n<div\r\n style=\"\r\n margin-top: 8px;\r\n font-size: 11px;\r\n color: #666;\r\n background: #f5f5f5;\r\n padding: 6px 10px;\r\n border-radius: 4px;\r\n font-family: monospace;\r\n border: 1px solid #ddd;\r\n \"\r\n>\r\n <div\r\n style=\"\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n \"\r\n >\r\n <div>\r\n <strong>\uD83D\uDD0D Debug:</strong>\r\n <span style=\"margin-left: 8px\">Items: {{ getValueLength() }}</span>\r\n <span style=\"margin-left: 8px\">IDs: {{ getValueDisplay('id') }}</span>\r\n <span style=\"margin-left: 8px\"\r\n >Nombres: {{ getValueDisplay('nombre') }}</span\r\n >\r\n <span style=\"margin-left: 8px; color: #888; font-size: 10px\">\r\n | returnID: {{ returnID() ? '\u2705 IDs' : '\u274C Objetos' }} | Multiple: {{\r\n isMultiple() ? '\u2705' : '\u274C' }} | Control: {{ controlName || 'sin nombre'\r\n }}\r\n </span>\r\n @if (pendingIds.length > 0) {\r\n <span style=\"margin-left: 8px; color: #f59e0b; font-weight: bold\">\r\n | \u23F3 Pendientes: {{ pendingIds.length }} ({{ pendingIds.join(', ') }})\r\n </span>\r\n } @else {\r\n <span style=\"margin-left: 8px; color: #10b981; font-weight: bold\">\r\n | \u2705 Sin pendientes\r\n </span>\r\n }\r\n <span style=\"margin-left: 8px; color: #888; font-size: 9px\">\r\n | Logs: {{ pendingLogsInfo }}\r\n </span>\r\n </div>\r\n <div\r\n style=\"\r\n font-size: 10px;\r\n color: #333;\r\n background: #e8e8e8;\r\n padding: 2px 8px;\r\n border-radius: 4px;\r\n \"\r\n >\r\n <strong>\uD83D\uDCCA Validaci\u00F3n:</strong>\r\n <span [style.color]=\"controlValid ? 'green' : 'red'\">\r\n {{ controlValid ? '\u2705 V\u00E1lido' : '\u274C Inv\u00E1lido' }}\r\n </span>\r\n <span style=\"margin-left: 4px\">\r\n | Touched: {{ controlTouched ? '\u2705' : '\u274C' }}\r\n </span>\r\n <span style=\"margin-left: 4px\">\r\n | Dirty: {{ controlDirty ? '\u2705' : '\u274C' }}\r\n </span>\r\n @if (controlErrors) {\r\n <span style=\"margin-left: 4px; color: red\">\r\n | Errors: {{ controlErrors | json }}\r\n </span>\r\n }\r\n </div>\r\n <button\r\n (click)=\"forceReload()\"\r\n style=\"\r\n font-size: 10px;\r\n padding: 2px 8px;\r\n border-radius: 4px;\r\n border: 1px solid #ccc;\r\n background: white;\r\n cursor: pointer;\r\n \"\r\n type=\"button\"\r\n >\r\n \uD83D\uDD04 Recargar\r\n </button>\r\n </div>\r\n</div>\r\n}\r\n", styles: [""], dependencies: [{ kind: "component", type: AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FloatLabel, selector: "p-floatlabel, p-floatLabel, p-float-label", inputs: ["variant"] }, { kind: "component", type: AppMessageErrorComponent, selector: "dsx-message-error", inputs: ["control", "form", "debugMode"] }, { kind: "pipe", type: JsonPipe, name: "json" }] });
|
|
9218
10086
|
}
|
|
9219
10087
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxAutocomplete, decorators: [{
|
|
9220
10088
|
type: Component,
|
|
@@ -9230,7 +10098,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
9230
10098
|
useExisting: forwardRef(() => DsxAutocomplete),
|
|
9231
10099
|
multi: true,
|
|
9232
10100
|
},
|
|
9233
|
-
], template: "<p-floatlabel variant=\"on\">\r\n <p-autoComplete\r\n fluid\r\n
|
|
10101
|
+
], template: "<p-floatlabel variant=\"on\">\r\n <p-autoComplete\r\n fluid\r\n [multiple]=\"isMultiple()\"\r\n [dropdown]=\"true\"\r\n [showClear]=\"true\"\r\n [suggestions]=\"dataFiltrada()\"\r\n (completeMethod)=\"searchRequisitos($event)\"\r\n [(ngModel)]=\"value\"\r\n (ngModelChange)=\"onNgModelChange($event)\"\r\n (onBlur)=\"onTouched()\"\r\n (onShow)=\"onShow()\"\r\n (onHide)=\"onHide()\"\r\n [disabled]=\"disabled\"\r\n [optionLabel]=\"optionLabel()\"\r\n [dataKey]=\"dataKey() || idKey()\"\r\n (keyup.enter)=\"evaluarYAgregar($event)\"\r\n [delay]=\"delay()\"\r\n placeholder=\"Ingresa un texto....\"\r\n [inputStyle]=\"{'text-transform': 'uppercase'}\"\r\n >\r\n <ng-template let-item pTemplate=\"selectedItem\">\r\n {{ item[optionLabel()] }}\r\n </ng-template>\r\n\r\n <ng-template let-item pTemplate=\"item\">\r\n {{ item[optionLabel()] }}\r\n </ng-template>\r\n </p-autoComplete>\r\n <label>{{ label() }}</label>\r\n</p-floatlabel>\r\n\r\n<!-- \u2728 Indicador de carga (cuando hay IDs pendientes) -->\r\n@if (pendingIds.length > 0 && debug() && isDevMode) {\r\n<div\r\n style=\"\r\n font-size: 10px;\r\n color: #f59e0b;\r\n margin-top: 2px;\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n \"\r\n>\r\n <span>\u23F3</span>\r\n <span\r\n >Cargando datos pendientes ({{ pendingIds.length }} IDs: [{{\r\n pendingIds.join(', ') }}])</span\r\n >\r\n</div>\r\n}\r\n\r\n<!-- \u2728 Bot\u00F3n de limpiar para modo single -->\r\n@if (!isMultiple() && value) {\r\n<button\r\n type=\"button\"\r\n (click)=\"clearSelection()\"\r\n style=\"\r\n position: absolute;\r\n right: 10px;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n background: transparent;\r\n border: none;\r\n cursor: pointer;\r\n color: #999;\r\n font-size: 14px;\r\n z-index: 1;\r\n \"\r\n aria-label=\"Limpiar selecci\u00F3n\"\r\n>\r\n \u2715\r\n</button>\r\n}\r\n\r\n<!-- Mensaje de error -->\r\n@if (showError() && errorControl()) {\r\n<dsx-message-error [control]=\"errorControl()\" />\r\n}\r\n\r\n<!-- Panel de Debug -->\r\n@if (debug() && isDevMode) {\r\n<div\r\n style=\"\r\n margin-top: 8px;\r\n font-size: 11px;\r\n color: #666;\r\n background: #f5f5f5;\r\n padding: 6px 10px;\r\n border-radius: 4px;\r\n font-family: monospace;\r\n border: 1px solid #ddd;\r\n \"\r\n>\r\n <div\r\n style=\"\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n \"\r\n >\r\n <div>\r\n <strong>\uD83D\uDD0D Debug:</strong>\r\n <span style=\"margin-left: 8px\">Items: {{ getValueLength() }}</span>\r\n <span style=\"margin-left: 8px\">IDs: {{ getValueDisplay('id') }}</span>\r\n <span style=\"margin-left: 8px\"\r\n >Nombres: {{ getValueDisplay('nombre') }}</span\r\n >\r\n <span style=\"margin-left: 8px; color: #888; font-size: 10px\">\r\n | returnID: {{ returnID() ? '\u2705 IDs' : '\u274C Objetos' }} | Multiple: {{\r\n isMultiple() ? '\u2705' : '\u274C' }} | Control: {{ controlName || 'sin nombre'\r\n }}\r\n </span>\r\n @if (pendingIds.length > 0) {\r\n <span style=\"margin-left: 8px; color: #f59e0b; font-weight: bold\">\r\n | \u23F3 Pendientes: {{ pendingIds.length }} ({{ pendingIds.join(', ') }})\r\n </span>\r\n } @else {\r\n <span style=\"margin-left: 8px; color: #10b981; font-weight: bold\">\r\n | \u2705 Sin pendientes\r\n </span>\r\n }\r\n <span style=\"margin-left: 8px; color: #888; font-size: 9px\">\r\n | Logs: {{ pendingLogsInfo }}\r\n </span>\r\n </div>\r\n <div\r\n style=\"\r\n font-size: 10px;\r\n color: #333;\r\n background: #e8e8e8;\r\n padding: 2px 8px;\r\n border-radius: 4px;\r\n \"\r\n >\r\n <strong>\uD83D\uDCCA Validaci\u00F3n:</strong>\r\n <span [style.color]=\"controlValid ? 'green' : 'red'\">\r\n {{ controlValid ? '\u2705 V\u00E1lido' : '\u274C Inv\u00E1lido' }}\r\n </span>\r\n <span style=\"margin-left: 4px\">\r\n | Touched: {{ controlTouched ? '\u2705' : '\u274C' }}\r\n </span>\r\n <span style=\"margin-left: 4px\">\r\n | Dirty: {{ controlDirty ? '\u2705' : '\u274C' }}\r\n </span>\r\n @if (controlErrors) {\r\n <span style=\"margin-left: 4px; color: red\">\r\n | Errors: {{ controlErrors | json }}\r\n </span>\r\n }\r\n </div>\r\n <button\r\n (click)=\"forceReload()\"\r\n style=\"\r\n font-size: 10px;\r\n padding: 2px 8px;\r\n border-radius: 4px;\r\n border: 1px solid #ccc;\r\n background: white;\r\n cursor: pointer;\r\n \"\r\n type=\"button\"\r\n >\r\n \uD83D\uDD04 Recargar\r\n </button>\r\n </div>\r\n</div>\r\n}\r\n" }]
|
|
9234
10102
|
}], ctorParameters: () => [], propDecorators: { datasource: [{ type: i0.Input, args: [{ isSignal: true, alias: "datasource", required: true }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], delay: [{ type: i0.Input, args: [{ isSignal: true, alias: "delay", required: false }] }], idKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "idKey", required: true }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: false }] }], permitirCrear: [{ type: i0.Input, args: [{ isSignal: true, alias: "permitirCrear", required: false }] }], isMultiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMultiple", required: false }] }], debug: [{ type: i0.Input, args: [{ isSignal: true, alias: "debug", required: false }] }], factoryNuevoRegistro: [{ type: i0.Input, args: [{ isSignal: true, alias: "factoryNuevoRegistro", required: false }] }], returnID: [{ type: i0.Input, args: [{ isSignal: true, alias: "returnID", required: false }] }], showError: [{ type: i0.Input, args: [{ isSignal: true, alias: "showError", required: false }] }] } });
|
|
9235
10103
|
|
|
9236
10104
|
// dsx-select.component.ts
|
|
@@ -9823,35 +10691,210 @@ class ResultFileService {
|
|
|
9823
10691
|
* FileContents|fileContents, FileDownloadName|fileDownloadName,
|
|
9824
10692
|
* ContentType|contentType, title y message.
|
|
9825
10693
|
*
|
|
9826
|
-
* Si el backend no cumple alguno de estos formatos, la conversión puede fallar
|
|
9827
|
-
* y el cliente recibirá data: null con isSuccess: false.
|
|
9828
|
-
*/
|
|
9829
|
-
/**
|
|
9830
|
-
* Procesa un ServiceResult<FileData> para visualizar o descargar.
|
|
9831
|
-
*/
|
|
9832
|
-
processFileResult(result, visualizar = false) {
|
|
9833
|
-
if (!result.isSuccess || !result.data?.blob) {
|
|
9834
|
-
this._alertaService.alertaHtmlSuccess(result.title, result.message, {
|
|
9835
|
-
icon: 'question',
|
|
9836
|
-
icono: '',
|
|
9837
|
-
showConfirmButton: true,
|
|
9838
|
-
});
|
|
9839
|
-
return;
|
|
9840
|
-
}
|
|
9841
|
-
const { blob, fileName } = result.data;
|
|
9842
|
-
if (visualizar) {
|
|
9843
|
-
this.PdfView(blob);
|
|
9844
|
-
return;
|
|
9845
|
-
}
|
|
9846
|
-
this.descargarBlob(blob, fileName);
|
|
9847
|
-
}
|
|
9848
|
-
/**
|
|
9849
|
-
* Realiza
|
|
10694
|
+
* Si el backend no cumple alguno de estos formatos, la conversión puede fallar
|
|
10695
|
+
* y el cliente recibirá data: null con isSuccess: false.
|
|
10696
|
+
*/
|
|
10697
|
+
/**
|
|
10698
|
+
* Procesa un ServiceResult<FileData> para visualizar o descargar.
|
|
10699
|
+
*/
|
|
10700
|
+
processFileResult(result, visualizar = false) {
|
|
10701
|
+
if (!result.isSuccess || !result.data?.blob) {
|
|
10702
|
+
this._alertaService.alertaHtmlSuccess(result.title, result.message, {
|
|
10703
|
+
icon: 'question',
|
|
10704
|
+
icono: '',
|
|
10705
|
+
showConfirmButton: true,
|
|
10706
|
+
});
|
|
10707
|
+
return;
|
|
10708
|
+
}
|
|
10709
|
+
const { blob, fileName } = result.data;
|
|
10710
|
+
if (visualizar) {
|
|
10711
|
+
this.PdfView(blob);
|
|
10712
|
+
return;
|
|
10713
|
+
}
|
|
10714
|
+
this.descargarBlob(blob, fileName);
|
|
10715
|
+
}
|
|
10716
|
+
/**
|
|
10717
|
+
* Realiza una petición GET para descargar un archivo y normaliza la respuesta a un ServiceResult<FileData>.
|
|
10718
|
+
*
|
|
10719
|
+
* 📋 **Descripción General:**
|
|
10720
|
+
* Este método unifica la forma en que se manejan las descargas de archivos, soportando múltiples
|
|
10721
|
+
* formatos de respuesta del backend y normalizándolos a una estructura consistente.
|
|
10722
|
+
*
|
|
10723
|
+
* 🔄 **Formatos de Respuesta Soportados:**
|
|
10724
|
+
*
|
|
10725
|
+
* 1. **Binario Directo** (Recomendado):
|
|
10726
|
+
* - El backend retorna directamente el archivo binario.
|
|
10727
|
+
* - El nombre del archivo se obtiene del header `Content-Disposition`.
|
|
10728
|
+
* - Mensajes opcionales via headers: `x-title`, `x-message`, `title`, `message`.
|
|
10729
|
+
* - Ejemplo:
|
|
10730
|
+
* ```
|
|
10731
|
+
* Content-Type: application/pdf
|
|
10732
|
+
* Content-Disposition: attachment; filename="documento.pdf"
|
|
10733
|
+
* x-title: Éxito
|
|
10734
|
+
* x-message: Descarga completada exitosamente
|
|
10735
|
+
*
|
|
10736
|
+
* [bytes del archivo]
|
|
10737
|
+
* ```
|
|
10738
|
+
*
|
|
10739
|
+
* 2. **JSON Serializado con ServiceResult**:
|
|
10740
|
+
* - El backend retorna un JSON con estructura ServiceResult<FileContentResult>.
|
|
10741
|
+
* - El archivo viene codificado en Base64 o como array de bytes.
|
|
10742
|
+
* - Soporta campos en PascalCase o camelCase.
|
|
10743
|
+
* - Ejemplo:
|
|
10744
|
+
* ```json
|
|
10745
|
+
* {
|
|
10746
|
+
* "isSuccess": true,
|
|
10747
|
+
* "title": "Éxito",
|
|
10748
|
+
* "message": "Archivo generado correctamente",
|
|
10749
|
+
* "data": {
|
|
10750
|
+
* "fileContents": "base64EncodedString...",
|
|
10751
|
+
* "fileDownloadName": "reporte.xlsx",
|
|
10752
|
+
* "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
10753
|
+
* }
|
|
10754
|
+
* }
|
|
10755
|
+
* ```
|
|
10756
|
+
*
|
|
10757
|
+
* 3. **Error del Backend**:
|
|
10758
|
+
* - Retorna un ServiceResult con isSuccess: false.
|
|
10759
|
+
* - Se extrae el título y mensaje de error.
|
|
10760
|
+
* - Ejemplo:
|
|
10761
|
+
* ```json
|
|
10762
|
+
* {
|
|
10763
|
+
* "isSuccess": false,
|
|
10764
|
+
* "title": "Error de Validación",
|
|
10765
|
+
* "message": "El ID del documento no existe"
|
|
10766
|
+
* }
|
|
10767
|
+
* ```
|
|
10768
|
+
*
|
|
10769
|
+
* 4. **Error de Red/HTTP**:
|
|
10770
|
+
* - Captura errores de conexión, timeout, etc.
|
|
10771
|
+
* - Retorna un ServiceResult con isSuccess: false y mensaje genérico.
|
|
10772
|
+
*
|
|
10773
|
+
* 🎯 **Método de Uso Recomendado:**
|
|
10774
|
+
* ```typescript
|
|
10775
|
+
* // Opción 1: Descarga automática
|
|
10776
|
+
* this.resultFileService.getDownloadableFile('/api/documentos/123')
|
|
10777
|
+
* .subscribe(result => {
|
|
10778
|
+
* this.resultFileService.processFileResult(result, false);
|
|
10779
|
+
* });
|
|
10780
|
+
*
|
|
10781
|
+
* // Opción 2: Visualización de PDF
|
|
10782
|
+
* this.resultFileService.getDownloadableFile('/api/documentos/123')
|
|
10783
|
+
* .subscribe(result => {
|
|
10784
|
+
* this.resultFileService.processFileResult(result, true);
|
|
10785
|
+
* });
|
|
10786
|
+
*
|
|
10787
|
+
* // Opción 3: Procesamiento personalizado
|
|
10788
|
+
* this.resultFileService.getDownloadableFile('/api/documentos/123')
|
|
10789
|
+
* .subscribe({
|
|
10790
|
+
* next: (result) => {
|
|
10791
|
+
* if (result.isSuccess && result.data?.blob) {
|
|
10792
|
+
* // Procesar el blob manualmente
|
|
10793
|
+
* const { blob, fileName } = result.data;
|
|
10794
|
+
* // Hacer algo con el archivo
|
|
10795
|
+
* } else {
|
|
10796
|
+
* // Manejar error
|
|
10797
|
+
* console.error(result.title, result.message);
|
|
10798
|
+
* }
|
|
10799
|
+
* },
|
|
10800
|
+
* error: (error) => {
|
|
10801
|
+
* console.error('Error de red:', error);
|
|
10802
|
+
* }
|
|
10803
|
+
* });
|
|
10804
|
+
* ```
|
|
10805
|
+
*
|
|
10806
|
+
* 📝 **Parámetros:**
|
|
10807
|
+
* @param url - URL del endpoint de descarga.
|
|
10808
|
+
* @param options - Opciones adicionales para la solicitud.
|
|
10809
|
+
* @param options.params - Parámetros de consulta (Query params). Soporta:
|
|
10810
|
+
* - `string`: valor simple
|
|
10811
|
+
* - `string[]`: múltiples valores para el mismo parámetro
|
|
10812
|
+
* - `Date`: se convierte automáticamente a ISO string
|
|
10813
|
+
* - `number`: se convierte a string
|
|
10814
|
+
* - `HttpParams`: instancia directamente
|
|
10815
|
+
* @param options.headers - Headers HTTP personalizados (HttpHeaders).
|
|
10816
|
+
* @param options.withCredentials - Enviar cookies/credenciales en la solicitud.
|
|
10817
|
+
*
|
|
10818
|
+
* 🔍 **Estructura de Retorno:**
|
|
10819
|
+
* ```typescript
|
|
10820
|
+
* Observable<ServiceResult<FileData>>
|
|
10821
|
+
* ```
|
|
10822
|
+
*
|
|
10823
|
+
* Donde `ServiceResult<FileData>` tiene:
|
|
10824
|
+
* ```typescript
|
|
10825
|
+
* {
|
|
10826
|
+
* data: {
|
|
10827
|
+
* blob: Blob, // El archivo como Blob
|
|
10828
|
+
* fileName: string // Nombre del archivo (con extensión)
|
|
10829
|
+
* } | null,
|
|
10830
|
+
* isSuccess: boolean, // Indica si la operación fue exitosa
|
|
10831
|
+
* title: string, // Título del resultado (éxito o error)
|
|
10832
|
+
* message: string // Mensaje descriptivo
|
|
10833
|
+
* }
|
|
10834
|
+
* ```
|
|
10835
|
+
*
|
|
10836
|
+
* ⚠️ **Notas Importantes:**
|
|
10837
|
+
* - El método siempre retorna un Observable que emite un ServiceResult.
|
|
10838
|
+
* - Nunca emite error en el Observable (los errores se convierten a ServiceResult con isSuccess: false).
|
|
10839
|
+
* - Para descarga automática usar `processFileResult(result, false)`.
|
|
10840
|
+
* - Para visualización de PDF usar `processFileResult(result, true)`.
|
|
10841
|
+
* - El nombre del archivo se determina en este orden de prioridad:
|
|
10842
|
+
* 1. `Content-Disposition` header (si existe)
|
|
10843
|
+
* 2. `fileDownloadName` del JSON (si es respuesta JSON)
|
|
10844
|
+
* 3. `fileName` del JSON (si es respuesta JSON)
|
|
10845
|
+
* 4. Valor por defecto: 'document'
|
|
10846
|
+
*
|
|
10847
|
+
* 💡 **Ejemplos de Uso Adicionales:**
|
|
10848
|
+
*
|
|
10849
|
+
* ```typescript
|
|
10850
|
+
* // Con parámetros de consulta
|
|
10851
|
+
* const options = {
|
|
10852
|
+
* params: {
|
|
10853
|
+
* id: 123,
|
|
10854
|
+
* formato: 'pdf',
|
|
10855
|
+
* fechas: ['2024-01-01', '2024-12-31']
|
|
10856
|
+
* }
|
|
10857
|
+
* };
|
|
10858
|
+
* this.resultFileService.getDownloadableFile('/api/reportes', options)
|
|
10859
|
+
* .subscribe(result => {
|
|
10860
|
+
* this.resultFileService.processFileResult(result);
|
|
10861
|
+
* });
|
|
10862
|
+
*
|
|
10863
|
+
* // Con headers personalizados (ej: autenticación)
|
|
10864
|
+
* const options = {
|
|
10865
|
+
* headers: new HttpHeaders({
|
|
10866
|
+
* 'Authorization': 'Bearer ' + token,
|
|
10867
|
+
* 'X-Custom-Header': 'valor'
|
|
10868
|
+
* })
|
|
10869
|
+
* };
|
|
10870
|
+
* this.resultFileService.getDownloadableFile('/api/documentos', options)
|
|
10871
|
+
* .subscribe(result => {
|
|
10872
|
+
* this.resultFileService.processFileResult(result);
|
|
10873
|
+
* });
|
|
10874
|
+
*
|
|
10875
|
+
* // Con credenciales
|
|
10876
|
+
* const options = {
|
|
10877
|
+
* withCredentials: true
|
|
10878
|
+
* };
|
|
10879
|
+
* this.resultFileService.getDownloadableFile('/api/documentos', options)
|
|
10880
|
+
* .subscribe(result => {
|
|
10881
|
+
* this.resultFileService.processFileResult(result);
|
|
10882
|
+
* });
|
|
10883
|
+
* ```
|
|
10884
|
+
*
|
|
10885
|
+
* 🚀 **Flujo de Trabajo Típico:**
|
|
10886
|
+
* 1. Llamar a `getDownloadableFile(url, options)` con la URL del endpoint.
|
|
10887
|
+
* 2. Suscribirse al Observable.
|
|
10888
|
+
* 3. En el `next`, recibir el `ServiceResult<FileData>`.
|
|
10889
|
+
* 4. Usar `processFileResult(result, visualizar)` para:
|
|
10890
|
+
* - Descargar automáticamente (visualizar: false)
|
|
10891
|
+
* - Visualizar PDF en nueva pestaña (visualizar: true)
|
|
10892
|
+
* 5. Alternativamente, procesar el blob manualmente si se necesita.
|
|
9850
10893
|
*
|
|
9851
|
-
*
|
|
9852
|
-
* -
|
|
9853
|
-
* -
|
|
9854
|
-
*
|
|
10894
|
+
* @see processFileResult - Para descargar o visualizar el archivo obtenido.
|
|
10895
|
+
* @see FileData - Estructura de datos del archivo.
|
|
10896
|
+
* @see BlobRequestOptions - Opciones de configuración para la solicitud.
|
|
10897
|
+
* @see ServiceResult - Estructura de resultado estandarizada.
|
|
9855
10898
|
*/
|
|
9856
10899
|
getDownloadableFile(url, options = {}) {
|
|
9857
10900
|
return this.http
|
|
@@ -10056,25 +11099,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
10056
11099
|
}] });
|
|
10057
11100
|
|
|
10058
11101
|
class SweetAlert2DialogService {
|
|
10059
|
-
/**
|
|
10060
|
-
* Configuración del entorno inyectada (opcional).
|
|
10061
|
-
* Se usa para obtener el tema global de SweetAlert2 desde el environment.
|
|
10062
|
-
*/
|
|
10063
11102
|
environment = inject(ENVIRONMENT, { optional: true });
|
|
10064
|
-
/**
|
|
10065
|
-
* Tema visual por defecto para todas las alertas SweetAlert2.
|
|
10066
|
-
* Se inicializa desde el environment si está configurado, o puede establecerse
|
|
10067
|
-
* manualmente llamando a setDefaultTheme().
|
|
10068
|
-
*
|
|
10069
|
-
* **Prioridad de temas:**
|
|
10070
|
-
* 1. Tema específico pasado en options de cada alerta
|
|
10071
|
-
* 2. Tema establecido con setDefaultTheme()
|
|
10072
|
-
* 3. Tema configurado en environment.sweetAlertTheme
|
|
10073
|
-
* 4. Tema por defecto de SweetAlert2
|
|
10074
|
-
*/
|
|
10075
11103
|
defaultTheme = undefined;
|
|
11104
|
+
NATIVE_ICONS = [
|
|
11105
|
+
'success',
|
|
11106
|
+
'error',
|
|
11107
|
+
'warning',
|
|
11108
|
+
'info',
|
|
11109
|
+
'question',
|
|
11110
|
+
];
|
|
10076
11111
|
constructor() {
|
|
10077
|
-
// Inicializar el tema desde el environment si está disponible
|
|
10078
11112
|
if (this.environment?.sweetAlertTheme) {
|
|
10079
11113
|
const allowed = SWEET_ALERT_THEMES;
|
|
10080
11114
|
const themeValue = this.environment.sweetAlertTheme;
|
|
@@ -10094,59 +11128,102 @@ class SweetAlert2DialogService {
|
|
|
10094
11128
|
img.onerror = (err) => reject(err);
|
|
10095
11129
|
});
|
|
10096
11130
|
}
|
|
10097
|
-
//
|
|
10098
|
-
|
|
10099
|
-
Dialog(firstParam, text, icoOrIcono, options = {}) {
|
|
11131
|
+
// Implementación unificada
|
|
11132
|
+
Dialog(firstParam, text, secondOrThird, thirdOrOptions) {
|
|
10100
11133
|
let title = '';
|
|
10101
|
-
let
|
|
10102
|
-
let
|
|
10103
|
-
|
|
11134
|
+
let messageContent = '';
|
|
11135
|
+
let options = {};
|
|
11136
|
+
let iconFromParam = null;
|
|
11137
|
+
let imageFromOptions = null;
|
|
11138
|
+
// === PROCESAR PARÁMETROS ===
|
|
10104
11139
|
if (typeof firstParam === 'object' && firstParam !== null) {
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
11140
|
+
// Caso 1: ActionMessageConfig
|
|
11141
|
+
const config = firstParam;
|
|
11142
|
+
title = config.title;
|
|
11143
|
+
messageContent = config.message;
|
|
11144
|
+
// Extraer opciones
|
|
11145
|
+
const { title: _title, message: _message, icon: _icon, icono: _icono, html: _html, footer: _footer, ...restOptions } = config;
|
|
11146
|
+
options = { ...restOptions };
|
|
11147
|
+
if (_html)
|
|
11148
|
+
options.html = _html;
|
|
11149
|
+
if (_footer)
|
|
11150
|
+
options.footer = _footer;
|
|
11151
|
+
// ✅ Si hay icono en el config, es el parámetro
|
|
11152
|
+
if (config.icon) {
|
|
11153
|
+
iconFromParam = config.icon;
|
|
11154
|
+
}
|
|
11155
|
+
// ✅ Si hay icono personalizado en el config
|
|
11156
|
+
if (config.icono) {
|
|
11157
|
+
imageFromOptions = config.icono;
|
|
11158
|
+
}
|
|
10110
11159
|
}
|
|
10111
11160
|
else {
|
|
11161
|
+
// Caso 2, 3, 4: Parámetros tradicionales
|
|
10112
11162
|
title = firstParam;
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
'
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
11163
|
+
messageContent = text || '';
|
|
11164
|
+
// Determinar qué tipo de parámetros tenemos
|
|
11165
|
+
if (secondOrThird !== undefined && secondOrThird !== null) {
|
|
11166
|
+
if (typeof secondOrThird === 'string') {
|
|
11167
|
+
// ✅ Es un icono nativo (Caso 4)
|
|
11168
|
+
if (this.NATIVE_ICONS.includes(secondOrThird)) {
|
|
11169
|
+
iconFromParam = secondOrThird;
|
|
11170
|
+
}
|
|
11171
|
+
options = thirdOrOptions || {};
|
|
11172
|
+
}
|
|
11173
|
+
else if (typeof secondOrThird === 'object') {
|
|
11174
|
+
// ✅ Son opciones (Caso 3)
|
|
11175
|
+
options = secondOrThird;
|
|
11176
|
+
// ✅ Si hay icono personalizado en options
|
|
11177
|
+
if (options.icono) {
|
|
11178
|
+
imageFromOptions = options.icono;
|
|
11179
|
+
}
|
|
11180
|
+
}
|
|
10127
11181
|
}
|
|
10128
11182
|
else {
|
|
10129
|
-
|
|
11183
|
+
// Solo título y texto (Caso 2)
|
|
11184
|
+
options = thirdOrOptions || {};
|
|
11185
|
+
if (options.icono) {
|
|
11186
|
+
imageFromOptions = options.icono;
|
|
11187
|
+
}
|
|
10130
11188
|
}
|
|
10131
11189
|
}
|
|
10132
|
-
//
|
|
10133
|
-
|
|
11190
|
+
// ✅ PRIORIDAD: imageFromOptions (imagen) > iconFromParam (icono nativo)
|
|
11191
|
+
let ico = null;
|
|
11192
|
+
let iconoImg = null;
|
|
11193
|
+
// 1. Si hay imagen en options, usarla (máxima prioridad)
|
|
11194
|
+
if (imageFromOptions) {
|
|
11195
|
+
iconoImg = imageFromOptions;
|
|
11196
|
+
}
|
|
11197
|
+
// 2. Si hay icono en el parámetro, usarlo
|
|
11198
|
+
else if (iconFromParam) {
|
|
11199
|
+
ico = iconFromParam;
|
|
11200
|
+
}
|
|
11201
|
+
// === CONFIGURACIÓN DE LA ALERTA ===
|
|
11202
|
+
const { confirmButtonText = 'Aceptar', cancelButtonText = 'Cancelar', showCloseButton = true, showCancelButton = true, imageWidth = 150, imageHeight = 150, theme, ...rest } = options;
|
|
10134
11203
|
const alertTheme = theme !== undefined ? theme : this.defaultTheme;
|
|
11204
|
+
// === CONSTRUIR FOOTER ===
|
|
10135
11205
|
const year = new Date().getFullYear();
|
|
10136
|
-
const
|
|
10137
|
-
const footer = `${
|
|
11206
|
+
const footerText = options.footer ?? this.environment?.sweetAlertFooter ?? 'DEVSOFTXela';
|
|
11207
|
+
const footer = `${footerText} <strong>${year}</strong>`;
|
|
11208
|
+
// === CONSTRUIR CONFIGURACIÓN DE SWEETALERT ===
|
|
10138
11209
|
const alertConfig = {
|
|
10139
11210
|
title,
|
|
10140
|
-
html: text,
|
|
10141
11211
|
footer,
|
|
10142
11212
|
showCloseButton,
|
|
10143
11213
|
showCancelButton,
|
|
10144
11214
|
confirmButtonText,
|
|
10145
11215
|
cancelButtonText,
|
|
10146
|
-
//reverseButtons: true,
|
|
10147
11216
|
...(alertTheme !== undefined && { theme: alertTheme }),
|
|
10148
11217
|
...rest,
|
|
10149
11218
|
};
|
|
11219
|
+
// === PRIORIDAD DEL CONTENIDO HTML ===
|
|
11220
|
+
if (options.html) {
|
|
11221
|
+
alertConfig.html = options.html;
|
|
11222
|
+
}
|
|
11223
|
+
else if (messageContent) {
|
|
11224
|
+
alertConfig.html = messageContent;
|
|
11225
|
+
}
|
|
11226
|
+
// === IMAGEN O ICONO ===
|
|
10150
11227
|
if (iconoImg) {
|
|
10151
11228
|
alertConfig.imageUrl = `/${iconoImg}`;
|
|
10152
11229
|
alertConfig.imageWidth = imageWidth;
|
|
@@ -10170,6 +11247,320 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
10170
11247
|
}]
|
|
10171
11248
|
}], ctorParameters: () => [] });
|
|
10172
11249
|
|
|
11250
|
+
class DsxMessagesService {
|
|
11251
|
+
AlertaService = inject(AlertaService);
|
|
11252
|
+
SweetDialog = inject(SweetAlert2DialogService);
|
|
11253
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
11254
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, providedIn: 'root' });
|
|
11255
|
+
}
|
|
11256
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, decorators: [{
|
|
11257
|
+
type: Injectable,
|
|
11258
|
+
args: [{
|
|
11259
|
+
providedIn: 'root',
|
|
11260
|
+
}]
|
|
11261
|
+
}] });
|
|
11262
|
+
|
|
11263
|
+
/**
|
|
11264
|
+
* Obtiene los datos de un FormGroup excluyendo TODAS las propiedades de tipo File
|
|
11265
|
+
*
|
|
11266
|
+
* @param form - FormGroup del cual extraer los datos
|
|
11267
|
+
* @returns Objeto con los datos (sin archivos) y la lista de archivos encontrados
|
|
11268
|
+
*
|
|
11269
|
+
* @internal
|
|
11270
|
+
*/
|
|
11271
|
+
function getFormDataExcludingFiles(form) {
|
|
11272
|
+
const rawValue = form.getRawValue();
|
|
11273
|
+
const entries = Object.entries(rawValue);
|
|
11274
|
+
const files = [];
|
|
11275
|
+
const data = {};
|
|
11276
|
+
for (const [key, value] of entries) {
|
|
11277
|
+
if (value instanceof File) {
|
|
11278
|
+
files.push(value);
|
|
11279
|
+
}
|
|
11280
|
+
else {
|
|
11281
|
+
data[key] = value;
|
|
11282
|
+
}
|
|
11283
|
+
}
|
|
11284
|
+
return { data: data, files };
|
|
11285
|
+
}
|
|
11286
|
+
/**
|
|
11287
|
+
* Servicio para guardar entidades y subir archivos automáticamente.
|
|
11288
|
+
*
|
|
11289
|
+
* Este servicio centraliza el flujo común de:
|
|
11290
|
+
* 1. Extraer automáticamente el archivo del FormGroup (por tipo `File`)
|
|
11291
|
+
* 2. Guardar la entidad (sin el archivo) en la base de datos
|
|
11292
|
+
* 3. Si el guardado es exitoso y hay archivo, subirlo
|
|
11293
|
+
* 4. Manejar errores de forma consistente
|
|
11294
|
+
* 5. Resetear el formulario automáticamente en caso de éxito
|
|
11295
|
+
*
|
|
11296
|
+
* @example
|
|
11297
|
+
* // En tu componente:
|
|
11298
|
+
*
|
|
11299
|
+
* // Guardar sin archivo
|
|
11300
|
+
* this._saveEntityPostFile.saveOnly(
|
|
11301
|
+
* this.form,
|
|
11302
|
+
* this._baseService.save.bind(this._baseService),
|
|
11303
|
+
* (response) => {
|
|
11304
|
+
* this.formState.emit(response);
|
|
11305
|
+
* }
|
|
11306
|
+
* );
|
|
11307
|
+
*
|
|
11308
|
+
* // Guardar con archivo (se extrae automáticamente)
|
|
11309
|
+
* this._saveEntityPostFile.saveWithFile(
|
|
11310
|
+
* this.form,
|
|
11311
|
+
* this._baseService.save.bind(this._baseService),
|
|
11312
|
+
* this._baseService.AdjuntarSoporte.bind(this._baseService),
|
|
11313
|
+
* (response) => {
|
|
11314
|
+
* this.formState.emit(response);
|
|
11315
|
+
* }
|
|
11316
|
+
* );
|
|
11317
|
+
*/
|
|
11318
|
+
class SaveEntityPostFileService {
|
|
11319
|
+
/** Servicio para mostrar mensajes al usuario */
|
|
11320
|
+
_dsxMessageService = inject(DsxMessagesService);
|
|
11321
|
+
/** Configuración del entorno (para controlar debug) */
|
|
11322
|
+
environment = inject(ENVIRONMENT);
|
|
11323
|
+
/**
|
|
11324
|
+
* Determina si el debug está activo
|
|
11325
|
+
*
|
|
11326
|
+
* @param debug - Solicitud explícita de debug
|
|
11327
|
+
* @returns `true` solo si `debug=true` Y no estamos en producción
|
|
11328
|
+
*
|
|
11329
|
+
* @internal
|
|
11330
|
+
*/
|
|
11331
|
+
shouldLog(debug) {
|
|
11332
|
+
return debug && !this.environment.production;
|
|
11333
|
+
}
|
|
11334
|
+
/**
|
|
11335
|
+
* Guarda una entidad Y sube automáticamente el archivo extraído del FormGroup.
|
|
11336
|
+
*
|
|
11337
|
+
* El servicio extrae automáticamente el archivo del FormGroup (por tipo `File`),
|
|
11338
|
+
* lo excluye del objeto que se envía al backend, y después de guardar
|
|
11339
|
+
* exitosamente, procede a subir el archivo usando el ID generado.
|
|
11340
|
+
*
|
|
11341
|
+
* @param form - FormGroup que contiene los datos del formulario
|
|
11342
|
+
* @param saveMethod - Método de save que recibe los datos (sin archivo)
|
|
11343
|
+
* @param uploadMethod - Método de upload que recibe (id, file) y retorna Observable
|
|
11344
|
+
* @param onSuccess - Callback de éxito (opcional)
|
|
11345
|
+
* @param onError - Callback de error (opcional)
|
|
11346
|
+
* @param resetForm - Función para resetear el formulario (opcional)
|
|
11347
|
+
* @param debug - Activa logs en consola (solo funciona en desarrollo). Por defecto `false`.
|
|
11348
|
+
*
|
|
11349
|
+
* @example
|
|
11350
|
+
* // Uso básico
|
|
11351
|
+
* this._saveEntityPostFile.saveWithFile(
|
|
11352
|
+
* this.form,
|
|
11353
|
+
* this._baseService.save.bind(this._baseService),
|
|
11354
|
+
* this._baseService.AdjuntarSoporte.bind(this._baseService),
|
|
11355
|
+
* (response) => {
|
|
11356
|
+
* this.formState.emit(response);
|
|
11357
|
+
* }
|
|
11358
|
+
* );
|
|
11359
|
+
*
|
|
11360
|
+
* @example
|
|
11361
|
+
* // Con reset personalizado
|
|
11362
|
+
* this._saveEntityPostFile.saveWithFile(
|
|
11363
|
+
* this.form,
|
|
11364
|
+
* this._baseService.save.bind(this._baseService),
|
|
11365
|
+
* this._baseService.AdjuntarSoporte.bind(this._baseService),
|
|
11366
|
+
* (response) => {
|
|
11367
|
+
* this.formState.emit(response);
|
|
11368
|
+
* },
|
|
11369
|
+
* (error) => {
|
|
11370
|
+
* console.error('Error:', error);
|
|
11371
|
+
* },
|
|
11372
|
+
* () => {
|
|
11373
|
+
* this.form.reset();
|
|
11374
|
+
* this.getForm();
|
|
11375
|
+
* this.cleanFileInput();
|
|
11376
|
+
* }
|
|
11377
|
+
* );
|
|
11378
|
+
*/
|
|
11379
|
+
saveWithFile(form, saveMethod, uploadMethod, deleteMethod, onSuccess, onError, resetForm, debug = false) {
|
|
11380
|
+
this.execute(form, saveMethod, uploadMethod, deleteMethod, onSuccess, onError, resetForm, debug);
|
|
11381
|
+
}
|
|
11382
|
+
/**
|
|
11383
|
+
* Ejecuta el flujo completo de guardado y subida de archivo.
|
|
11384
|
+
*
|
|
11385
|
+
* Este método es privado y contiene la lógica central del servicio.
|
|
11386
|
+
*
|
|
11387
|
+
* ## Flujo de ejecución:
|
|
11388
|
+
* 1. Extrae el archivo del FormGroup (por tipo `File`)
|
|
11389
|
+
* 2. Construye el objeto de datos sin el archivo
|
|
11390
|
+
* 3. Ejecuta el `saveMethod` con los datos limpios
|
|
11391
|
+
* 4. Si el save es exitoso y hay archivo, ejecuta el `uploadMethod`
|
|
11392
|
+
* 5. Si todo es exitoso, ejecuta `resetForm` y `onSuccess`
|
|
11393
|
+
* 6. Si hay error en cualquier paso, ejecuta `onError`
|
|
11394
|
+
*
|
|
11395
|
+
* @param form - FormGroup que contiene los datos del formulario
|
|
11396
|
+
* @param saveMethod - Método de save que recibe los datos (sin archivo)
|
|
11397
|
+
* @param uploadMethod - Método de upload que recibe (id, file) (opcional)
|
|
11398
|
+
* @param onSuccess - Callback de éxito (opcional)
|
|
11399
|
+
* @param onError - Callback de error (opcional)
|
|
11400
|
+
* @param resetForm - Función para resetear el formulario (opcional)
|
|
11401
|
+
* @param debug - Activa logs en consola (solo funciona en desarrollo)
|
|
11402
|
+
*
|
|
11403
|
+
* @internal
|
|
11404
|
+
*/
|
|
11405
|
+
execute(form, saveMethod, uploadMethod, deleteMethod, onSuccess, onError, resetForm, debug = false) {
|
|
11406
|
+
const log = this.shouldLog(debug);
|
|
11407
|
+
// 1️⃣ Extraer archivo y datos del FormGroup
|
|
11408
|
+
const { data: saveData, files } = getFormDataExcludingFiles(form);
|
|
11409
|
+
const fileUpload = files.length > 0 ? files[0] : null;
|
|
11410
|
+
if (log) {
|
|
11411
|
+
console.group('📤 [SaveEntityPostFile] Preparando datos');
|
|
11412
|
+
console.log('📄 Datos a guardar (sin archivo):', saveData);
|
|
11413
|
+
console.log('📎 Archivo encontrado:', fileUpload?.name || 'Ninguno');
|
|
11414
|
+
console.groupEnd();
|
|
11415
|
+
}
|
|
11416
|
+
// 2️⃣ Crear el observable del save con los datos sin archivo
|
|
11417
|
+
const save$ = saveMethod(saveData);
|
|
11418
|
+
// 3️⃣ Ejecutar el flujo completo
|
|
11419
|
+
save$.subscribe({
|
|
11420
|
+
next: (saveResult) => {
|
|
11421
|
+
// 📥 Log del save
|
|
11422
|
+
if (log) {
|
|
11423
|
+
console.group('📥 [SaveEntityPostFile] Respuesta del save');
|
|
11424
|
+
console.log('Resultado:', saveResult);
|
|
11425
|
+
console.groupEnd();
|
|
11426
|
+
}
|
|
11427
|
+
// ❌ Si el guardado falla
|
|
11428
|
+
if (!saveResult.isSuccess) {
|
|
11429
|
+
this._dsxMessageService.SweetDialog.Dialog(saveResult.title || 'Error', saveResult.message || 'Error al guardar los datos', 'error', { showConfirmButton: true, showCancelButton: false });
|
|
11430
|
+
onError?.(saveResult);
|
|
11431
|
+
return;
|
|
11432
|
+
}
|
|
11433
|
+
// ✅ Si el guardado es exitoso y hay archivo
|
|
11434
|
+
if (uploadMethod && fileUpload && saveResult.data) {
|
|
11435
|
+
if (log)
|
|
11436
|
+
console.log('📤 [SaveEntityPostFile] Subiendo archivo...');
|
|
11437
|
+
uploadMethod(saveResult.data, fileUpload).subscribe({
|
|
11438
|
+
next: (uploadResult) => {
|
|
11439
|
+
// 📥 Log del upload
|
|
11440
|
+
if (log) {
|
|
11441
|
+
console.group('📥 [SaveEntityPostFile] Respuesta del upload');
|
|
11442
|
+
console.log('Resultado:', uploadResult);
|
|
11443
|
+
console.groupEnd();
|
|
11444
|
+
}
|
|
11445
|
+
if (uploadResult.isSuccess) {
|
|
11446
|
+
// ✅ Éxito: guardado + archivo
|
|
11447
|
+
if (log)
|
|
11448
|
+
console.log('✅ [SaveEntityPostFile] Subida exitosa');
|
|
11449
|
+
resetForm?.();
|
|
11450
|
+
onSuccess?.(uploadResult);
|
|
11451
|
+
}
|
|
11452
|
+
else {
|
|
11453
|
+
// ❌ Error en la subida
|
|
11454
|
+
if (log)
|
|
11455
|
+
console.warn('⚠️ [SaveEntityPostFile] Subida falló');
|
|
11456
|
+
const htmlContent = `
|
|
11457
|
+
<div style="text-align: left; font-family: -apple-system, system-ui, sans-serif;">
|
|
11458
|
+
|
|
11459
|
+
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 12px;">
|
|
11460
|
+
<span style="font-size: 22px;">⚠️</span>
|
|
11461
|
+
<span style="font-size: 17px; font-weight: 600; color: #1a1a1a;">Archivo no subido</span>
|
|
11462
|
+
</div>
|
|
11463
|
+
|
|
11464
|
+
<p style="color: #555; font-size: 14px; line-height: 1.5; margin: 0 0 8px 0;">
|
|
11465
|
+
El registro se guardó, pero el archivo falló.
|
|
11466
|
+
</p>
|
|
11467
|
+
|
|
11468
|
+
<div style="background: #f8f4ec; padding: 6px 12px; border-radius: 4px; margin: 6px 0 14px 0;">
|
|
11469
|
+
<span style="color: #7a6b4a; font-size: 13px;">
|
|
11470
|
+
<strong>Motivo:</strong> ${uploadResult.message}
|
|
11471
|
+
</span>
|
|
11472
|
+
</div>
|
|
11473
|
+
|
|
11474
|
+
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #eee;">
|
|
11475
|
+
<p style="font-weight: 500; color: #1a1a1a; font-size: 13px; margin: 0 0 6px 0;">
|
|
11476
|
+
¿Qué deseas hacer?
|
|
11477
|
+
</p>
|
|
11478
|
+
<div style="font-size: 13px; color: #555; line-height: 1.7;">
|
|
11479
|
+
<div>📁 Guardar sin archivo <span style="color: #999; font-size: 12px;">(subir después)</span></div>
|
|
11480
|
+
<div>🗑️ Eliminar registro <span style="color: #999; font-size: 12px;">(corregir y reintentar)</span></div>
|
|
11481
|
+
</div>
|
|
11482
|
+
</div>
|
|
11483
|
+
|
|
11484
|
+
</div>`;
|
|
11485
|
+
this._dsxMessageService.SweetDialog.Dialog(uploadResult.title || 'Error', '', 'error', {
|
|
11486
|
+
html: htmlContent,
|
|
11487
|
+
showConfirmButton: true,
|
|
11488
|
+
confirmButtonText: 'Guardar',
|
|
11489
|
+
showCancelButton: true,
|
|
11490
|
+
cancelButtonText: 'Eliminar',
|
|
11491
|
+
}).then((isConfirmed) => {
|
|
11492
|
+
if (isConfirmed) {
|
|
11493
|
+
if (log)
|
|
11494
|
+
console.warn('⚠️ [SaveEntityPostFile] Se conservo el registro');
|
|
11495
|
+
resetForm?.();
|
|
11496
|
+
onSuccess?.(saveResult);
|
|
11497
|
+
}
|
|
11498
|
+
else {
|
|
11499
|
+
if (log)
|
|
11500
|
+
console.warn('❌ [SaveEntityPostFile] Se elimino el registro');
|
|
11501
|
+
// 2️⃣ Crear el observable del save con los datos sin archivo
|
|
11502
|
+
const delete$ = deleteMethod(saveResult.data, false);
|
|
11503
|
+
delete$.subscribe({
|
|
11504
|
+
next: (deleteResult) => {
|
|
11505
|
+
// 📥 Log del save
|
|
11506
|
+
if (log) {
|
|
11507
|
+
console.group('📥 [SaveEntityPostFile] Respuesta del delete');
|
|
11508
|
+
console.log('Resultado:', deleteResult);
|
|
11509
|
+
console.groupEnd();
|
|
11510
|
+
}
|
|
11511
|
+
if (deleteResult.isSuccess) {
|
|
11512
|
+
if (log)
|
|
11513
|
+
console.log('✅ [SaveEntityPostFile] Registro eliminado');
|
|
11514
|
+
onSuccess?.(deleteResult);
|
|
11515
|
+
}
|
|
11516
|
+
else {
|
|
11517
|
+
this._dsxMessageService.SweetDialog.Dialog('❌ Error al eliminar', deleteResult.message ||
|
|
11518
|
+
'No se pudo eliminar el registro.', 'error', {
|
|
11519
|
+
showConfirmButton: true,
|
|
11520
|
+
showCancelButton: false,
|
|
11521
|
+
});
|
|
11522
|
+
onError?.(deleteResult);
|
|
11523
|
+
}
|
|
11524
|
+
},
|
|
11525
|
+
error: (err) => console.warn(err),
|
|
11526
|
+
complete: () => '',
|
|
11527
|
+
});
|
|
11528
|
+
}
|
|
11529
|
+
});
|
|
11530
|
+
onError?.(uploadResult);
|
|
11531
|
+
}
|
|
11532
|
+
},
|
|
11533
|
+
error: (uploadError) => {
|
|
11534
|
+
// ❌ Error en la subida (excepción)
|
|
11535
|
+
console.warn('Error no controlado en upload:', uploadError);
|
|
11536
|
+
onError?.(uploadError);
|
|
11537
|
+
},
|
|
11538
|
+
});
|
|
11539
|
+
return;
|
|
11540
|
+
}
|
|
11541
|
+
// ✅ Éxito sin archivo
|
|
11542
|
+
if (log)
|
|
11543
|
+
console.log('✅ [SaveEntityPostFile] Proceso completado (sin archivo)');
|
|
11544
|
+
resetForm?.();
|
|
11545
|
+
onSuccess?.(saveResult);
|
|
11546
|
+
},
|
|
11547
|
+
error: (error) => {
|
|
11548
|
+
// ❌ Error en el save (excepción)
|
|
11549
|
+
console.warn('Error no controlado en save:', error);
|
|
11550
|
+
onError?.(error);
|
|
11551
|
+
},
|
|
11552
|
+
});
|
|
11553
|
+
}
|
|
11554
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveEntityPostFileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
11555
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveEntityPostFileService, providedIn: 'root' });
|
|
11556
|
+
}
|
|
11557
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SaveEntityPostFileService, decorators: [{
|
|
11558
|
+
type: Injectable,
|
|
11559
|
+
args: [{
|
|
11560
|
+
providedIn: 'root',
|
|
11561
|
+
}]
|
|
11562
|
+
}] });
|
|
11563
|
+
|
|
10173
11564
|
class NextToastNotifyService {
|
|
10174
11565
|
// Configuraciones iniciales por defecto (ambos activados como en tus switches)
|
|
10175
11566
|
defaultOptions = {
|
|
@@ -10213,8 +11604,10 @@ class ResultResponseService {
|
|
|
10213
11604
|
else {
|
|
10214
11605
|
const guideHtml = this.buildAssociatedGuideHtml(response?.data);
|
|
10215
11606
|
const mainMessage = this.formatPrimaryAlertMessage(response.message);
|
|
10216
|
-
this._sweetAlert2DialogService.Dialog(response.title,
|
|
10217
|
-
|
|
11607
|
+
this._sweetAlert2DialogService.Dialog(response.title,
|
|
11608
|
+
// Se concatena limpiamente sin alterar la lógica
|
|
11609
|
+
`${mainMessage}${guideHtml}`, {
|
|
11610
|
+
icono: 'icon/sign.png',
|
|
10218
11611
|
showCancelButton: false,
|
|
10219
11612
|
showConfirmButton: true,
|
|
10220
11613
|
});
|
|
@@ -10260,10 +11653,407 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
10260
11653
|
}]
|
|
10261
11654
|
}] });
|
|
10262
11655
|
|
|
11656
|
+
/**
|
|
11657
|
+
* Servicio especializado para extraer y procesar archivos (Blob) desde respuestas del backend.
|
|
11658
|
+
*
|
|
11659
|
+
* 📋 **Propósito:**
|
|
11660
|
+
* Este servicio actúa como un puente entre la respuesta del backend (normalizada como ServiceResult<FileData>)
|
|
11661
|
+
* y el consumidor final, extrayendo el archivo (Blob) y su nombre de forma segura y validada.
|
|
11662
|
+
*
|
|
11663
|
+
* 🎯 **Cuándo usar este servicio:**
|
|
11664
|
+
* - Cuando necesitas obtener el Blob y el nombre de archivo de un Observable<ServiceResult<FileData>>
|
|
11665
|
+
* - Cuando quieres validar que el archivo existe y no está vacío antes de procesarlo
|
|
11666
|
+
* - Cuando necesitas asegurar que el nombre del archivo tenga la extensión correcta
|
|
11667
|
+
* - Cuando quieres un manejo de errores detallado y estructurado
|
|
11668
|
+
*
|
|
11669
|
+
* 🔄 **Flujo de Trabajo:**
|
|
11670
|
+
* 1. El backend retorna un ServiceResult<FileData> (normalizado por getDownloadableFile)
|
|
11671
|
+
* 2. Este servicio extrae el Blob y el fileName
|
|
11672
|
+
* 3. Valida que el archivo exista y tenga contenido
|
|
11673
|
+
* 4. Asegura que el nombre tenga la extensión correcta según el MIME type
|
|
11674
|
+
* 5. Retorna un objeto con { blob, fileName } o un error estructurado
|
|
11675
|
+
*
|
|
11676
|
+
* 🔗 **Relación con otros servicios:**
|
|
11677
|
+
* - **ResultFileService**: Proporciona el Observable<ServiceResult<FileData>> de entrada
|
|
11678
|
+
* - **AlertaService**: Para mostrar mensajes al usuario
|
|
11679
|
+
*
|
|
11680
|
+
* @see ResultFileService - Servicio que realiza la petición HTTP
|
|
11681
|
+
* @see FileData - Estructura de datos del archivo
|
|
11682
|
+
* @see ServiceResult - Estructura de resultado estandarizada
|
|
11683
|
+
* @see Blob - API nativa de Blob de JavaScript
|
|
11684
|
+
*/
|
|
11685
|
+
class ResultFileReturnBlobNameService {
|
|
11686
|
+
/** Configuración del entorno (para controlar debug) */
|
|
11687
|
+
environment = inject(ENVIRONMENT);
|
|
11688
|
+
/**
|
|
11689
|
+
* Extrae el Blob y el nombre de archivo de un Observable<ServiceResult<FileData>>,
|
|
11690
|
+
* realizando validaciones exhaustivas y manejo de errores detallado.
|
|
11691
|
+
*
|
|
11692
|
+
* 📝 **Descripción:**
|
|
11693
|
+
* Este método toma el Observable resultante de `getDownloadableFile` y extrae
|
|
11694
|
+
* el archivo (Blob) y su nombre, validando que todo sea correcto antes de
|
|
11695
|
+
* entregar el resultado al consumidor.
|
|
11696
|
+
*
|
|
11697
|
+
* 🔍 **Validaciones Realizadas:**
|
|
11698
|
+
* 1. ✅ Resultado nulo o indefinido → Error: "El resultado es nulo o indefinido"
|
|
11699
|
+
* 2. ✅ isSuccess = false → Error con title y message del backend
|
|
11700
|
+
* 3. ✅ data es null → Error: "El resultado no contiene datos"
|
|
11701
|
+
* 4. ✅ blob es null → Error: "El blob del archivo no está presente"
|
|
11702
|
+
* 5. ✅ blob.size = 0 → Error: "El archivo descargado está vacío (0 bytes)"
|
|
11703
|
+
* 6. ✅ Error de conexión → Error: "No se pudo obtener el archivo del servidor"
|
|
11704
|
+
*
|
|
11705
|
+
* 📁 **Gestión de Extensiones:**
|
|
11706
|
+
* El método automáticamente asegura que el nombre del archivo tenga la extensión
|
|
11707
|
+
* correcta basada en el MIME type. Si el nombre ya tiene extensión, la mantiene.
|
|
11708
|
+
*
|
|
11709
|
+
* 💡 **Ejemplos de Uso:**
|
|
11710
|
+
*
|
|
11711
|
+
* ```typescript
|
|
11712
|
+
* // EJEMPLO 1: Uso básico con descarga automática
|
|
11713
|
+
* const fileObservable = this.resultFileService.getDownloadableFile('/api/documentos/123');
|
|
11714
|
+
*
|
|
11715
|
+
* this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(fileObservable)
|
|
11716
|
+
* .subscribe({
|
|
11717
|
+
* next: ({ blob, fileName }) => {
|
|
11718
|
+
* // Descargar el archivo automáticamente
|
|
11719
|
+
* this.resultFileService.descargarBlob(blob, fileName);
|
|
11720
|
+
* },
|
|
11721
|
+
* error: (error) => {
|
|
11722
|
+
* // Error estructurado
|
|
11723
|
+
* this.alertaService.mostrarError(error.title, error.message);
|
|
11724
|
+
* }
|
|
11725
|
+
* });
|
|
11726
|
+
*
|
|
11727
|
+
* // EJEMPLO 2: Visualizar PDF
|
|
11728
|
+
* const fileObservable = this.resultFileService.getDownloadableFile('/api/documentos/123');
|
|
11729
|
+
*
|
|
11730
|
+
* this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(fileObservable)
|
|
11731
|
+
* .subscribe({
|
|
11732
|
+
* next: ({ blob, fileName }) => {
|
|
11733
|
+
* // Crear URL para visualizar
|
|
11734
|
+
* const url = URL.createObjectURL(blob);
|
|
11735
|
+
* window.open(url, '_blank');
|
|
11736
|
+
* },
|
|
11737
|
+
* error: (error) => {
|
|
11738
|
+
* console.error('Error al visualizar:', error);
|
|
11739
|
+
* }
|
|
11740
|
+
* });
|
|
11741
|
+
*
|
|
11742
|
+
* // EJEMPLO 3: Procesamiento personalizado con validación
|
|
11743
|
+
* const fileObservable = this.resultFileService.getDownloadableFile('/api/reportes', {
|
|
11744
|
+
* params: { formato: 'excel' }
|
|
11745
|
+
* });
|
|
11746
|
+
*
|
|
11747
|
+
* this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(
|
|
11748
|
+
* fileObservable,
|
|
11749
|
+
* 'reporte_generado' // fallbackFileName
|
|
11750
|
+
* ).subscribe({
|
|
11751
|
+
* next: ({ blob, fileName }) => {
|
|
11752
|
+
* // Procesar el blob según necesidades
|
|
11753
|
+
* if (fileName.endsWith('.xlsx')) {
|
|
11754
|
+
* this.procesarExcel(blob);
|
|
11755
|
+
* } else {
|
|
11756
|
+
* this.descargarArchivo(blob, fileName);
|
|
11757
|
+
* }
|
|
11758
|
+
* },
|
|
11759
|
+
* error: (error) => {
|
|
11760
|
+
* // Manejo detallado de errores
|
|
11761
|
+
* if (error.title === 'Archivo vacío') {
|
|
11762
|
+
* this.alertaService.advertencia('El archivo está vacío');
|
|
11763
|
+
* } else if (error.title === 'Error de conexión') {
|
|
11764
|
+
* this.alertaService.error('Error de red', 'Verifica tu conexión');
|
|
11765
|
+
* } else {
|
|
11766
|
+
* this.alertaService.error(error.title, error.message);
|
|
11767
|
+
* }
|
|
11768
|
+
* }
|
|
11769
|
+
* });
|
|
11770
|
+
*
|
|
11771
|
+
* // EJEMPLO 4: Uso con loading state
|
|
11772
|
+
* cargando = false;
|
|
11773
|
+
*
|
|
11774
|
+
* descargarDocumento(id: number): void {
|
|
11775
|
+
* this.cargando = true;
|
|
11776
|
+
* const fileObservable = this.resultFileService.getDownloadableFile(`/api/documentos/${id}`);
|
|
11777
|
+
*
|
|
11778
|
+
* this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(
|
|
11779
|
+
* fileObservable,
|
|
11780
|
+
* `documento_${id}`
|
|
11781
|
+
* ).subscribe({
|
|
11782
|
+
* next: ({ blob, fileName }) => {
|
|
11783
|
+
* this.cargando = false;
|
|
11784
|
+
* this.resultFileService.descargarBlob(blob, fileName);
|
|
11785
|
+
* this.alertaService.exito('Descarga completada');
|
|
11786
|
+
* },
|
|
11787
|
+
* error: (error) => {
|
|
11788
|
+
* this.cargando = false;
|
|
11789
|
+
* this.alertaService.error('Error', error.message);
|
|
11790
|
+
* }
|
|
11791
|
+
* });
|
|
11792
|
+
* }
|
|
11793
|
+
*
|
|
11794
|
+
* // EJEMPLO 5: Combinación con otros operadores RxJS
|
|
11795
|
+
* const fileObservable = this.resultFileService.getDownloadableFile('/api/documentos/123');
|
|
11796
|
+
*
|
|
11797
|
+
* this.resultFileReturnBlobNameService.extractFileDataFromServiceResultEnhanced(fileObservable)
|
|
11798
|
+
* .pipe(
|
|
11799
|
+
* tap(({ blob, fileName }) => {
|
|
11800
|
+
* console.log(`Archivo: ${fileName}, Tamaño: ${blob.size} bytes`);
|
|
11801
|
+
* }),
|
|
11802
|
+
* delay(1000), // Simular procesamiento
|
|
11803
|
+
* map(({ blob, fileName }) => ({
|
|
11804
|
+
* blob,
|
|
11805
|
+
* fileName,
|
|
11806
|
+
* tipo: this.obtenerTipoArchivo(fileName)
|
|
11807
|
+
* }))
|
|
11808
|
+
* )
|
|
11809
|
+
* .subscribe(({ blob, fileName, tipo }) => {
|
|
11810
|
+
* console.log(`Tipo de archivo: ${tipo}`);
|
|
11811
|
+
* });
|
|
11812
|
+
* ```
|
|
11813
|
+
*
|
|
11814
|
+
* @param fileObservable - Observable que emite un ServiceResult<FileData>
|
|
11815
|
+
* @param fallbackFileName - Nombre alternativo si no se puede determinar el nombre (por defecto: 'archivo sin nombre')
|
|
11816
|
+
*
|
|
11817
|
+
* @returns Observable que emite un objeto con:
|
|
11818
|
+
* - blob: Blob - El archivo extraído
|
|
11819
|
+
* - fileName: string - Nombre del archivo con extensión
|
|
11820
|
+
* - headers?: HttpHeaders - Headers de la respuesta (opcional, actualmente no implementado)
|
|
11821
|
+
*
|
|
11822
|
+
* @throws {Object} En caso de error, emite un objeto con:
|
|
11823
|
+
* - success: false
|
|
11824
|
+
* - title: string - Título del error
|
|
11825
|
+
* - message: string - Mensaje descriptivo
|
|
11826
|
+
* - error?: any - Error original (en caso de error de conexión)
|
|
11827
|
+
*
|
|
11828
|
+
* @see ResultFileService.getDownloadableFile - Método que genera el Observable de entrada
|
|
11829
|
+
* @see ResultFileService.descargarBlob - Para descargar el archivo extraído
|
|
11830
|
+
* @see ResultFileService.PdfView - Para visualizar PDFs
|
|
11831
|
+
*/
|
|
11832
|
+
extractFileDataFromServiceResultEnhanced(fileObservable, fallbackFileName = 'archivo sin nombre') {
|
|
11833
|
+
return new Observable((observer) => {
|
|
11834
|
+
fileObservable.subscribe({
|
|
11835
|
+
next: (result) => {
|
|
11836
|
+
// Validación completa del resultado
|
|
11837
|
+
if (!result) {
|
|
11838
|
+
observer.error({
|
|
11839
|
+
success: false,
|
|
11840
|
+
title: 'Resultado nulo',
|
|
11841
|
+
message: 'El resultado es nulo o indefinido',
|
|
11842
|
+
});
|
|
11843
|
+
return;
|
|
11844
|
+
}
|
|
11845
|
+
if (!result.isSuccess) {
|
|
11846
|
+
observer.error({
|
|
11847
|
+
success: false,
|
|
11848
|
+
title: result.title || 'Error en la operación',
|
|
11849
|
+
message: result.message || 'El servidor reportó un error',
|
|
11850
|
+
});
|
|
11851
|
+
return;
|
|
11852
|
+
}
|
|
11853
|
+
if (!result.data) {
|
|
11854
|
+
observer.error({
|
|
11855
|
+
success: false,
|
|
11856
|
+
title: 'Datos no disponibles',
|
|
11857
|
+
message: 'El resultado no contiene datos',
|
|
11858
|
+
});
|
|
11859
|
+
return;
|
|
11860
|
+
}
|
|
11861
|
+
const blob = result.data.blob;
|
|
11862
|
+
if (!blob) {
|
|
11863
|
+
observer.error({
|
|
11864
|
+
success: false,
|
|
11865
|
+
title: 'Archivo no disponible',
|
|
11866
|
+
message: 'El blob del archivo no está presente',
|
|
11867
|
+
});
|
|
11868
|
+
return;
|
|
11869
|
+
}
|
|
11870
|
+
// Validar que el blob tenga tamaño
|
|
11871
|
+
if (blob.size === 0) {
|
|
11872
|
+
observer.error({
|
|
11873
|
+
success: false,
|
|
11874
|
+
title: 'Archivo vacío',
|
|
11875
|
+
message: 'El archivo descargado está vacío (0 bytes)',
|
|
11876
|
+
});
|
|
11877
|
+
return;
|
|
11878
|
+
}
|
|
11879
|
+
// Procesar nombre de archivo
|
|
11880
|
+
let fileName = result.data.fileName || fallbackFileName;
|
|
11881
|
+
fileName = this.ensureFileNameWithExtension(fileName, blob.type);
|
|
11882
|
+
// Log para debugging
|
|
11883
|
+
this.logIfNotProduction('✅ Archivo extraído exitosamente:', {
|
|
11884
|
+
tamaño: this.formatFileSize(blob.size),
|
|
11885
|
+
tipo: blob.type,
|
|
11886
|
+
nombre: fileName,
|
|
11887
|
+
});
|
|
11888
|
+
observer.next({
|
|
11889
|
+
blob: blob,
|
|
11890
|
+
fileName: fileName,
|
|
11891
|
+
});
|
|
11892
|
+
observer.complete();
|
|
11893
|
+
},
|
|
11894
|
+
error: (err) => {
|
|
11895
|
+
this.logIfNotProduction('❌ Error extrayendo archivo:', err);
|
|
11896
|
+
observer.error({
|
|
11897
|
+
success: false,
|
|
11898
|
+
title: 'Error de conexión',
|
|
11899
|
+
message: 'No se pudo obtener el archivo del servidor',
|
|
11900
|
+
error: err,
|
|
11901
|
+
});
|
|
11902
|
+
},
|
|
11903
|
+
});
|
|
11904
|
+
});
|
|
11905
|
+
}
|
|
11906
|
+
/**
|
|
11907
|
+
* Asegura que el nombre de archivo tenga una extensión válida.
|
|
11908
|
+
*
|
|
11909
|
+
* 📋 **Funcionamiento:**
|
|
11910
|
+
* 1. Si el nombre ya tiene una extensión (contiene un punto), lo retorna sin cambios.
|
|
11911
|
+
* 2. Si no tiene extensión, intenta obtenerla del MIME type.
|
|
11912
|
+
* 3. Si se puede determinar la extensión, la agrega al nombre.
|
|
11913
|
+
* 4. Si no se puede determinar, retorna el nombre sin cambios.
|
|
11914
|
+
*
|
|
11915
|
+
* 💡 **Ejemplos:**
|
|
11916
|
+
* - `ensureFileNameWithExtension('documento', 'application/pdf')` → `'documento.pdf'`
|
|
11917
|
+
* - `ensureFileNameWithExtension('foto', 'image/jpeg')` → `'foto.jpg'`
|
|
11918
|
+
* - `ensureFileNameWithExtension('archivo.txt', 'text/plain')` → `'archivo.txt'` (ya tiene extensión)
|
|
11919
|
+
* - `ensureFileNameWithExtension('reporte', 'application/unknown')` → `'reporte'`
|
|
11920
|
+
*
|
|
11921
|
+
* @param fileName - Nombre del archivo (puede o no tener extensión)
|
|
11922
|
+
* @param mimeType - Tipo MIME del archivo
|
|
11923
|
+
* @returns Nombre del archivo con extensión (si fue posible determinarla)
|
|
11924
|
+
*/
|
|
11925
|
+
ensureFileNameWithExtension(fileName, mimeType) {
|
|
11926
|
+
// Si ya tiene extensión, retornarlo
|
|
11927
|
+
if (fileName.includes('.')) {
|
|
11928
|
+
return fileName;
|
|
11929
|
+
}
|
|
11930
|
+
// Obtener extensión del MIME type
|
|
11931
|
+
const extension = this.getExtensionFromMimeType(mimeType);
|
|
11932
|
+
return extension ? `${fileName}.${extension}` : fileName;
|
|
11933
|
+
}
|
|
11934
|
+
/**
|
|
11935
|
+
* Obtiene la extensión del archivo basado en el MIME type.
|
|
11936
|
+
*
|
|
11937
|
+
* 📁 **Mapa de MIME Types Soportados:**
|
|
11938
|
+
*
|
|
11939
|
+
* | MIME Type | Extensión |
|
|
11940
|
+
* |-----------|-----------|
|
|
11941
|
+
* | application/pdf | pdf |
|
|
11942
|
+
* | application/msword | doc |
|
|
11943
|
+
* | application/vnd.openxmlformats-officedocument.wordprocessingml.document | docx |
|
|
11944
|
+
* | application/vnd.ms-excel | xls |
|
|
11945
|
+
* | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | xlsx |
|
|
11946
|
+
* | application/vnd.ms-powerpoint | ppt |
|
|
11947
|
+
* | application/vnd.openxmlformats-officedocument.presentationml.presentation | pptx |
|
|
11948
|
+
* | image/jpeg | jpg |
|
|
11949
|
+
* | image/png | png |
|
|
11950
|
+
* | image/gif | gif |
|
|
11951
|
+
* | text/plain | txt |
|
|
11952
|
+
* | application/json | json |
|
|
11953
|
+
* | application/zip | zip |
|
|
11954
|
+
* | application/octet-stream | bin |
|
|
11955
|
+
*
|
|
11956
|
+
* 🔄 **Comportamiento:**
|
|
11957
|
+
* 1. Busca el MIME type en el mapa predefinido.
|
|
11958
|
+
* 2. Si lo encuentra, retorna la extensión correspondiente.
|
|
11959
|
+
* 3. Si no lo encuentra, intenta extraer el subtipo del MIME (ej: 'pdf' de 'application/pdf').
|
|
11960
|
+
* 4. Si el subtipo es 'octet-stream' o no se puede determinar, retorna 'file'.
|
|
11961
|
+
*
|
|
11962
|
+
* @param mimeType - Tipo MIME del archivo
|
|
11963
|
+
* @returns Extensión del archivo (sin el punto) o 'file' si no se puede determinar
|
|
11964
|
+
*/
|
|
11965
|
+
getExtensionFromMimeType(mimeType) {
|
|
11966
|
+
const mimeMap = {
|
|
11967
|
+
'application/pdf': 'pdf',
|
|
11968
|
+
'application/msword': 'doc',
|
|
11969
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
|
|
11970
|
+
'application/vnd.ms-excel': 'xls',
|
|
11971
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
|
|
11972
|
+
'application/vnd.ms-powerpoint': 'ppt',
|
|
11973
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
|
|
11974
|
+
'image/jpeg': 'jpg',
|
|
11975
|
+
'image/png': 'png',
|
|
11976
|
+
'image/gif': 'gif',
|
|
11977
|
+
'text/plain': 'txt',
|
|
11978
|
+
'application/json': 'json',
|
|
11979
|
+
'application/zip': 'zip',
|
|
11980
|
+
'application/octet-stream': 'bin',
|
|
11981
|
+
};
|
|
11982
|
+
// Buscar extensión en el mapa
|
|
11983
|
+
const extension = mimeMap[mimeType];
|
|
11984
|
+
if (extension) {
|
|
11985
|
+
return extension;
|
|
11986
|
+
}
|
|
11987
|
+
// Si no está en el mapa, intentar extraer del MIME type
|
|
11988
|
+
const mimeParts = mimeType.split('/');
|
|
11989
|
+
if (mimeParts.length === 2) {
|
|
11990
|
+
const type = mimeParts[1];
|
|
11991
|
+
if (type !== 'octet-stream') {
|
|
11992
|
+
return type;
|
|
11993
|
+
}
|
|
11994
|
+
}
|
|
11995
|
+
// Si no se puede determinar, devolver 'file'
|
|
11996
|
+
return 'file';
|
|
11997
|
+
}
|
|
11998
|
+
/**
|
|
11999
|
+
* Formatea el tamaño del archivo para logging.
|
|
12000
|
+
*
|
|
12001
|
+
* 📊 **Formato de Salida:**
|
|
12002
|
+
* - Bytes: `"123 B"`
|
|
12003
|
+
* - Kilobytes: `"1.5 KB"`
|
|
12004
|
+
* - Megabytes: `"2.3 MB"`
|
|
12005
|
+
* - Gigabytes: `"1.2 GB"`
|
|
12006
|
+
*
|
|
12007
|
+
* 💡 **Ejemplos:**
|
|
12008
|
+
* - `formatFileSize(0)` → `"0 B"`
|
|
12009
|
+
* - `formatFileSize(1024)` → `"1.00 KB"`
|
|
12010
|
+
* - `formatFileSize(1536)` → `"1.50 KB"`
|
|
12011
|
+
* - `formatFileSize(1048576)` → `"1.00 MB"`
|
|
12012
|
+
*
|
|
12013
|
+
* @param bytes - Tamaño en bytes
|
|
12014
|
+
* @returns String formateado del tamaño
|
|
12015
|
+
*/
|
|
12016
|
+
formatFileSize(bytes) {
|
|
12017
|
+
if (bytes === 0)
|
|
12018
|
+
return '0 B';
|
|
12019
|
+
const k = 1024;
|
|
12020
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
12021
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
12022
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
12023
|
+
}
|
|
12024
|
+
/**
|
|
12025
|
+
* Registra mensajes de log solo en entornos de desarrollo.
|
|
12026
|
+
*
|
|
12027
|
+
* 🛠️ **Comportamiento:**
|
|
12028
|
+
* - En desarrollo (production = false): Muestra los logs en consola
|
|
12029
|
+
* - En producción (production = true): No muestra nada
|
|
12030
|
+
*
|
|
12031
|
+
* 📝 **Uso Interno:**
|
|
12032
|
+
* Se utiliza para depuración sin afectar el rendimiento en producción.
|
|
12033
|
+
*
|
|
12034
|
+
* @param args - Argumentos para mostrar en el log
|
|
12035
|
+
*/
|
|
12036
|
+
logIfNotProduction(...args) {
|
|
12037
|
+
if (!this.environment.production) {
|
|
12038
|
+
console.log('[ResultFileService]', ...args);
|
|
12039
|
+
}
|
|
12040
|
+
}
|
|
12041
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResultFileReturnBlobNameService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
12042
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResultFileReturnBlobNameService, providedIn: 'root' });
|
|
12043
|
+
}
|
|
12044
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ResultFileReturnBlobNameService, decorators: [{
|
|
12045
|
+
type: Injectable,
|
|
12046
|
+
args: [{
|
|
12047
|
+
providedIn: 'root',
|
|
12048
|
+
}]
|
|
12049
|
+
}] });
|
|
12050
|
+
|
|
10263
12051
|
class HelpersService {
|
|
10264
12052
|
Array = inject(ArrayHelpersService);
|
|
12053
|
+
ServiceEntityPostFile = inject(SaveEntityPostFileService);
|
|
10265
12054
|
ServiceResultFile = inject(ResultFileService);
|
|
10266
12055
|
ServiceResultResponse = inject(ResultResponseService);
|
|
12056
|
+
ServiceResultFileReturnBlobName = inject(ResultFileReturnBlobNameService);
|
|
10267
12057
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HelpersService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
10268
12058
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: HelpersService, providedIn: 'root' });
|
|
10269
12059
|
}
|
|
@@ -10721,19 +12511,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
10721
12511
|
}]
|
|
10722
12512
|
}] });
|
|
10723
12513
|
|
|
10724
|
-
class DsxMessagesService {
|
|
10725
|
-
AlertaService = inject(AlertaService);
|
|
10726
|
-
SweetDialog = inject(SweetAlert2DialogService);
|
|
10727
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
10728
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, providedIn: 'root' });
|
|
10729
|
-
}
|
|
10730
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxMessagesService, decorators: [{
|
|
10731
|
-
type: Injectable,
|
|
10732
|
-
args: [{
|
|
10733
|
-
providedIn: 'root',
|
|
10734
|
-
}]
|
|
10735
|
-
}] });
|
|
10736
|
-
|
|
10737
12514
|
// form-preview.service.ts
|
|
10738
12515
|
class FormPreviewService {
|
|
10739
12516
|
environment;
|
|
@@ -11980,5 +13757,5 @@ function sorensenDiceValidator(dataSource, key, umbral = 0.7) {
|
|
|
11980
13757
|
* Generated bundle index. Do not edit.
|
|
11981
13758
|
*/
|
|
11982
13759
|
|
|
11983
|
-
export { ACTION_TYPES, AlertaService, AppMessageErrorComponent, AppMessageHelpComponent, ArrowNavigationDirective, AuthorizeService, AutoScrollHeightDirective, BaseCRUDService, CACHE_KEYS, CacheService, CssV2Component, DSX_PALETTE, DateIndicator, DocxPreviewComponent, DsxAddToolsModule, DsxAutocomplete, DsxButtonComponent, DsxEnableDisable, DsxInputCurrency, DsxMessagesService, DsxSelect, DsxStatusToggle, DsxdateShort, DteService, ENVIRONMENT, EndpointService, ErrorHandlerService, FileComponent, FormPreviewService, GTQFormatter, HeaderDsx, HelpersService, HttpHelpersService, INITIAL_PARAMETERS, IcoLabel, IconDsxComponent, JoinByPipe, JsonHighlightPipe, JsonValuesDebujComponent, JsonViewerComponent, KpicardComponent, LoadingComponent, LoadingLottieComponent, LogoDsxComponent, MasterDetailChangeService, NavbarDsxComponent, NetworkStatusComponent, OnlyRangoPatternDirective, ParameterValuesService, PdfPreviewComponent, PrimeNgModule, QrGenerator, ResultFileService, SWEET_ALERT_THEMES, ScreenInspector, SecurityService, SelectAllOnFocusDirective, SpinnerLoadingService, SweetAlert2DialogService, TablePreviewService, TemplateHighlight, TokenPurposeLogin, TruncatePipe, UtilityAddService, asyncExistsValidator, atLeastOneFieldRequiredValidator, chainControlGroups, createCurrencyFormatter, createInitialCache, createTypedCacheProvider, cuiValidator, dateMinMaxValidator, dateRangeValidator, dateRangeValidatorFromTo, developmentEnvironment, getActionMessageConfig, getZeroBasedRolIndex, guardTokenPurposeGuard, httpAuthorizeInterceptor, minimumAgeValidator, nitValidator, productionEnvironment, provideEnvironment, sorensenDiceValidator, templateStructureValidator, templateVariablesValidator, validateEnvironmentConfig };
|
|
13760
|
+
export { ACTION_TYPES, AlertaService, AppMessageErrorComponent, AppMessageHelpComponent, ArrowNavigationDirective, AuthorizeService, AutoScrollHeightDirective, BaseCRUDService, CACHE_KEYS, CacheService, CssV2Component, DSX_PALETTE, DateIndicator, DocxPreviewComponent, DsxAddToolsModule, DsxAutocomplete, DsxButtonComponent, DsxEnableDisable, DsxInputCurrency, DsxMessagesService, DsxSelect, DsxStatusToggle, DsxdateShort, DsxdateTime, DteService, ENVIRONMENT, EndpointService, ErrorHandlerService, FileComponent, FormPreviewService, GTQFormatter, HeaderDsx, HelpersService, HttpHelpersService, INITIAL_PARAMETERS, IcoLabel, IconDsxComponent, JoinByPipe, JsonHighlightPipe, JsonValuesDebujComponent, JsonViewerComponent, KpicardComponent, LoadingComponent, LoadingLottieComponent, LogoDsxComponent, MasterDetailChangeService, NavbarDsxComponent, NetworkStatusComponent, OnlyRangoPatternDirective, ParameterValuesService, PdfPreviewComponent, PrimeNgModule, QrGenerator, ResultFileService, SWEET_ALERT_THEMES, ScreenInspector, SecurityService, SelectAllOnFocusDirective, SpinnerLoadingService, SweetAlert2DialogService, TablePreviewService, TemplateHighlight, TokenPurposeLogin, TruncatePipe, UtilityAddService, asyncExistsValidator, atLeastOneFieldRequiredValidator, chainControlGroups, createCurrencyFormatter, createInitialCache, createTypedCacheProvider, cuiValidator, dateMinMaxValidator, dateRangeValidator, dateRangeValidatorFromTo, developmentEnvironment, getActionMessageConfig, getZeroBasedRolIndex, guardTokenPurposeGuard, httpAuthorizeInterceptor, minimumAgeValidator, nitValidator, productionEnvironment, provideEnvironment, sorensenDiceValidator, templateStructureValidator, templateVariablesValidator, validateEnvironmentConfig };
|
|
11984
13761
|
//# sourceMappingURL=ngx-dsxlibrary.mjs.map
|