ngx-dsxlibrary 2.21.70 → 2.21.71

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.
@@ -2184,6 +2184,181 @@ function provideEnvironment(environment) {
2184
2184
  };
2185
2185
  }
2186
2186
 
2187
+ /**
2188
+ * action-message.model.ts
2189
+ *
2190
+ * Centraliza los tipos de acción y la configuración de mensajes para acciones de botones y operaciones comunes en la UI.
2191
+ *
2192
+ * - Define los tipos de acción estándar usados en dsx-button y otros componentes.
2193
+ * - Permite mantener uniformidad y evitar errores por typos en los tipos de acción.
2194
+ * - Provee la función getActionMessageConfig para obtener los textos, títulos e íconos de confirmación según la acción y el estado.
2195
+ * - Si se usa una acción válida pero sin mensaje configurado, en modo desarrollo muestra un warning para facilitar el mantenimiento.
2196
+ *
2197
+ * Uso recomendado:
2198
+ * - Importa ACTION_TYPES y ActionType donde se requiera validar o tipar acciones de botones.
2199
+ * - Usa getActionMessageConfig para obtener la configuración de confirmación antes de ejecutar acciones destructivas o de restauración.
2200
+ *
2201
+ * Ejemplo:
2202
+ * import { ACTION_TYPES, ActionType, getActionMessageConfig } from '.../action-message.model';
2203
+ * const config = getActionMessageConfig('softDelete', true);
2204
+ * // config: { title, message, icon }
2205
+ *
2206
+ * Si agregas un nuevo tipo de acción, recuerda añadir su mensaje en getActionMessageConfig.
2207
+ */
2208
+ /**
2209
+ * Tipos de acción disponibles para el componente `dsx-button`.
2210
+ *
2211
+ * Usa `ACTION_TYPES` en el consumidor para evitar typos.
2212
+ *
2213
+ * **Persistencia de datos (formulario)**
2214
+ * - `create` → Navega a la ruta de nuevo registro (azul)
2215
+ * - `saveOrUpdate` → Muestra "Guardar" si `id` es 0, "Actualizar" si `id` > 0 (amarillo)
2216
+ *
2217
+ * **Eliminar**
2218
+ * - `delete` → Eliminar registro permanentemente (rojo)
2219
+ * - `softDelete` → Borrar registro con borrado lógico (amarillo)
2220
+ *
2221
+ * **Formulario**
2222
+ * - `validate` → Validar formulario sin guardar (gris)
2223
+ * - `restore` → Restaurar datos originales (gris)
2224
+ *
2225
+ * **Navegación / Utilidades**
2226
+ * - `home` → Ir al inicio (gris)
2227
+ * - `refresh` → Recargar datos desde el servidor (morado)
2228
+ *
2229
+ * **Consulta / Descarga**
2230
+ * - `details` → Ver el detalle de un registro (azul índigo)
2231
+ * - `view` → Previsualizar o abrir detalle (azul cielo)
2232
+ * - `export` → Exportar información a Excel (verde Excel)
2233
+ * - `download` → Descargar archivo o recurso (ámbar cálido)
2234
+ * - `import` → Importar datos desde archivo (azul acero)
2235
+ */
2236
+ const ACTION_TYPES = {
2237
+ hardDelete: 'hardDelete',
2238
+ softDelete: 'softDelete',
2239
+ return: 'return',
2240
+ home: 'home',
2241
+ validate: 'validate',
2242
+ restore: 'restore',
2243
+ edit: 'edit',
2244
+ create: 'create',
2245
+ save: 'save',
2246
+ update: 'update',
2247
+ refresh: 'refresh',
2248
+ details: 'details',
2249
+ view: 'view',
2250
+ export: 'export',
2251
+ download: 'download',
2252
+ import: 'import',
2253
+ };
2254
+ function getActionMessageConfig(action, secondArg, thirdArg) {
2255
+ const isActive = action === 'softDelete' || action === 'hardDelete'
2256
+ ? typeof secondArg === 'boolean'
2257
+ ? secondArg
2258
+ : true
2259
+ : true;
2260
+ const dataPreview = action === 'softDelete' || action === 'hardDelete'
2261
+ ? thirdArg
2262
+ : typeof secondArg === 'string'
2263
+ ? secondArg
2264
+ : undefined;
2265
+ // Valores por defecto para todas las propiedades
2266
+ const defaults = {
2267
+ title: '',
2268
+ message: '',
2269
+ icon: undefined,
2270
+ confirmButtonText: undefined,
2271
+ cancelButtonText: undefined,
2272
+ showConfirmButton: undefined,
2273
+ showCancelButton: undefined,
2274
+ };
2275
+ if (action === 'softDelete') {
2276
+ if (isActive) {
2277
+ return {
2278
+ ...defaults,
2279
+ title: '¿Borrar registro?',
2280
+ message: 'El registro dejará de estar disponible en las listas principales.' +
2281
+ (dataPreview ? `<br>${dataPreview}` : ''),
2282
+ icono: 'icon/trash-bin.png',
2283
+ confirmButtonText: 'Eliminar',
2284
+ cancelButtonText: 'Cancelar',
2285
+ showConfirmButton: true,
2286
+ showCancelButton: true,
2287
+ };
2288
+ }
2289
+ else {
2290
+ return {
2291
+ ...defaults,
2292
+ title: '¿Restaurar registro?',
2293
+ message: 'El registro volverá a estar <strong>activo</strong> y visible en todas las listas principales del sistema.' +
2294
+ (dataPreview ? `<br>${dataPreview}` : ''),
2295
+ icono: 'icon/documentation.png',
2296
+ confirmButtonText: 'Sí, restaurar',
2297
+ cancelButtonText: 'Dejar archivado',
2298
+ showConfirmButton: true,
2299
+ showCancelButton: true,
2300
+ };
2301
+ }
2302
+ }
2303
+ if (action === 'hardDelete') {
2304
+ return {
2305
+ ...defaults,
2306
+ title: '¿Eliminar permanentemente?',
2307
+ message: 'Esta acción <strong>no se puede deshacer</strong>. Se perderán todos los datos asociados a este registro de forma inmediata.' +
2308
+ (dataPreview ? `<br>${dataPreview}` : ''),
2309
+ icono: 'icon/delete.png',
2310
+ confirmButtonText: 'Eliminar permanentemente',
2311
+ cancelButtonText: 'Cancelar',
2312
+ showConfirmButton: true,
2313
+ showCancelButton: true,
2314
+ };
2315
+ }
2316
+ if (action === 'save') {
2317
+ return {
2318
+ ...defaults,
2319
+ title: '¿Guardar registro?',
2320
+ message: 'El nuevo registro se guardará en el sistema.' +
2321
+ (dataPreview ? `<br>${dataPreview}` : ''),
2322
+ icono: 'icon2/save_2029637.png',
2323
+ confirmButtonText: 'Guardar',
2324
+ cancelButtonText: 'Cancelar',
2325
+ showConfirmButton: true,
2326
+ showCancelButton: true,
2327
+ };
2328
+ }
2329
+ if (action === 'update') {
2330
+ return {
2331
+ ...defaults,
2332
+ title: '¿Actualizar registro?',
2333
+ message: 'Los cambios realizados se actualizarán en el sistema.' +
2334
+ (dataPreview ? `<br>${dataPreview}` : ''),
2335
+ icono: 'icon2/save_2029637.png',
2336
+ confirmButtonText: 'Actualizar',
2337
+ cancelButtonText: 'Cancelar',
2338
+ showConfirmButton: true,
2339
+ showCancelButton: true,
2340
+ };
2341
+ }
2342
+ if (action === 'restore') {
2343
+ return {
2344
+ ...defaults,
2345
+ title: '¿Revertir cambios?',
2346
+ message: 'Se perderán todos los cambios que hayas realizado en el formulario y se volverán a cargar los datos originales.',
2347
+ icono: 'icon/data-recovery.png',
2348
+ confirmButtonText: 'Restaurar',
2349
+ cancelButtonText: 'Cancelar',
2350
+ showConfirmButton: true,
2351
+ showCancelButton: true,
2352
+ };
2353
+ }
2354
+ if (typeof ngDevMode !== 'undefined' && ngDevMode) {
2355
+ console.warn(`[getActionMessageConfig] Acción "${action}" existe pero no tiene mensaje configurado. ` +
2356
+ 'Agrega la configuración correspondiente para evitar este warning.');
2357
+ }
2358
+ // Retorna los valores por defecto si la acción no es válida
2359
+ return { ...defaults };
2360
+ }
2361
+
2187
2362
  const INITIAL_PARAMETERS = new InjectionToken('InitialParameters');
2188
2363
 
2189
2364
  /**
@@ -4102,181 +4277,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
4102
4277
  }]
4103
4278
  }] });
4104
4279
 
4105
- /**
4106
- * action-message.model.ts
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)
4142
- *
4143
- * **Navegación / Utilidades**
4144
- * - `home` → Ir al inicio (gris)
4145
- * - `refresh` → Recargar datos desde el servidor (morado)
4146
- *
4147
- * **Consulta / Descarga**
4148
- * - `details` → Ver el detalle de un registro (azul índigo)
4149
- * - `view` → Previsualizar o abrir detalle (azul cielo)
4150
- * - `export` → Exportar información a Excel (verde Excel)
4151
- * - `download` → Descargar archivo o recurso (ámbar cálido)
4152
- * - `import` → Importar datos desde archivo (azul acero)
4153
- */
4154
- const ACTION_TYPES = {
4155
- hardDelete: 'hardDelete',
4156
- softDelete: 'softDelete',
4157
- return: 'return',
4158
- home: 'home',
4159
- validate: 'validate',
4160
- restore: 'restore',
4161
- edit: 'edit',
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
4280
  /**
4281
4281
  * Paleta corporativa de colores para `dsx-button`.
4282
4282
  *
@@ -4303,22 +4303,22 @@ const DSX_PALETTE = {
4303
4303
  danger: '#EF5350',
4304
4304
  warning: '#FF7043',
4305
4305
  caution: '#FFB300',
4306
- success: '#0ba055', // Verde para Guardar
4307
- primary: '#0066cc', // Azul para Actualizar
4306
+ success: '#0ba055',
4307
+ primary: '#0066cc',
4308
4308
  info: '#26C6DA',
4309
4309
  neutral: '#90A4AE',
4310
- restore: '#B0BEC5', // Gris suave, menos resaltado
4311
- return: '#11438d', // Azul violeta para "Regresar registro archivado"
4310
+ restore: '#B0BEC5',
4311
+ return: '#11438d',
4312
4312
  view: '#00838F',
4313
4313
  excel: '#217346',
4314
4314
  download: '#E65100',
4315
4315
  import: '#546E7A',
4316
4316
  details: '#3949AB',
4317
- magenta: '#D81B60', // Magenta vibrante
4318
- cyan: '#00BCD4', // Cian brillante
4319
- olive: '#808000', // Verde oliva
4320
- brown: '#8D6E63', // Marrón suave
4321
- slate: '#607D8B', // Azul pizarra
4317
+ magenta: '#D81B60',
4318
+ cyan: '#00BCD4',
4319
+ olive: '#808000',
4320
+ brown: '#8D6E63',
4321
+ slate: '#607D8B',
4322
4322
  };
4323
4323
  /** Tipos que necesitan un `[id]` mayor a 0 para poder mostrarse. */
4324
4324
  const TYPES_REQUIRING_ID = [
@@ -4331,23 +4331,56 @@ class DsxButtonComponent {
4331
4331
  renderCount = 0;
4332
4332
  _paramService = inject((ParameterValuesService));
4333
4333
  _router = inject(Router);
4334
- // Agrega un input para el tamaño
4334
+ _isDev = isDevMode();
4335
+ lastValidationState = null;
4335
4336
  size = input(undefined, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
4336
4337
  _environment = inject(ENVIRONMENT, {
4337
4338
  optional: true,
4338
4339
  });
4339
4340
  constructor() {
4340
- if (isDevMode()) {
4341
+ // Solo activar efectos de debug en desarrollo
4342
+ if (this._isDev) {
4343
+ // Efecto principal que captura todo el estado
4341
4344
  effect(() => {
4342
4345
  if (!this.debug())
4343
4346
  return;
4344
4347
  this.renderCount++;
4345
- console.log(`[dsx-button:${this.type()}] render #${this.renderCount}`, {
4346
- id: this.id(),
4347
- type: this.type(),
4348
- disabled: this.disabled(),
4349
- routerLink: this.routerLink(),
4350
- });
4348
+ this.logFullState();
4349
+ });
4350
+ // Efecto específico para validaciones
4351
+ effect(() => {
4352
+ if (!this.debug())
4353
+ return;
4354
+ const visible = this.visible();
4355
+ const validations = {
4356
+ global: this.passesGlobalVisibilityPolicy(),
4357
+ parameter: this.passesParameterValidation(),
4358
+ id: this.passesIdValidation(),
4359
+ visible,
4360
+ };
4361
+ const stateKey = JSON.stringify(validations);
4362
+ if (stateKey !== this.lastValidationState) {
4363
+ this.lastValidationState = stateKey;
4364
+ this.logValidationIssues(validations);
4365
+ }
4366
+ });
4367
+ // Efecto para detectar problemas con IDs
4368
+ effect(() => {
4369
+ if (!this.debug())
4370
+ return;
4371
+ const type = this.type();
4372
+ if (TYPES_REQUIRING_ID.includes(type)) {
4373
+ const id = this.getNormalizedId();
4374
+ const routeId = this.getIdFromNavigationTarget(this.effectiveRouterLink());
4375
+ if (!id && !routeId) {
4376
+ console.warn(`%c⚠️ [dsx-button:${type}] ID no disponible para acción que lo requiere`, 'color:#ed6c02;font-weight:bold', {
4377
+ type,
4378
+ explicitId: this.id(),
4379
+ routeId,
4380
+ requireIdInput: this.requireIdInput(),
4381
+ });
4382
+ }
4383
+ }
4351
4384
  });
4352
4385
  }
4353
4386
  }
@@ -4357,17 +4390,7 @@ class DsxButtonComponent {
4357
4390
  type = input(ACTION_TYPES.hardDelete, ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
4358
4391
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
4359
4392
  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
4393
  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
4394
  width = input('9rem', ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
4372
4395
  iconOnly = input(false, ...(ngDevMode ? [{ debugName: "iconOnly" }] : /* istanbul ignore next */ []));
4373
4396
  iconOnlyWidth = input('2.5rem', ...(ngDevMode ? [{ debugName: "iconOnlyWidth" }] : /* istanbul ignore next */ []));
@@ -4383,24 +4406,12 @@ class DsxButtonComponent {
4383
4406
  tooltipOverride = input(undefined, ...(ngDevMode ? [{ debugName: "tooltipOverride" }] : /* istanbul ignore next */ []));
4384
4407
  iconOverride = input(undefined, ...(ngDevMode ? [{ debugName: "iconOverride" }] : /* istanbul ignore next */ []));
4385
4408
  primeIconOverride = input(undefined, ...(ngDevMode ? [{ debugName: "primeIconOverride" }] : /* istanbul ignore next */ []));
4386
- /** Permite personalizar el color usando únicamente tokens de la paleta DSX. */
4387
4409
  colorOverride = input(undefined, ...(ngDevMode ? [{ debugName: "colorOverride" }] : /* istanbul ignore next */ []));
4388
- /** Permite forzar estilo outlined o sólido por instancia. */
4389
4410
  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
4411
  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
4412
  showRestoreButtonOverride = input(undefined, ...(ngDevMode ? [{ debugName: "showRestoreButtonOverride" }] : /* istanbul ignore next */ []));
4400
4413
  action = output();
4401
- /** Valida y normaliza `routerPath`: agrega `/` inicial y rechaza caracteres inválidos. */
4402
4414
  resolvedRouterLink = computed(() => {
4403
- this.log('computed resolvedRouterLink');
4404
4415
  const raw = this.routerPath();
4405
4416
  if (!raw)
4406
4417
  return undefined;
@@ -4409,23 +4420,22 @@ class DsxButtonComponent {
4409
4420
  const rawSegments = normalizedRaw.split('/');
4410
4421
  const hasEmptySegments = normalizedRaw.length > 0 && rawSegments.some((s) => s.length === 0);
4411
4422
  if (hasEmptySegments) {
4412
- console.warn(`[dsx-button] routerPath contiene segmentos vacíos ("//" o "/" al final). ` +
4413
- `Ruta recibida: "${raw}"`);
4423
+ if (this._isDev && this.debug()) {
4424
+ console.warn(`[dsx-button] routerPath contiene segmentos vacíos: "${raw}"`);
4425
+ }
4414
4426
  return undefined;
4415
4427
  }
4416
4428
  const segments = rawSegments.filter(Boolean);
4417
4429
  const invalid = segments.filter((s) => !VALID_SEGMENT.test(s));
4418
4430
  if (invalid.length > 0) {
4419
- console.warn(`[dsx-button] routerPath contiene segmentos inválidos: "${invalid.join('", "')}". ` +
4420
- `Solo se permiten letras, números, guion (-), guion bajo (_), punto (.) y dos puntos (:). ` +
4421
- `Ruta recibida: "${raw}"`);
4431
+ if (this._isDev && this.debug()) {
4432
+ console.warn(`[dsx-button] routerPath contiene segmentos inválidos: "${invalid.join('", "')}"`);
4433
+ }
4422
4434
  return undefined;
4423
4435
  }
4424
4436
  return `/${segments.join('/')}`;
4425
4437
  }, ...(ngDevMode ? [{ debugName: "resolvedRouterLink" }] : /* istanbul ignore next */ []));
4426
- /** Prioriza routerPath (validado) sobre routerLink (libre). */
4427
4438
  effectiveRouterLink = computed(() => {
4428
- this.log('computed effectiveRouterLink');
4429
4439
  const resolvedPath = this.resolvedRouterLink();
4430
4440
  if (resolvedPath) {
4431
4441
  return resolvedPath;
@@ -4438,7 +4448,6 @@ class DsxButtonComponent {
4438
4448
  if (!trimmed) {
4439
4449
  return link;
4440
4450
  }
4441
- // URLs externas no se normalizan ni validan como rutas internas.
4442
4451
  if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed)) {
4443
4452
  return link;
4444
4453
  }
@@ -4448,20 +4457,20 @@ class DsxButtonComponent {
4448
4457
  const hasEmptySegments = pathForValidation.length > 0 &&
4449
4458
  rawSegments.some((segment) => segment.length === 0);
4450
4459
  if (hasEmptySegments) {
4451
- console.warn(`[dsx-button] routerLink contiene segmentos vacíos ("//" o "/" al final). ` +
4452
- `Ruta recibida: "${link}"`);
4460
+ if (this._isDev && this.debug()) {
4461
+ console.warn(`[dsx-button] routerLink contiene segmentos vacíos: "${link}"`);
4462
+ }
4453
4463
  return link;
4454
4464
  }
4455
4465
  const invalid = rawSegments
4456
4466
  .filter(Boolean)
4457
4467
  .filter((segment) => !VALID_SEGMENT.test(segment));
4458
4468
  if (invalid.length > 0) {
4459
- console.warn(`[dsx-button] routerLink contiene segmentos inválidos: "${invalid.join('", "')}". ` +
4460
- `Solo se permiten letras, números, guion (-), guion bajo (_), punto (.) y dos puntos (:). ` +
4461
- `Ruta recibida: "${link}"`);
4469
+ if (this._isDev && this.debug()) {
4470
+ console.warn(`[dsx-button] routerLink contiene segmentos inválidos: "${invalid.join('", "')}"`);
4471
+ }
4462
4472
  return link;
4463
4473
  }
4464
- // Si llega una ruta simple sin prefijo, se normaliza a absoluta para evitar navegación relativa inesperada.
4465
4474
  if (link.startsWith('/') ||
4466
4475
  link.startsWith('./') ||
4467
4476
  link.startsWith('../')) {
@@ -4472,23 +4481,6 @@ class DsxButtonComponent {
4472
4481
  }
4473
4482
  return `/${trimmed.split('/').filter(Boolean).join('/')}`;
4474
4483
  }, ...(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
4484
  buttonStyleComputed = computed(() => {
4493
4485
  const color = DSX_PALETTE[this.config.colorToken];
4494
4486
  const width = this.iconOnly() ? this.iconOnlyWidth() : this.width();
@@ -4518,17 +4510,13 @@ class DsxButtonComponent {
4518
4510
  get buttonLabel() {
4519
4511
  return this.iconOnly() ? undefined : this.label;
4520
4512
  }
4521
- // Y modifica el getter buttonSize
4522
4513
  get buttonSize() {
4523
- // Si el usuario especificó un tamaño, úsalo
4524
4514
  if (this.size()) {
4525
4515
  return this.size();
4526
4516
  }
4527
- // Si es compacto, siempre small
4528
4517
  if (this.isCompactIconButton) {
4529
4518
  return 'small';
4530
4519
  }
4531
- // Por defecto, small para todos los botones
4532
4520
  return 'small';
4533
4521
  }
4534
4522
  get buttonStyleClass() {
@@ -4541,7 +4529,6 @@ class DsxButtonComponent {
4541
4529
  const cfg = ACTION_CONFIG[this.type()];
4542
4530
  const colorOverride = this.colorOverride();
4543
4531
  const outlinedOverride = this.outlinedOverride();
4544
- // Ya no se gestiona softDelete/return como modo, cada acción es independiente
4545
4532
  return {
4546
4533
  ...cfg,
4547
4534
  colorToken: colorOverride ?? cfg.colorToken,
@@ -4551,10 +4538,6 @@ class DsxButtonComponent {
4551
4538
  get label() {
4552
4539
  return this.labelOverride() ?? this.config.label;
4553
4540
  }
4554
- // get tooltip(): string {
4555
- // this.log('getter tooltip');
4556
- // return this.tooltipOverride() ?? this.config.tooltip;
4557
- // }
4558
4541
  tooltip = computed(() => {
4559
4542
  return this.tooltipOverride() ?? this.config.tooltip;
4560
4543
  }, ...(ngDevMode ? [{ debugName: "tooltip" }] : /* istanbul ignore next */ []));
@@ -4570,18 +4553,6 @@ class DsxButtonComponent {
4570
4553
  }
4571
4554
  return 'primeIcon' in this.config ? this.config.primeIcon : undefined;
4572
4555
  }
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
4556
  visible = computed(() => {
4586
4557
  return (this.passesGlobalVisibilityPolicy() &&
4587
4558
  this.passesParameterValidation() &&
@@ -4606,45 +4577,44 @@ class DsxButtonComponent {
4606
4577
  return true;
4607
4578
  }
4608
4579
  async onButtonClick() {
4609
- this.log('click', {
4610
- id: this.id(),
4611
- target: this.effectiveRouterLink(),
4612
- });
4580
+ if (this._isDev && this.debug()) {
4581
+ console.group(`%c🖱️ [dsx-button:${this.type()}] Click`, 'color:#1976d2;font-weight:bold;font-size:13px');
4582
+ console.log('ID:', this.id());
4583
+ console.log('Target:', this.effectiveRouterLink());
4584
+ console.log('Disabled:', this.disabled());
4585
+ console.groupEnd();
4586
+ }
4613
4587
  this.action.emit();
4614
4588
  const target = this.effectiveRouterLink();
4615
4589
  if (target == null) {
4616
- if (this.type() === 'home') {
4617
- console.warn('[dsx-button] El botón tipo "home" no tiene routerLink ni routerPath configurado.');
4590
+ if (this.type() === 'home' && this._isDev && this.debug()) {
4591
+ console.warn(`[dsx-button:${this.type()}] No tiene routerLink ni routerPath configurado.`);
4618
4592
  }
4619
4593
  return;
4620
4594
  }
4621
4595
  try {
4622
4596
  const start = performance.now();
4623
4597
  const navigated = await this.navigateToTarget(target);
4624
- this.log('navigation', {
4625
- target,
4626
- navigated,
4627
- durationMs: Math.round(performance.now() - start),
4628
- });
4598
+ const duration = Math.round(performance.now() - start);
4599
+ if (this._isDev && this.debug()) {
4600
+ const emoji = navigated ? '✅' : '⚠️';
4601
+ const color = navigated ? '#2e7d32' : '#ed6c02';
4602
+ console.log(`%c${emoji} Navegación ${navigated ? 'exitosa' : 'rechazada'} (${duration}ms)`, `color:${color};font-weight:bold`, target);
4603
+ }
4629
4604
  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
4605
  const retried = await new Promise((resolve) => setTimeout(async () => resolve(await this.navigateToTarget(target)), 0));
4634
- if (!retried) {
4635
- console.warn('[dsx-button] Navegación rechazada (posiblemente por un route guard). ' +
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);
4606
+ if (!retried && this._isDev && this.debug()) {
4607
+ console.warn(`[dsx-button:${this.type()}] Navegación rechazada por route guard.`, target);
4639
4608
  }
4640
4609
  }
4641
4610
  }
4642
4611
  catch (error) {
4643
- console.warn('[dsx-button] Error al navegar desde el botón.', target, error);
4612
+ if (this._isDev && this.debug()) {
4613
+ console.error(`[dsx-button:${this.type()}] Error al navegar:`, target, error);
4614
+ }
4644
4615
  }
4645
4616
  }
4646
4617
  passesIdValidation() {
4647
- this.log('passesIdValidation');
4648
4618
  const explicitId = this.getNormalizedId();
4649
4619
  const routeId = this.getIdFromNavigationTarget(this.effectiveRouterLink());
4650
4620
  const id = explicitId ?? routeId;
@@ -4653,7 +4623,6 @@ class DsxButtonComponent {
4653
4623
  this.type() === 'softDelete' ||
4654
4624
  this.type() === 'restore' ||
4655
4625
  this.type() === 'return';
4656
- // Permite exigir que venga [id] cuando el caso de uso lo requiera.
4657
4626
  if (this.requireIdInput() && typeof id !== 'number') {
4658
4627
  return false;
4659
4628
  }
@@ -4713,16 +4682,6 @@ class DsxButtonComponent {
4713
4682
  }
4714
4683
  return undefined;
4715
4684
  }
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
4685
  navigateToTarget(target) {
4727
4686
  if (Array.isArray(target)) {
4728
4687
  return this._router.navigate(target);
@@ -4734,41 +4693,154 @@ class DsxButtonComponent {
4734
4693
  return this._router.navigateByUrl(target);
4735
4694
  }
4736
4695
  passesParameterValidation() {
4737
- this.log('passesParameterValidation');
4738
4696
  const parameterName = this.parameterName();
4739
- // Si no se define un parámetro de seguridad, el botón se muestra sin restricción.
4740
4697
  if (!parameterName) {
4741
4698
  return true;
4742
4699
  }
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
4700
  if (!this._paramService.loaded) {
4746
4701
  return false;
4747
4702
  }
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
4703
  return this._paramService.isParameterValue(parameterName, this.parameterExpectedValue(), this.parameterIndex());
4752
4704
  }
4753
- log(message, data) {
4754
- if (!this.debug())
4705
+ // ============ MÉTODOS DE DEBUG ============
4706
+ logFullState() {
4707
+ if (!this._isDev || !this.debug())
4755
4708
  return;
4756
- console.log(`%c[dsx-button:${this.type()}] ${message}`, 'color:#1976d2;font-weight:bold', data ?? '');
4709
+ const id = this.getNormalizedId();
4710
+ const routeId = this.getIdFromNavigationTarget(this.effectiveRouterLink());
4711
+ const paramName = this.parameterName();
4712
+ const paramLoaded = this._paramService.loaded;
4713
+ const paramValue = paramName && paramLoaded
4714
+ ? this._paramService.getValue(paramName, this.parameterIndex())
4715
+ : undefined;
4716
+ console.group(`%c📊 [dsx-button:${this.type()}] Render #${this.renderCount}`, 'color:#1976d2;font-weight:bold;font-size:14px');
4717
+ // 1. Estado general
4718
+ console.log('🎯 Estado:', {
4719
+ visible: this.visible(),
4720
+ disabled: this.disabled(),
4721
+ type: this.type(),
4722
+ size: this.size(),
4723
+ iconOnly: this.iconOnly(),
4724
+ rounded: this.rounded(),
4725
+ isCompact: this.isCompactIconButton,
4726
+ });
4727
+ // 2. Configuración
4728
+ console.log('⚙️ Configuración:', {
4729
+ label: this.label,
4730
+ icon: this.icon,
4731
+ primeIcon: this.primeIcon,
4732
+ colorToken: this.config.colorToken,
4733
+ outlined: this.config.outlined,
4734
+ tooltip: this.tooltip(),
4735
+ });
4736
+ // 3. Router
4737
+ console.log('🔗 Router:', {
4738
+ routerLink: this.routerLink(),
4739
+ routerPath: this.routerPath(),
4740
+ resolvedRouterLink: this.resolvedRouterLink(),
4741
+ effectiveRouterLink: this.effectiveRouterLink(),
4742
+ });
4743
+ // 4. IDs
4744
+ console.log('🆔 IDs:', {
4745
+ inputId: this.id(),
4746
+ normalizedId: id,
4747
+ routeId: routeId,
4748
+ requireIdInput: this.requireIdInput(),
4749
+ requiresExistingRecord: this.requiresIdGreaterThanZero() ||
4750
+ ['hardDelete', 'softDelete', 'restore', 'return'].includes(this.type()),
4751
+ });
4752
+ // 5. Parámetros
4753
+ if (paramName) {
4754
+ console.log('🔐 Parámetros:', {
4755
+ name: paramName,
4756
+ expected: this.parameterExpectedValue(),
4757
+ index: this.parameterIndex(),
4758
+ loaded: paramLoaded,
4759
+ actual: paramValue,
4760
+ matches: paramLoaded
4761
+ ? paramValue === this.parameterExpectedValue()
4762
+ : 'No cargado',
4763
+ });
4764
+ }
4765
+ // 6. Overrides
4766
+ const overrides = {
4767
+ label: this.labelOverride(),
4768
+ tooltip: this.tooltipOverride(),
4769
+ icon: this.iconOverride(),
4770
+ primeIcon: this.primeIconOverride(),
4771
+ color: this.colorOverride(),
4772
+ outlined: this.outlinedOverride(),
4773
+ showValidate: this.showValidateButtonOverride(),
4774
+ showRestore: this.showRestoreButtonOverride(),
4775
+ };
4776
+ const activeOverrides = Object.entries(overrides)
4777
+ .filter(([_, value]) => value !== undefined)
4778
+ .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
4779
+ if (Object.keys(activeOverrides).length > 0) {
4780
+ console.log('🔄 Overrides activos:', activeOverrides);
4781
+ }
4782
+ // 7. Validaciones
4783
+ console.log('✅ Validaciones:', {
4784
+ globalVisibility: this.passesGlobalVisibilityPolicy(),
4785
+ parameterValidation: this.passesParameterValidation(),
4786
+ idValidation: this.passesIdValidation(),
4787
+ visible: this.visible(),
4788
+ });
4789
+ // 8. Estilos
4790
+ console.log('🎨 Estilos:', {
4791
+ width: this.iconOnly() ? this.iconOnlyWidth() : this.width(),
4792
+ style: this.buttonStyleComputed(),
4793
+ styleClass: this.buttonStyleClass,
4794
+ size: this.buttonSize,
4795
+ });
4796
+ console.groupEnd();
4757
4797
  }
4758
- warn(message, data) {
4759
- if (!this.debug())
4798
+ logValidationIssues(validations) {
4799
+ if (!this._isDev || !this.debug())
4760
4800
  return;
4761
- console.warn(`[dsx-button:${this.type()}] ${message}`, data ?? '');
4801
+ if (!validations.visible) {
4802
+ console.group(`%c❌ [dsx-button:${this.type()}] NO VISIBLE`, 'color:#d32f2f;font-weight:bold;font-size:13px');
4803
+ console.log('Validaciones fallidas:', {
4804
+ globalVisibility: validations.global,
4805
+ parameterValidation: validations.parameter,
4806
+ idValidation: validations.id,
4807
+ });
4808
+ if (!validations.id) {
4809
+ const id = this.getNormalizedId();
4810
+ const routeId = this.getIdFromNavigationTarget(this.effectiveRouterLink());
4811
+ console.warn('⚠️ Problema con ID:', {
4812
+ explicitId: this.id(),
4813
+ normalizedId: id,
4814
+ routeId: routeId,
4815
+ requireIdInput: this.requireIdInput(),
4816
+ requiresExistingRecord: this.requiresIdGreaterThanZero() ||
4817
+ ['hardDelete', 'softDelete', 'restore', 'return'].includes(this.type()),
4818
+ });
4819
+ }
4820
+ if (!validations.parameter && this.parameterName()) {
4821
+ console.warn('⚠️ Problema con parámetro:', {
4822
+ name: this.parameterName(),
4823
+ expected: this.parameterExpectedValue(),
4824
+ index: this.parameterIndex(),
4825
+ loaded: this._paramService.loaded,
4826
+ actualValue: this._paramService.loaded
4827
+ ? this._paramService.getValue(this.parameterName(), this.parameterIndex())
4828
+ : 'No cargado',
4829
+ });
4830
+ }
4831
+ console.groupEnd();
4832
+ }
4762
4833
  }
4763
- time(label) {
4764
- if (!this.debug())
4834
+ // Métodos de log existentes mejorados
4835
+ log(message, data) {
4836
+ if (!this._isDev || !this.debug())
4765
4837
  return;
4766
- console.time(`[dsx-button:${this.type()}] ${label}`);
4838
+ console.log(`%c[dsx-button:${this.type()}] ${message}`, 'color:#1976d2;font-weight:bold', data ?? '');
4767
4839
  }
4768
- timeEnd(label) {
4769
- if (!this.debug())
4840
+ warn(message, data) {
4841
+ if (!this._isDev || !this.debug())
4770
4842
  return;
4771
- console.timeEnd(`[dsx-button:${this.type()}] ${label}`);
4843
+ console.warn(`[dsx-button:${this.type()}] ${message}`, data ?? '');
4772
4844
  }
4773
4845
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4774
4846
  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 +4945,7 @@ const ACTION_CONFIG = {
4873
4945
  view: {
4874
4946
  label: 'Visualizar',
4875
4947
  icon: 'visibility',
4876
- primeIcon: 'pi pi-eye',
4948
+ primeIcon: 'fa-solid fa-eye',
4877
4949
  colorToken: 'view',
4878
4950
  tooltip: 'Vista previa',
4879
4951
  },
@@ -8702,6 +8774,10 @@ class DsxAutocomplete {
8702
8774
  ngControl = null;
8703
8775
  isDevModeInternal = isDevMode();
8704
8776
  controlSignal = signal(null, ...(ngDevMode ? [{ debugName: "controlSignal" }] : /* istanbul ignore next */ []));
8777
+ // ✅ Almacenar IDs pendientes (privado)
8778
+ pendingIdsInternal = [];
8779
+ pendingValue = null;
8780
+ pendingLogs = [];
8705
8781
  datasource = input.required(...(ngDevMode ? [{ debugName: "datasource" }] : /* istanbul ignore next */ []));
8706
8782
  optionLabel = input.required(...(ngDevMode ? [{ debugName: "optionLabel" }] : /* istanbul ignore next */ []));
8707
8783
  label = input('Seleccionar', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
@@ -8723,6 +8799,14 @@ class DsxAutocomplete {
8723
8799
  get isDevMode() {
8724
8800
  return this.isDevModeInternal;
8725
8801
  }
8802
+ // ✅ Getter público para IDs pendientes (solo para debug)
8803
+ get pendingIds() {
8804
+ return this.pendingIdsInternal;
8805
+ }
8806
+ // ✅ Getter para logs de pendientes
8807
+ get pendingLogsInfo() {
8808
+ return this.pendingLogs.join(' | ');
8809
+ }
8726
8810
  get formValue() {
8727
8811
  return this.ngControl?.control?.value;
8728
8812
  }
@@ -8773,11 +8857,19 @@ class DsxAutocomplete {
8773
8857
  });
8774
8858
  }
8775
8859
  });
8860
+ // ✅ Effect mejorado para recargar cuando cambia el datasource
8776
8861
  effect(() => {
8777
8862
  const ds = this.datasource();
8778
- if (ds.length > 0 && this.initialValueSet) {
8863
+ if (ds.length > 0) {
8779
8864
  this.logDebug('📚 Datasource actualizado:', ds.length);
8780
- if (this.ngControl?.control?.value) {
8865
+ // Si hay IDs pendientes, intentar cargarlos
8866
+ if (this.pendingIdsInternal.length > 0) {
8867
+ this.logDebug('🔄 Procesando IDs pendientes:', this.pendingIdsInternal);
8868
+ this.procesarIdsPendientes(this.pendingIdsInternal);
8869
+ // ✅ No limpiamos inmediatamente para poder ver el estado en debug
8870
+ }
8871
+ // ✅ Si ya se inicializó y hay valor en el formulario, recargar
8872
+ if (this.initialValueSet && this.ngControl?.control?.value) {
8781
8873
  this.logDebug('🔄 Recargando valor con nuevo datasource');
8782
8874
  this.writeValue(this.ngControl.control.value);
8783
8875
  }
@@ -8824,6 +8916,70 @@ class DsxAutocomplete {
8824
8916
  this.lastLogTime = now;
8825
8917
  console.log(`[DsxAutocomplete] ${message}`, data || '');
8826
8918
  }
8919
+ // ✅ Método para registrar eventos de pendientes
8920
+ logPending(message, ids) {
8921
+ if (!this.shouldDebug())
8922
+ return;
8923
+ const timestamp = new Date().toISOString().substring(11, 19);
8924
+ const logEntry = `[${timestamp}] ${message} ${ids ? `IDs: [${ids.join(', ')}]` : ''}`;
8925
+ this.pendingLogs.push(logEntry);
8926
+ // Mantener solo los últimos 10 logs
8927
+ if (this.pendingLogs.length > 10) {
8928
+ this.pendingLogs.shift();
8929
+ }
8930
+ console.log(`[DsxAutocomplete] ⏳ PENDING: ${logEntry}`, {
8931
+ pendingIds: this.pendingIdsInternal,
8932
+ totalPending: this.pendingIdsInternal.length,
8933
+ });
8934
+ }
8935
+ // ✅ Nuevo método: Procesar IDs pendientes con logs mejorados
8936
+ procesarIdsPendientes(ids) {
8937
+ if (ids.length === 0) {
8938
+ this.logDebug('✅ No hay IDs pendientes para procesar');
8939
+ return;
8940
+ }
8941
+ const sourceData = this.datasource();
8942
+ if (sourceData.length === 0) {
8943
+ this.logPending('⚠️ Datasource vacío, no se pueden procesar IDs pendientes', ids);
8944
+ return;
8945
+ }
8946
+ this.logPending(`🔍 Procesando ${ids.length} IDs pendientes`, ids);
8947
+ const objetos = sourceData.filter((item) => ids.includes(Number(item[this.idKey()])));
8948
+ const objetosOrdenados = ids
8949
+ .map((id) => objetos.find((item) => Number(item[this.idKey()]) === id))
8950
+ .filter((item) => item !== undefined);
8951
+ const encontrados = objetosOrdenados.length;
8952
+ const noEncontrados = ids.length - encontrados;
8953
+ this.logPending(`✅ Procesados: ${encontrados} encontrados, ${noEncontrados} no encontrados`, ids);
8954
+ if (noEncontrados > 0) {
8955
+ const idsNoEncontrados = ids.filter((id) => !objetos.some((item) => Number(item[this.idKey()]) === id));
8956
+ this.logPending(`⚠️ IDs no encontrados en datasource:`, idsNoEncontrados);
8957
+ }
8958
+ // Si se encontraron todos los objetos, actualizar la vista
8959
+ if (encontrados > 0) {
8960
+ if (this.isMultiple()) {
8961
+ this.value = objetosOrdenados;
8962
+ }
8963
+ else {
8964
+ this.value = objetosOrdenados.length > 0 ? objetosOrdenados[0] : null;
8965
+ }
8966
+ this.cdr.detectChanges();
8967
+ // ✅ Limpiar pendientes solo si se encontraron todos
8968
+ if (noEncontrados === 0) {
8969
+ this.pendingIdsInternal = [];
8970
+ this.logPending('✅ Todos los IDs pendientes fueron resueltos', ids);
8971
+ }
8972
+ else {
8973
+ // Mantener solo los no encontrados
8974
+ const idsNoEncontrados = ids.filter((id) => !objetos.some((item) => Number(item[this.idKey()]) === id));
8975
+ this.pendingIdsInternal = idsNoEncontrados;
8976
+ this.logPending(`⏳ ${idsNoEncontrados.length} IDs pendientes aún no resueltos`, idsNoEncontrados);
8977
+ }
8978
+ }
8979
+ else {
8980
+ this.logPending('❌ Ningún ID pendiente pudo ser resuelto', ids);
8981
+ }
8982
+ }
8827
8983
  searchRequisitos(event) {
8828
8984
  const inicio = performance.now();
8829
8985
  const label = this.optionLabel();
@@ -8862,13 +9018,10 @@ class DsxAutocomplete {
8862
9018
  .replace(/\s+/g, ' ')
8863
9019
  .trim();
8864
9020
  }
8865
- // ✨ CORREGIDO: evaluarYAgregar para manejar valores no encontrados
8866
9021
  evaluarYAgregar(event) {
8867
9022
  const valorInput = event.target.value?.trim();
8868
9023
  if (!valorInput) {
8869
9024
  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
9025
  if (this.obtenerArrayValue().length === 0) {
8873
9026
  this.actualizarFormulario([]);
8874
9027
  }
@@ -8929,11 +9082,8 @@ class DsxAutocomplete {
8929
9082
  this.logDebug('🆕 Nuevo elemento creado', valorInput);
8930
9083
  }
8931
9084
  else {
8932
- // ✅ NO permite crear: limpiar el valor a null o []
8933
9085
  this.logDebug('⚠️ No se encontraron coincidencias y no se permite crear', valorInput);
8934
- // ✨ IMPORTANTE: Limpiar la selección a null o []
8935
9086
  this.actualizarFormulario([]);
8936
- // Marcar como touched para mostrar validación
8937
9087
  if (this.ngControl?.control) {
8938
9088
  this.ngControl.control.markAsTouched();
8939
9089
  this.onTouched();
@@ -9059,9 +9209,7 @@ class DsxAutocomplete {
9059
9209
  this.timeoutLimpieza = null;
9060
9210
  }
9061
9211
  }
9062
- // ✨ MEJORADO: actualizarFormulario para manejar correctamente valores vacíos
9063
9212
  actualizarFormulario(nuevaLista) {
9064
- // Si la lista está vacía, siempre enviar null o [] según el modo
9065
9213
  if (nuevaLista.length === 0) {
9066
9214
  if (this.isMultiple()) {
9067
9215
  this.value = [];
@@ -9077,7 +9225,6 @@ class DsxAutocomplete {
9077
9225
  });
9078
9226
  return;
9079
9227
  }
9080
- // Si hay elementos, procesar normalmente
9081
9228
  if (this.isMultiple()) {
9082
9229
  this.value = nuevaLista;
9083
9230
  }
@@ -9096,6 +9243,7 @@ class DsxAutocomplete {
9096
9243
  getFormControlName() {
9097
9244
  return this.ngControl?.name || null;
9098
9245
  }
9246
+ // ✨ MEJORADO: writeValue con manejo de IDs pendientes y logs
9099
9247
  writeValue(value) {
9100
9248
  const timestamp = new Date().toISOString();
9101
9249
  this.logDebug(`🔄 writeValue INVOCADO ${timestamp}`, {
@@ -9113,10 +9261,16 @@ class DsxAutocomplete {
9113
9261
  isMultiple: this.isMultiple(),
9114
9262
  hasDatasource: this.datasource().length > 0,
9115
9263
  datasourceSize: this.datasource().length,
9264
+ pendingIdsActuales: this.pendingIdsInternal,
9116
9265
  });
9117
9266
  if (value === null || value === undefined) {
9118
9267
  this.logDebug(`⚠️ Valor null/undefined, limpiando selección`);
9119
9268
  this.value = this.isMultiple() ? [] : null;
9269
+ if (this.pendingIdsInternal.length > 0) {
9270
+ this.logPending('🧹 Limpiando IDs pendientes por null/undefined', this.pendingIdsInternal);
9271
+ this.pendingIdsInternal = [];
9272
+ }
9273
+ this.pendingValue = null;
9120
9274
  this.initialValueSet = true;
9121
9275
  this.cdr.markForCheck();
9122
9276
  this.cdr.detectChanges();
@@ -9127,6 +9281,11 @@ class DsxAutocomplete {
9127
9281
  if (Array.isArray(value)) {
9128
9282
  if (value.length === 0) {
9129
9283
  this.value = this.isMultiple() ? [] : null;
9284
+ if (this.pendingIdsInternal.length > 0) {
9285
+ this.logPending('🧹 Limpiando IDs pendientes por array vacío', this.pendingIdsInternal);
9286
+ this.pendingIdsInternal = [];
9287
+ }
9288
+ this.pendingValue = null;
9130
9289
  this.initialValueSet = true;
9131
9290
  this.cdr.markForCheck();
9132
9291
  this.cdr.detectChanges();
@@ -9134,41 +9293,73 @@ class DsxAutocomplete {
9134
9293
  }
9135
9294
  if (typeof value[0] === 'number') {
9136
9295
  ids = value;
9296
+ this.logDebug(`📊 Recibidos ${ids.length} IDs:`, ids);
9137
9297
  }
9138
9298
  else if (typeof value[0] === 'object' && value[0] !== null) {
9139
9299
  items = value;
9300
+ this.logDebug(`📊 Recibidos ${items.length} objetos:`, items.map((i) => i[this.optionLabel()]));
9140
9301
  }
9141
9302
  }
9142
9303
  else if (typeof value === 'number') {
9143
9304
  ids = [value];
9305
+ this.logDebug(`📊 Recibido 1 ID:`, [value]);
9144
9306
  }
9145
9307
  else if (typeof value === 'object' && value !== null) {
9146
9308
  items = [value];
9309
+ this.logDebug(`📊 Recibido 1 objeto:`, value[this.optionLabel()]);
9147
9310
  }
9148
9311
  else {
9149
9312
  this.value = this.isMultiple() ? [] : null;
9313
+ if (this.pendingIdsInternal.length > 0) {
9314
+ this.logPending('🧹 Limpiando IDs pendientes por tipo no soportado', this.pendingIdsInternal);
9315
+ this.pendingIdsInternal = [];
9316
+ }
9317
+ this.pendingValue = null;
9150
9318
  this.initialValueSet = true;
9151
9319
  this.cdr.markForCheck();
9152
9320
  this.cdr.detectChanges();
9153
9321
  return;
9154
9322
  }
9155
9323
  const sourceData = this.datasource();
9156
- if (ids.length > 0) {
9157
- if (sourceData.length === 0) {
9158
- this.logDebug(`⚠️ Datasource vacío, guardando IDs para luego`);
9159
- this.value = this.isMultiple() ? [] : null;
9160
- this.initialValueSet = true;
9161
- this.cdr.markForCheck();
9162
- this.cdr.detectChanges();
9163
- return;
9164
- }
9324
+ // Si tenemos IDs y el datasource está vacío, guardarlos para después
9325
+ if (ids.length > 0 && sourceData.length === 0) {
9326
+ this.logPending(`⏳ Datasource vacío (${sourceData.length}), guardando ${ids.length} IDs para procesar después`, ids);
9327
+ this.pendingIdsInternal = ids;
9328
+ this.pendingValue = value;
9329
+ this.initialValueSet = true;
9330
+ // ✅ Mostrar un placeholder visual mientras se cargan los datos
9331
+ this.value = this.isMultiple() ? [] : null;
9332
+ this.cdr.markForCheck();
9333
+ this.cdr.detectChanges();
9334
+ this.logDebug(`⏳ IDs guardados como pendientes:`, this.pendingIdsInternal);
9335
+ return;
9336
+ }
9337
+ // ✅ Si tenemos IDs y el datasource existe, buscar los objetos
9338
+ if (ids.length > 0 && sourceData.length > 0) {
9339
+ this.logDebug(`🔍 Buscando ${ids.length} IDs en datasource de ${sourceData.length} elementos`);
9165
9340
  const objetos = sourceData.filter((item) => ids.includes(Number(item[this.idKey()])));
9166
9341
  const objetosOrdenados = ids
9167
9342
  .map((id) => objetos.find((item) => Number(item[this.idKey()]) === id))
9168
9343
  .filter((item) => item !== undefined);
9169
9344
  items = objetosOrdenados;
9345
+ // ✅ Si no se encontraron algunos IDs, guardarlos como pendientes
9346
+ const idsNoEncontrados = ids.filter((id) => !objetos.some((item) => Number(item[this.idKey()]) === id));
9347
+ if (idsNoEncontrados.length > 0) {
9348
+ this.logPending(`⚠️ ${idsNoEncontrados.length} IDs no encontrados en datasource`, idsNoEncontrados);
9349
+ this.pendingIdsInternal = idsNoEncontrados;
9350
+ this.pendingValue = value;
9351
+ }
9352
+ else {
9353
+ // ✅ Todos los IDs fueron encontrados
9354
+ if (this.pendingIdsInternal.length > 0) {
9355
+ this.logPending('✅ Todos los IDs pendientes fueron resueltos', this.pendingIdsInternal);
9356
+ this.pendingIdsInternal = [];
9357
+ this.pendingValue = null;
9358
+ }
9359
+ }
9170
9360
  this.logDebug(`🔍 Encontrados ${items.length} de ${ids.length} IDs`);
9171
9361
  }
9362
+ // ✅ Verificar que los items existan en el datasource
9172
9363
  if (items.length > 0 && sourceData.length > 0) {
9173
9364
  const itemsValidos = items.filter((item) => sourceData.some((s) => s[this.idKey()] === item[this.idKey()]));
9174
9365
  if (itemsValidos.length !== items.length) {
@@ -9179,6 +9370,7 @@ class DsxAutocomplete {
9179
9370
  items = itemsValidos;
9180
9371
  }
9181
9372
  }
9373
+ // ✅ Asignar el valor
9182
9374
  if (this.isMultiple()) {
9183
9375
  this.value = items;
9184
9376
  }
@@ -9188,6 +9380,9 @@ class DsxAutocomplete {
9188
9380
  this.initialValueSet = true;
9189
9381
  this.cdr.detectChanges();
9190
9382
  this.logDebug(`✅ Vista actualizada con ${items.length} elementos (${this.isMultiple() ? 'multiple' : 'single'})`);
9383
+ if (this.pendingIdsInternal.length > 0) {
9384
+ this.logDebug(`⏳ IDs pendientes actuales:`, this.pendingIdsInternal);
9385
+ }
9191
9386
  }
9192
9387
  forceReload() {
9193
9388
  this.logDebug('🔄 Forzando recarga');
@@ -9214,7 +9409,7 @@ class DsxAutocomplete {
9214
9409
  useExisting: forwardRef(() => DsxAutocomplete),
9215
9410
  multi: true,
9216
9411
  },
9217
- ], ngImport: i0, template: "<p-floatlabel variant=\"on\">\r\n <p-autoComplete\r\n fluid\r\n appendTo=\"body\"\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<!-- 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 </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" }] });
9412
+ ], 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
9413
  }
9219
9414
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DsxAutocomplete, decorators: [{
9220
9415
  type: Component,
@@ -9230,7 +9425,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
9230
9425
  useExisting: forwardRef(() => DsxAutocomplete),
9231
9426
  multi: true,
9232
9427
  },
9233
- ], template: "<p-floatlabel variant=\"on\">\r\n <p-autoComplete\r\n fluid\r\n appendTo=\"body\"\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<!-- 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 </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" }]
9428
+ ], 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
9429
  }], 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
9430
 
9236
9431
  // dsx-select.component.ts