@jvsoft/utils 0.0.13-alpha.6 → 1.0.0-alpha.11

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.
Files changed (72) hide show
  1. package/README.md +5 -0
  2. package/directives/auto-select-first.directive.d.ts +13 -0
  3. package/directives/autocomplete-match-validator.directive.d.ts +10 -0
  4. package/directives/autocomplete.directive.d.ts +23 -0
  5. package/{src/functions → directives}/index.d.ts +1 -1
  6. package/directives/popover/popover-listener.directive.d.ts +12 -0
  7. package/directives/popover/popover-panel.component.d.ts +13 -0
  8. package/directives/popover/popover.directive.d.ts +23 -0
  9. package/directives/popover/popover.interface.d.ts +8 -0
  10. package/directives/popover/popover.service.d.ts +17 -0
  11. package/directives/public-api.d.ts +8 -0
  12. package/fesm2022/jvsoft-utils-directives.mjs +459 -0
  13. package/fesm2022/jvsoft-utils-directives.mjs.map +1 -0
  14. package/fesm2022/{jvsoft-utils-src-functions.mjs → jvsoft-utils-functions.mjs} +485 -191
  15. package/fesm2022/jvsoft-utils-functions.mjs.map +1 -0
  16. package/fesm2022/{jvsoft-utils-src-interfaces.mjs → jvsoft-utils-interfaces.mjs} +1 -1
  17. package/fesm2022/jvsoft-utils-interfaces.mjs.map +1 -0
  18. package/fesm2022/{jvsoft-utils-src-pipes.mjs → jvsoft-utils-pipes.mjs} +57 -2
  19. package/fesm2022/jvsoft-utils-pipes.mjs.map +1 -0
  20. package/fesm2022/jvsoft-utils-services.mjs +105 -0
  21. package/fesm2022/jvsoft-utils-services.mjs.map +1 -0
  22. package/fesm2022/jvsoft-utils.mjs +1084 -197
  23. package/fesm2022/jvsoft-utils.mjs.map +1 -1
  24. package/functions/base64.d.ts +63 -0
  25. package/functions/dev-log.d.ts +97 -0
  26. package/functions/index.d.ts +4 -0
  27. package/functions/mat-form-controls/autocomplete.d.ts +43 -27
  28. package/functions/objects-arrays.d.ts +13 -97
  29. package/functions/public-api.d.ts +1 -0
  30. package/interfaces/index.d.ts +4 -0
  31. package/package.json +32 -15
  32. package/pipes/display-with.pipe.d.ts +14 -0
  33. package/pipes/index.d.ts +4 -0
  34. package/pipes/public-api.d.ts +1 -0
  35. package/public-api.d.ts +6 -4
  36. package/{src/pipes → services}/index.d.ts +1 -1
  37. package/services/public-api.d.ts +1 -0
  38. package/services/reloj.service.d.ts +31 -0
  39. package/fesm2022/jvsoft-utils-src-functions.mjs.map +0 -1
  40. package/fesm2022/jvsoft-utils-src-interfaces.mjs.map +0 -1
  41. package/fesm2022/jvsoft-utils-src-pipes.mjs.map +0 -1
  42. package/src/functions/base64.d.ts +0 -26
  43. package/src/functions/browser.d.ts +0 -1
  44. package/src/functions/crypto-js.d.ts +0 -2
  45. package/src/functions/date.d.ts +0 -3
  46. package/src/functions/email.d.ts +0 -2
  47. package/src/functions/file.d.ts +0 -10
  48. package/src/functions/forms.d.ts +0 -23
  49. package/src/functions/http-client.d.ts +0 -2
  50. package/src/functions/local-storage.d.ts +0 -29
  51. package/src/functions/mat-form-controls/autocomplete.d.ts +0 -51
  52. package/src/functions/mat-form-controls/index.d.ts +0 -2
  53. package/src/functions/number.d.ts +0 -2
  54. package/src/functions/object-transformation.d.ts +0 -2
  55. package/src/functions/objects-arrays.d.ts +0 -147
  56. package/src/functions/public-api.d.ts +0 -16
  57. package/src/functions/string.d.ts +0 -23
  58. package/src/functions/sweetalert.d.ts +0 -5
  59. package/src/functions/utiles.d.ts +0 -1
  60. package/src/interfaces/datos.d.ts +0 -4
  61. package/src/interfaces/index.d.ts +0 -5
  62. package/src/interfaces/public-api.d.ts +0 -1
  63. package/src/pipes/data-en-lista.pipe.d.ts +0 -8
  64. package/src/pipes/date-diff-string.pipe.d.ts +0 -17
  65. package/src/pipes/filtro.pipe.d.ts +0 -18
  66. package/src/pipes/form-control-is-required.pipe.d.ts +0 -9
  67. package/src/pipes/json-parse.pipe.d.ts +0 -7
  68. package/src/pipes/no-sanitize.pipe.d.ts +0 -10
  69. package/src/pipes/public-api.d.ts +0 -8
  70. package/src/pipes/tipo-valor-funcion.pipe.d.ts +0 -9
  71. package/src/pipes/zero-fill.pipe.d.ts +0 -8
  72. /package/{classes → src/classes}/data-model.d.ts +0 -0
@@ -1,27 +1,24 @@
1
- import { Validators, FormGroup, FormControl, FormArray } from '@angular/forms';
2
- import { untilDestroyed } from '@ngneat/until-destroy';
3
- import { startWith, debounceTime, tap, isObservable, switchMap, of, finalize } from 'rxjs';
1
+ import { Validators, FormGroup, FormControl, FormArray, NG_VALIDATORS, NgControl, FormControlName } from '@angular/forms';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, DestroyRef, Pipe, input, forwardRef, Directive, computed, Component, TemplateRef, Injectable, ElementRef, ViewContainerRef, HostListener, output, effect, untracked, signal } from '@angular/core';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { tap, startWith, debounceTime, isObservable, switchMap, of, finalize, Subject, merge, distinctUntilChanged, map as map$1 } from 'rxjs';
4
6
  import { map } from 'rxjs/operators';
5
7
  import { Buffer } from 'buffer';
6
8
  import CryptoJS from 'crypto-js';
7
- import { formatDate } from '@angular/common';
9
+ import * as i1$1 from '@angular/common';
10
+ import { formatDate, CommonModule } from '@angular/common';
8
11
  import { saveAs } from 'file-saver';
9
12
  import { ReactiveFormConfig } from '@rxweb/reactive-form-validators';
10
13
  import moment from 'moment';
11
14
  import swal from 'sweetalert2';
12
15
  import { jwtDecode } from 'jwt-decode';
13
- import * as i0 from '@angular/core';
14
- import { Pipe } from '@angular/core';
15
16
  import * as i1 from '@angular/platform-browser';
17
+ import { Overlay, OverlayPositionBuilder } from '@angular/cdk/overlay';
18
+ import { ComponentPortal } from '@angular/cdk/portal';
19
+ import { ToastrService } from 'ngx-toastr';
20
+ import { format } from 'date-fns';
16
21
 
17
- /**
18
- * Realiza una fusión profunda entre dos objetos, sin modificar el original.
19
- * Si el valor en target no es un objeto, lo reemplaza directamente.
20
- *
21
- * @param source - Objeto fuente que se clonará y fusionará.
22
- * @param target - Objeto destino cuyos valores se fusionarán.
23
- * @returns Un nuevo objeto con la fusión profunda.
24
- */
25
22
  function deepMerge(source, target) {
26
23
  // Crea un clon profundo sin usar JSON.parse(JSON.stringify)
27
24
  let cloneSource = deepClone(source);
@@ -43,12 +40,7 @@ function deepMerge(source, target) {
43
40
  }
44
41
  return cloneSource; // Retorna el clon y no modifica el original
45
42
  }
46
- /**
47
- * Realiza una clonación profunda de un objeto, manejando funciones, fechas y arrays.
48
- *
49
- * @param obj - Objeto a clonar.
50
- * @returns Una copia profunda del objeto.
51
- */
43
+ // Función de clonación profunda que maneja funciones, fechas y otros tipos especiales
52
44
  function deepClone(obj) {
53
45
  if (obj === null || typeof obj !== 'object') {
54
46
  return obj; // Devuelve el valor si no es un objeto
@@ -74,7 +66,6 @@ function deepClone(obj) {
74
66
  /**
75
67
  * Busca un elemento en un array o jerarquía de objetos según un campo y valor especificado.
76
68
  *
77
- * @param datosFn - Objeto con los parámetros de búsqueda: items, campo, valor y opcionalmente campoHijo.
78
69
  * @returns El elemento encontrado o undefined si no existe.
79
70
  */
80
71
  function buscarPorCampo(datosFn) {
@@ -101,33 +92,13 @@ function buscarPorCampo(datosFn) {
101
92
  // Si no se encuentra nada, retorna undefined
102
93
  return undefined;
103
94
  }
104
- /**
105
- * Suma los valores de las propiedades especificadas de un objeto.
106
- *
107
- * @param item - Objeto con las propiedades a sumar.
108
- * @param campos - Array de nombres de las propiedades a sumar.
109
- * @returns La suma de los valores de las propiedades.
110
- */
111
95
  function sumarPropiedades(item, campos) {
112
96
  const datosSumar = campos.map(campo => (item[campo] * 1));
113
97
  return datosSumar.reduce((a, b) => a + b, 0);
114
98
  }
115
- /**
116
- * Verifica si el valor proporcionado es un número válido.
117
- *
118
- * @param value - Valor a verificar.
119
- * @returns true si es un número, false en caso contrario.
120
- */
121
99
  function esNumero(value) {
122
100
  return !isNaN(Number(value));
123
101
  }
124
- /**
125
- * Suma los valores de las propiedades especificadas en un array de objetos.
126
- *
127
- * @param arrayObjetos - Array de objetos a procesar.
128
- * @param campos - Array de nombres de las propiedades a sumar.
129
- * @returns Objeto con la suma de cada propiedad.
130
- */
131
102
  function sumarObjetos(arrayObjetos, campos) {
132
103
  return arrayObjetos.reduce((accumulator, item) => {
133
104
  campos.forEach(campo => {
@@ -139,22 +110,9 @@ function sumarObjetos(arrayObjetos, campos) {
139
110
  return accumulator;
140
111
  }, {});
141
112
  }
142
- /**
143
- * Obtiene los valores únicos de un array.
144
- *
145
- * @param array - Array de valores.
146
- * @returns Array con los valores únicos.
147
- */
148
113
  function getUniqueValues(array) {
149
114
  return array.filter((currentValue, index, arr) => (arr.indexOf(currentValue) === index));
150
115
  }
151
- /**
152
- * Obtiene los objetos únicos de un array según una propiedad específica.
153
- *
154
- * @param objetos - Array de objetos.
155
- * @param campo - Nombre de la propiedad para determinar unicidad.
156
- * @returns Array de objetos únicos por la propiedad.
157
- */
158
116
  function getUniqueValuesByProperty(objetos, campo) {
159
117
  const objetosUnicos = {};
160
118
  objetos.forEach(objeto => {
@@ -170,14 +128,6 @@ function getUniqueValuesByProperty(objetos, campo) {
170
128
  // Convertir el objeto de claves únicas de vuelta a una lista de objetos
171
129
  return Object.values(objetosUnicos);
172
130
  }
173
- /**
174
- * Ordena un array de valores numéricos o alfabéticos.
175
- *
176
- * @param array - Array a ordenar.
177
- * @param numeros - Si es true, ordena como números.
178
- * @param sentido - 'ASC' para ascendente, 'DESC' para descendente.
179
- * @returns Array ordenado.
180
- */
181
131
  function ordenarArray(array, numeros = false, sentido = 'ASC') {
182
132
  if (numeros) {
183
133
  if (sentido != 'ASC') {
@@ -187,24 +137,9 @@ function ordenarArray(array, numeros = false, sentido = 'ASC') {
187
137
  }
188
138
  return array.sort((a, b) => (a > b) ? 1 : ((b > a) ? -1 : 0));
189
139
  }
190
- /**
191
- * Ordena un array de objetos por una propiedad específica.
192
- *
193
- * @param objData - Array de objetos a ordenar.
194
- * @param propiedad - Propiedad por la que se ordena.
195
- * @param numeros - (Obsoleto) Si es true, ordena como números.
196
- * @returns Array ordenado.
197
- */
198
140
  function ordenarPorPropiedad(objData, propiedad, /**@deprecated*/ numeros = false) {
199
141
  return ordenarPorPropiedades(objData, { propiedades: [propiedad], direcciones: ['asc'] });
200
142
  }
201
- /**
202
- * Ordena un array de objetos por varias propiedades y direcciones.
203
- *
204
- * @param arr - Array de objetos a ordenar.
205
- * @param options - Opciones con propiedades y direcciones de orden.
206
- * @returns Array ordenado.
207
- */
208
143
  function ordenarPorPropiedades(arr, options) {
209
144
  const { propiedades, direcciones = [] } = options;
210
145
  const orden = direcciones.map(d => d === 'desc' ? -1 : 1);
@@ -224,13 +159,6 @@ function ordenarPorPropiedades(arr, options) {
224
159
  }, 0);
225
160
  });
226
161
  }
227
- /**
228
- * Agrupa los elementos de un array según una clave o función de clave.
229
- *
230
- * @param array - Array de objetos a agrupar.
231
- * @param key - Propiedad o función para agrupar.
232
- * @returns Objeto con los grupos por clave.
233
- */
234
162
  function groupBy(array, key) {
235
163
  const keyFn = key instanceof Function ? key : (obj) => obj[key];
236
164
  return array.reduce((objectsByKeyValue, obj) => {
@@ -239,13 +167,6 @@ function groupBy(array, key) {
239
167
  return objectsByKeyValue;
240
168
  }, {});
241
169
  }
242
- /**
243
- * Agrupa y anida los elementos de un array según una lista de propiedades.
244
- *
245
- * @param arr - Array de objetos a agrupar.
246
- * @param properties - Propiedades para agrupar de forma anidada.
247
- * @returns Objeto anidado por los grupos de propiedades.
248
- */
249
170
  function nestGroupsBy(arr, properties) {
250
171
  const fnGroupBy = (conversions, property2) => {
251
172
  return conversions.reduce((acc, obj) => {
@@ -319,13 +240,6 @@ function eliminarColumnaPorIndex(data, columnIndex) {
319
240
  return newRow;
320
241
  });
321
242
  }
322
- /**
323
- * Elimina elementos duplicados de un array de objetos según claves específicas.
324
- *
325
- * @param array - Array de objetos.
326
- * @param claves - Claves para determinar unicidad. Si no se especifica, compara todo el objeto.
327
- * @returns Array sin duplicados.
328
- */
329
243
  function eliminarDuplicados(array, claves) {
330
244
  const unicos = new Map();
331
245
  for (const item of array) {
@@ -338,14 +252,6 @@ function eliminarDuplicados(array, claves) {
338
252
  }
339
253
  return Array.from(unicos.values());
340
254
  }
341
- /**
342
- * Elimina elementos de un array origen que estén presentes en otro array, según claves específicas.
343
- *
344
- * @param origen - Array original.
345
- * @param elementosAEliminar - Elementos a eliminar del array origen.
346
- * @param claves - Claves para comparar los objetos. Si no se especifica, compara todo el objeto.
347
- * @returns Array filtrado sin los elementos eliminados.
348
- */
349
255
  function eliminarElementos(origen, elementosAEliminar, claves) {
350
256
  const clavesSet = new Set();
351
257
  for (const item of elementosAEliminar) {
@@ -361,41 +267,12 @@ function eliminarElementos(origen, elementosAEliminar, claves) {
361
267
  return !clavesSet.has(key);
362
268
  });
363
269
  }
364
-
365
270
  /**
366
- * Devuelve un valor de visualización para un elemento buscado.
367
- * Compatible con listas simples, objetos y campos múltiples.
368
- */
369
- function mostrarValorEnBusqueda(campos, idxSel) {
370
- const buscarEnLista = (lista) => {
371
- if (!lista)
372
- return null;
373
- if (campos.campoId === '*object*' || campos.campoId === '*objeto*') {
374
- return lista.find((x) => JSON.stringify(x).trim() === JSON.stringify(idxSel).trim());
375
- }
376
- return lista.find((x) => x[campos.campoId] === idxSel);
377
- };
378
- const impDataMostrar = () => {
379
- let vD = buscarEnLista(campos.lista) || (campos.opcExtra && buscarEnLista(campos.opcExtra));
380
- if (!vD)
381
- return '';
382
- if (Array.isArray(campos.campoValue)) {
383
- return campos.campoValue.map((vCampo) => vD[vCampo] ?? '').join(' - ').trim();
384
- }
385
- return (vD[campos.campoValue] ?? '').trim();
386
- };
387
- if (esNumero(idxSel)) {
388
- if ((idxSel > 0 && campos.lista?.length) || (idxSel && typeof idxSel === 'object')) {
389
- return impDataMostrar();
390
- }
391
- }
392
- else if (campos.lista?.length) {
393
- return impDataMostrar();
394
- }
395
- return '';
396
- }
397
- /**
398
- * Filtra datos locales de un array basado en un valor de búsqueda.
271
+ * Filtra datos localmente según un valor de búsqueda
272
+ * @param data - Array de datos a filtrar
273
+ * @param value - Valor de búsqueda
274
+ * @param campoBuscar - Campo(s) por el cual buscar
275
+ * @returns Array filtrado
399
276
  */
400
277
  function filtrarDatosLocal(data, value, campoBuscar) {
401
278
  if (!value)
@@ -414,32 +291,286 @@ function filtrarDatosLocal(data, value, campoBuscar) {
414
291
  });
415
292
  });
416
293
  }
294
+
417
295
  /**
418
- * Vincula un FormControl a datos locales para autocompletado.
296
+ * Intenta obtener el DestroyRef desde el llamador (objThis/control)
297
+ * o inyectándolo si estamos en un contexto de inyección.
419
298
  */
420
- function changeSelectData(objThis, { formControl, data, campoBuscar, variableResultado }) {
421
- objThis.filtrados[variableResultado] = formControl.valueChanges.pipe(untilDestroyed(objThis)).pipe(startWith(''), map(value => data ? filtrarDatosLocal(data, value, campoBuscar) : []));
299
+ function obtenerDestroyRef(caller, manualDRef, fnName = 'N/A') {
300
+ let dRef = manualDRef || caller?.['destroyRef'] || caller?.['_destroyRef'];
301
+ if (!dRef) {
302
+ try {
303
+ dRef = inject(DestroyRef, { optional: true });
304
+ }
305
+ catch (e) {
306
+ console.warn(`${fnName}: Falló inject(DestroyRef) (fuera del contexto de inyección). Pasa el destroyRef explícitamente o decláralo en el componente para habilitar la limpieza.`);
307
+ }
308
+ }
309
+ return dRef ?? undefined;
310
+ }
311
+ function mostrarValorEnBusqueda(campos, idxSel) {
312
+ const impDataMostrar = () => {
313
+ let vD;
314
+ if (campos.campoId == '*object*' || campos.campoId == '*objeto*') {
315
+ console.log(campos);
316
+ vD = campos.lista.find((x) => JSON.stringify(x).trim() == JSON.stringify(idxSel).trim());
317
+ console.log(vD);
318
+ }
319
+ else {
320
+ vD = campos.lista.find((x) => x[campos.campoId] == idxSel);
321
+ }
322
+ if (!vD && campos.opcExtra) {
323
+ console.log('eval ', campos.opcExtra);
324
+ if (campos.campoId == '*object*' || campos.campoId == '*objeto*') {
325
+ console.log(campos);
326
+ vD = campos.opcExtra.find((x) => JSON.stringify(x).trim() == JSON.stringify(idxSel).trim());
327
+ console.log(vD);
328
+ }
329
+ else {
330
+ vD = campos.opcExtra.find((x) => x[campos.campoId] == idxSel);
331
+ }
332
+ }
333
+ if (vD) {
334
+ let txtFinal = '';
335
+ if (Array.isArray(campos.campoValue)) {
336
+ campos.campoValue.forEach((vCampo, idx) => {
337
+ txtFinal += (vD[vCampo] ?? '');
338
+ if (idx < campos.campoValue.length - 1) {
339
+ txtFinal += ' - ';
340
+ }
341
+ });
342
+ }
343
+ else {
344
+ txtFinal = vD[campos.campoValue] ?? '';
345
+ }
346
+ return txtFinal.trim();
347
+ }
348
+ else {
349
+ console.log('ASSSSS ----- SSSS ');
350
+ }
351
+ return '';
352
+ };
353
+ if (esNumero(idxSel)) {
354
+ if (idxSel > 0 && campos.lista?.length > 0) {
355
+ return impDataMostrar();
356
+ }
357
+ else if (idxSel && typeof idxSel == 'object') {
358
+ return impDataMostrar();
359
+ }
360
+ }
361
+ else {
362
+ if (campos.lista?.length > 0) {
363
+ return impDataMostrar();
364
+ }
365
+ }
366
+ return '';
367
+ }
368
+ /**
369
+ * @deprecated Use JvsAutocompleteDirective en su lugar para un enfoque más declarativo y robusto.
370
+ */
371
+ function changeSelectData(objThis, dataFiltro) {
372
+ const dRef = obtenerDestroyRef(objThis, dataFiltro.destroyRef, 'changeSelectData');
373
+ objThis['filtrados'][dataFiltro.variableResultado] = dataFiltro.formControl.valueChanges.pipe(dRef ? takeUntilDestroyed(dRef) : tap(() => { })).pipe(startWith(''), map(value => {
374
+ const varN = dataFiltro.data;
375
+ if (varN) {
376
+ if (value) {
377
+ return varN.map(x => x).filter(dat => {
378
+ if (Array.isArray(dataFiltro.campoBuscar)) {
379
+ let encontrado = false;
380
+ for (const vCampo of dataFiltro.campoBuscar) {
381
+ // console.log(vCampo, value, dat[vCampo]);
382
+ if (isNaN(Number(value))) {
383
+ // NO ES NUMERO
384
+ if (value && dat[vCampo] && dat[vCampo].toLowerCase().includes(value?.toString().toLowerCase())) {
385
+ encontrado = true;
386
+ break;
387
+ }
388
+ }
389
+ else {
390
+ if (value && dat[vCampo] && dat[vCampo].toString().includes(value?.toString())) {
391
+ encontrado = true;
392
+ break;
393
+ }
394
+ }
395
+ }
396
+ return encontrado;
397
+ }
398
+ else {
399
+ if (isNaN(Number(value))) {
400
+ return dat[dataFiltro.campoBuscar].toLowerCase().includes(value?.toString().toLowerCase());
401
+ }
402
+ else {
403
+ return dat[dataFiltro.campoBuscar].toString().includes(value?.toString());
404
+ }
405
+ }
406
+ });
407
+ }
408
+ return varN;
409
+ }
410
+ return false;
411
+ }));
422
412
  }
423
413
  /**
424
- * Vincula un FormControl a datos locales obtenidos de dataServidor o dataServidorSuscripcion.
414
+ * @deprecated Use JvsAutocompleteDirective en su lugar para un enfoque más declarativo y robusto.
425
415
  */
426
- function changeSelect(control, formControl, tipo, campoBuscar, campoFiltro = null) {
427
- const filtro = campoFiltro ?? tipo;
428
- control.filtrados[filtro] = formControl.valueChanges.pipe(untilDestroyed(control)).pipe(startWith(''), map(value => {
416
+ function changeSelect(control, formControl, tipo, campoBuscar, campoFiltro = null, destroyRef) {
417
+ // console.log(formControl);
418
+ // const formGroup = formControl.parent.controls;
419
+ // console.warn( Object.keys(formGroup).find(name => formControl === formGroup[name]) || null );
420
+ if (!campoFiltro) {
421
+ campoFiltro = tipo;
422
+ }
423
+ const dRef = obtenerDestroyRef(control, destroyRef, 'changeSelect');
424
+ control['filtrados'][campoFiltro ?? '__'] = formControl.valueChanges.pipe(dRef ? takeUntilDestroyed(dRef) : tap(() => { })).pipe(startWith(''), map(value => {
425
+ // console.warn(value);
429
426
  const partes = tipo.split('.');
430
- const varN = control.dataServidor?.[partes[0]]?.[partes[1]] ??
431
- control.dataServidor?.[tipo] ??
432
- control.dataServidorSuscripcion?.[tipo]?.getValue();
433
- return varN ? filtrarDatosLocal(varN, value, campoBuscar) : [];
427
+ let varN;
428
+ if (control['dataServidor']) {
429
+ varN = (partes.length > 1) ? control['dataServidor'][partes[0]][partes[1]] : control['dataServidor'][tipo];
430
+ }
431
+ else if (control['dataServidorSuscripcion']) {
432
+ varN = control['dataServidorSuscripcion'][tipo].getValue();
433
+ }
434
+ if (varN) {
435
+ if (value) {
436
+ return varN.map((x) => x).filter((dat) => {
437
+ if (Array.isArray(campoBuscar)) {
438
+ let encontrado = false;
439
+ for (const vCampo of campoBuscar) {
440
+ // console.log(vCampo, value, dat[vCampo]);
441
+ if (isNaN(Number(value))) {
442
+ // NO ES NUMERO
443
+ if (value && dat[vCampo] && dat[vCampo].toLowerCase().includes(value?.toString().toLowerCase())) {
444
+ encontrado = true;
445
+ break;
446
+ }
447
+ }
448
+ else {
449
+ if (value && dat[vCampo] && dat[vCampo].toString().includes(value?.toString())) {
450
+ encontrado = true;
451
+ break;
452
+ }
453
+ }
454
+ }
455
+ return encontrado;
456
+ }
457
+ else {
458
+ if (isNaN(Number(value))) {
459
+ return dat[campoBuscar].toLowerCase().includes(value?.toString().toLowerCase());
460
+ }
461
+ else {
462
+ return dat[campoBuscar].toString().includes(value?.toString());
463
+ }
464
+ }
465
+ });
466
+ }
467
+ return varN;
468
+ }
469
+ return false;
434
470
  }));
435
471
  }
472
+ /**
473
+ * @deprecated Use JvsAutocompleteDirective en su lugar para un enfoque más declarativo y robusto.
474
+ */
475
+ function changeSelectDataApi(objThis, dataFiltro) {
476
+ if (!dataFiltro.variableResultado) {
477
+ dataFiltro.variableResultado = dataFiltro.tipoReq;
478
+ }
479
+ const idFiltrado = dataFiltro.variableResultado;
480
+ const dRef = obtenerDestroyRef(objThis, dataFiltro.destroyRef, 'changeSelectDataApi');
481
+ dataFiltro.formControl.valueChanges.pipe(dRef ? takeUntilDestroyed(dRef) : tap(() => { }), debounceTime(500), tap(() => {
482
+ objThis.filtrados[dataFiltro.variableResultado + 'tmp'] = isObservable(objThis.filtrados[idFiltrado]) ? [] : objThis.filtrados[idFiltrado] || [];
483
+ if (objThis.filtrados[idFiltrado] !== objThis.filtrados[idFiltrado + 'tmp']) {
484
+ objThis.filtrados[idFiltrado] = [];
485
+ }
486
+ objThis.isLoading = true;
487
+ }), switchMap(value => {
488
+ if (dataFiltro.campoId) {
489
+ const busquedaActual2 = objThis.filtrados[idFiltrado + 'tmp'].findIndex((item) => item[dataFiltro.campoId ?? '--'] === value);
490
+ if (busquedaActual2 >= 0) {
491
+ return of({ [dataFiltro.tipoReq]: objThis.filtrados[idFiltrado + 'tmp'] });
492
+ }
493
+ }
494
+ return !value || value.length < (dataFiltro.minLength ?? 3) ? [] : (dataFiltro.queryService.getDataMethod('GET', dataFiltro.tipoReq, {
495
+ ...(dataFiltro.dataExtra ?? {}),
496
+ ...(dataFiltro.dataExtraVariable ? Object.fromEntries(dataFiltro.dataExtraVariable.map((objData) => [objData.campo, objData.ctrlValue.value])) : {}),
497
+ txtBuscar: value,
498
+ }, dataFiltro.anonimo).pipe(finalize(() => objThis.isLoading = false)));
499
+ })).subscribe((data) => {
500
+ objThis.filtrados[idFiltrado] = data[dataFiltro.tipoReq] ?? [];
501
+ });
502
+ }
503
+ /**
504
+ * @deprecated Use JvsAutocompleteDirective en su lugar para un enfoque más declarativo y robusto.
505
+ */
506
+ function changeSelectApi(control, queryService, formControl, tipo, dataExtra = {}, dataExtraVariable = null, minLength = 1, anonimo = false, destroyRef) {
507
+ const dRef = obtenerDestroyRef(control, destroyRef, 'changeSelectApi');
508
+ formControl.valueChanges.pipe(dRef ? takeUntilDestroyed(dRef) : tap(() => { }), debounceTime(500), tap((value) => {
509
+ control['filtrados'][tipo + 'tmp'] = isObservable(control['filtrados'][tipo]) ? [] : control['filtrados'][tipo];
510
+ if (control['filtrados'][tipo] != control['filtrados'][tipo + 'tmp']) {
511
+ control['filtrados'][tipo] = [];
512
+ }
513
+ control['isLoading'] = true;
514
+ }), switchMap(value => {
515
+ const formGroup = formControl.parent?.controls;
516
+ const nombreControl = Object.keys(formGroup).find(name => formControl === formGroup[name]) || null;
517
+ if (nombreControl && control['filtrados'][tipo + 'tmp'] && control['filtrados'][tipo + 'tmp'].length > 0) {
518
+ const busquedaActual = control['filtrados'][tipo + 'tmp'].findIndex((item) => item[nombreControl] == value);
519
+ if (busquedaActual >= 0) {
520
+ const vRet = {};
521
+ vRet[tipo] = control['filtrados'][tipo + 'tmp'];
522
+ control['isLoading'] = false;
523
+ return of(vRet);
524
+ }
525
+ }
526
+ if (!value || value.length < minLength) {
527
+ return [];
528
+ }
529
+ const dataExtraVariableData = {};
530
+ if (dataExtraVariable) {
531
+ // @ts-ignore
532
+ for (const objData of dataExtraVariable) {
533
+ dataExtraVariableData[objData.campo] = objData.ctrlValue.value;
534
+ }
535
+ }
536
+ return queryService.getDataMethod('GET', tipo, { ...dataExtra, ...dataExtraVariableData, ...{ txtBuscar: value } }, anonimo).pipe(finalize(() => {
537
+ control['isLoading'] = false;
538
+ }));
539
+ })).subscribe((data) => {
540
+ if (data[tipo] == undefined) {
541
+ control['filtrados'][tipo] = [];
542
+ }
543
+ else {
544
+ control['filtrados'][tipo] = data[tipo];
545
+ }
546
+ });
547
+ }
548
+ /**
549
+ * Comprueba si un valor es una Promesa
550
+ * @param valor - Valor a verificar
551
+ * @returns true si es una Promise
552
+ */
553
+ function esPromise(valor) {
554
+ return !!valor && typeof valor.then === 'function';
555
+ }
556
+ /**
557
+ * Selecciona todo el texto de un input
558
+ * @param event - Evento del input
559
+ */
560
+ function seleccionarTextoInput$1(event) {
561
+ event.target.select();
562
+ }
436
563
  /**
437
564
  * Función genérica para vincular un FormControl con datos desde API o Promise.
438
565
  * Preparada para migrar a signals en el futuro.
439
566
  */
567
+ /**
568
+ * @deprecated Use JvsAutocompleteDirective en su lugar para un enfoque más declarativo y robusto.
569
+ */
440
570
  function changeSelectReformateado(config) {
441
- const { objThis, tipoReq, formControl, queryService, campoId, minLength = 3, dataExtra = {}, dataExtraVariable = [], anonimo = false, variableResultado = tipoReq, } = config;
442
- formControl.valueChanges.pipe(debounceTime(500), tap(() => {
571
+ const { objThis, tipoReq, formControl, queryService, campoId, minLength = 3, dataExtra = {}, dataExtraVariable = [], anonimo = false, variableResultado = tipoReq, destroyRef } = config;
572
+ const dRef = obtenerDestroyRef(objThis, destroyRef, 'changeSelectReformateado');
573
+ formControl.valueChanges.pipe(dRef ? takeUntilDestroyed(dRef) : tap(() => { }), debounceTime(500), tap(() => {
443
574
  objThis.filtrados[variableResultado + 'tmp'] = isObservable(objThis.filtrados[variableResultado])
444
575
  ? []
445
576
  : objThis.filtrados[variableResultado] || [];
@@ -459,10 +590,11 @@ function changeSelectReformateado(config) {
459
590
  objThis.isLoading = false;
460
591
  return of({ [tipoReq]: [] });
461
592
  }
462
- const extraVars = Object.fromEntries(dataExtraVariable.map(v => [v.campo, v.ctrlValue.value]));
593
+ const extraVars = Object.fromEntries(dataExtraVariable.map((v) => [v.campo, v.ctrlValue.value]));
463
594
  const query = queryService.getDataMethod('GET', tipoReq, { ...dataExtra, ...extraVars, txtBuscar: value }, anonimo);
464
595
  if (esPromise(query)) {
465
- return query.then((data) => ({ [tipoReq]: data[tipoReq] ?? [] }))
596
+ return query
597
+ .then((data) => ({ [tipoReq]: data[tipoReq] ?? [] }))
466
598
  .finally(() => { objThis.isLoading = false; });
467
599
  }
468
600
  return query.pipe(finalize(() => { objThis.isLoading = false; }));
@@ -470,57 +602,39 @@ function changeSelectReformateado(config) {
470
602
  objThis.filtrados[variableResultado] = data[tipoReq] ?? [];
471
603
  });
472
604
  }
473
- /**
474
- * Alias para compatibilidad.
475
- */
476
- function changeSelectDataApi(objThis, dataFiltro) {
477
- return changeSelectReformateado({
478
- objThis,
479
- tipoReq: dataFiltro.tipoReq,
480
- formControl: dataFiltro.formControl,
481
- queryService: dataFiltro.queryService,
482
- campoId: dataFiltro.campoId,
483
- minLength: dataFiltro.minLength,
484
- dataExtra: dataFiltro.dataExtra,
485
- dataExtraVariable: dataFiltro.dataExtraVariable,
486
- anonimo: dataFiltro.anonimo,
487
- variableResultado: dataFiltro.variableResultado,
488
- });
489
- }
490
- /**
491
- * Alias para compatibilidad.
492
- */
493
- function changeSelectApi(control, queryService, formControl, tipo, dataExtra = {}, dataExtraVariable = null, minLength = 1, anonimo = false) {
494
- return changeSelectReformateado({
495
- objThis: control,
496
- tipoReq: tipo,
497
- formControl,
498
- queryService,
499
- minLength,
500
- dataExtra,
501
- dataExtraVariable: dataExtraVariable ?? [],
502
- anonimo,
503
- });
504
- }
505
- /**
506
- * Comprueba si un valor es una Promesa.
507
- */
508
- function esPromise(valor) {
509
- return !!valor && typeof valor.then === 'function';
510
- }
511
605
 
512
606
  function seleccionarTextoInput(event) {
513
607
  event.target.select();
514
608
  }
515
609
 
610
+ /**
611
+ * Codifica un string a base64 (método legacy)
612
+ * @param val - String a codificar
613
+ * @returns String codificado en base64
614
+ * @deprecated Use encodeBase64String() en su lugar
615
+ */
516
616
  function b64Encode(val) {
517
617
  return Buffer.from(val, 'binary').toString('base64');
518
618
  }
619
+ /**
620
+ * Decodifica un string desde base64 (método legacy)
621
+ * @param val - String en base64 a decodificar
622
+ * @returns String decodificado
623
+ * @deprecated Use decodeBase64String() en su lugar
624
+ */
519
625
  function b64Decode(val) {
520
626
  return Buffer.from(val, 'base64').toString('binary');
521
627
  }
522
628
  /**
523
629
  * Codificar string a Base64 (UTF-8 seguro)
630
+ * @param str - String a codificar
631
+ * @returns String codificado en base64
632
+ *
633
+ * @example
634
+ * ```typescript
635
+ * const encoded = encodeBase64String('Hola Mundo ñ');
636
+ * // encoded: "SG9sYSBNdW5kbyDDsQ=="
637
+ * ```
524
638
  */
525
639
  function encodeBase64String(str) {
526
640
  const encoder = new TextEncoder();
@@ -531,6 +645,14 @@ function encodeBase64String(str) {
531
645
  }
532
646
  /**
533
647
  * Decodificar Base64 a string (UTF-8 seguro)
648
+ * @param b64 - String en base64 a decodificar
649
+ * @returns String decodificado
650
+ *
651
+ * @example
652
+ * ```typescript
653
+ * const decoded = decodeBase64String('SG9sYSBNdW5kbyDDsQ==');
654
+ * // decoded: "Hola Mundo ñ"
655
+ * ```
534
656
  */
535
657
  function decodeBase64String(b64) {
536
658
  const binary = atob(b64);
@@ -543,18 +665,43 @@ function decodeBase64String(b64) {
543
665
  }
544
666
  /**
545
667
  * Codificar un objeto a base64 (UTF-8 seguro)
668
+ * @param obj - Objeto a codificar
669
+ * @returns String codificado en base64
670
+ *
671
+ * @example
672
+ * ```typescript
673
+ * const obj = { nombre: 'Juan', edad: 25 };
674
+ * const encoded = encodeBase64Object(obj);
675
+ * ```
546
676
  */
547
677
  function encodeBase64Object(obj) {
548
678
  return encodeBase64String(JSON.stringify(obj));
549
679
  }
550
680
  /**
551
681
  * Decodificar un base64 y obtener el objeto original
682
+ * @param b64 - String en base64 a decodificar
683
+ * @returns Objeto decodificado
684
+ *
685
+ * @example
686
+ * ```typescript
687
+ * const obj = decodeBase64Object<{ nombre: string, edad: number }>(encoded);
688
+ * // obj: { nombre: 'Juan', edad: 25 }
689
+ * ```
552
690
  */
553
691
  function decodeBase64Object(b64) {
554
692
  return JSON.parse(decodeBase64String(b64));
555
693
  }
556
694
  /**
557
695
  * Codificar archivo a Base64 (retorna solo el contenido, sin "data:...")
696
+ * @param file - Archivo o Blob a codificar
697
+ * @returns Promise con el string en base64
698
+ *
699
+ * @example
700
+ * ```typescript
701
+ * const file = event.target.files[0];
702
+ * const base64 = await encodeBase64File(file);
703
+ * // base64: "iVBORw0KGgoAAAANSUhEUgAA..."
704
+ * ```
558
705
  */
559
706
  function encodeBase64File(file) {
560
707
  return new Promise((resolve, reject) => {
@@ -569,6 +716,16 @@ function encodeBase64File(file) {
569
716
  }
570
717
  /**
571
718
  * Decodificar Base64 a Blob (para reconstruir archivos en Angular)
719
+ * @param b64 - String en base64
720
+ * @param mimeType - Tipo MIME del archivo (opcional)
721
+ * @returns Blob con el contenido decodificado
722
+ *
723
+ * @example
724
+ * ```typescript
725
+ * const blob = decodeBase64ToBlob(base64String, 'image/png');
726
+ * const url = URL.createObjectURL(blob);
727
+ * // Usar url para mostrar imagen o descargar
728
+ * ```
572
729
  */
573
730
  function decodeBase64ToBlob(b64, mimeType = 'application/octet-stream') {
574
731
  const byteChars = atob(b64);
@@ -642,6 +799,147 @@ function formatearFechaCadena(fecha) {
642
799
  return new Date(year, month, day);
643
800
  }
644
801
 
802
+ /**
803
+ * Configuración global para las utilidades de logging
804
+ */
805
+ let isProductionMode = false;
806
+ /**
807
+ * Configura el modo de producción para las utilidades de logging
808
+ * @param production - true si está en modo producción, false para desarrollo
809
+ *
810
+ * @example
811
+ * ```typescript
812
+ * import { setProductionMode } from '@jvsoft/utils';
813
+ * import { environment } from './environments/environment';
814
+ *
815
+ * setProductionMode(environment.production);
816
+ * ```
817
+ */
818
+ function setProductionMode(production) {
819
+ isProductionMode = production;
820
+ }
821
+ /**
822
+ * Obtiene el estado actual del modo de producción
823
+ * @returns true si está en modo producción, false en desarrollo
824
+ */
825
+ function isProduction() {
826
+ return isProductionMode;
827
+ }
828
+ /**
829
+ * Muestra mensajes de log solo en modo desarrollo
830
+ * @param args - Argumentos a mostrar en consola
831
+ *
832
+ * @example
833
+ * ```typescript
834
+ * devLog('Usuario cargado:', usuario);
835
+ * devLog('Estado:', { activo: true, rol: 'admin' });
836
+ * ```
837
+ */
838
+ function devLog(...args) {
839
+ if (!isProductionMode) {
840
+ console.log(...args);
841
+ }
842
+ }
843
+ /**
844
+ * Muestra advertencias solo en modo desarrollo
845
+ * @param args - Argumentos a mostrar como advertencia
846
+ *
847
+ * @example
848
+ * ```typescript
849
+ * devWarn('Función deprecada, usar nuevaFuncion() en su lugar');
850
+ * ```
851
+ */
852
+ function devWarn(...args) {
853
+ if (!isProductionMode) {
854
+ console.warn(...args);
855
+ }
856
+ }
857
+ /**
858
+ * Muestra errores en consola (siempre, incluso en producción)
859
+ * @param args - Argumentos a mostrar como error
860
+ *
861
+ * @example
862
+ * ```typescript
863
+ * devError('Error al cargar datos:', error);
864
+ * ```
865
+ */
866
+ function devError(...args) {
867
+ console.error(...args);
868
+ }
869
+ /**
870
+ * Crea un grupo de logs solo en modo desarrollo
871
+ * @param label - Etiqueta del grupo
872
+ * @param collapsed - Si el grupo debe estar colapsado por defecto
873
+ *
874
+ * @example
875
+ * ```typescript
876
+ * devGroup('Datos del usuario');
877
+ * devLog('Nombre:', usuario.nombre);
878
+ * devLog('Email:', usuario.email);
879
+ * devGroupEnd();
880
+ * ```
881
+ */
882
+ function devGroup(label, collapsed = false) {
883
+ if (!isProductionMode) {
884
+ if (collapsed) {
885
+ console.groupCollapsed(label);
886
+ }
887
+ else {
888
+ console.group(label);
889
+ }
890
+ }
891
+ }
892
+ /**
893
+ * Cierra el grupo de logs actual
894
+ */
895
+ function devGroupEnd() {
896
+ if (!isProductionMode) {
897
+ console.groupEnd();
898
+ }
899
+ }
900
+ /**
901
+ * Muestra una tabla en consola solo en modo desarrollo
902
+ * @param data - Datos a mostrar en formato tabla
903
+ *
904
+ * @example
905
+ * ```typescript
906
+ * devTable([
907
+ * { nombre: 'Juan', edad: 25 },
908
+ * { nombre: 'María', edad: 30 }
909
+ * ]);
910
+ * ```
911
+ */
912
+ function devTable(data) {
913
+ if (!isProductionMode) {
914
+ console.table(data);
915
+ }
916
+ }
917
+ /**
918
+ * Inicia un temporizador solo en modo desarrollo
919
+ * @param label - Etiqueta del temporizador
920
+ *
921
+ * @example
922
+ * ```typescript
923
+ * devTime('carga-datos');
924
+ * // ... código a medir
925
+ * devTimeEnd('carga-datos'); // Muestra: carga-datos: 123.45ms
926
+ * ```
927
+ */
928
+ function devTime(label) {
929
+ if (!isProductionMode) {
930
+ console.time(label);
931
+ }
932
+ }
933
+ /**
934
+ * Finaliza un temporizador y muestra el tiempo transcurrido
935
+ * @param label - Etiqueta del temporizador
936
+ */
937
+ function devTimeEnd(label) {
938
+ if (!isProductionMode) {
939
+ console.timeEnd(label);
940
+ }
941
+ }
942
+
645
943
  function maskEmail(email) {
646
944
  const [user, domain] = email.split("@");
647
945
  if (user.length <= 2) {
@@ -1850,18 +2148,607 @@ function zeroFill(value, digitos, ...args) {
1850
2148
  return new ZeroFillPipe().transform(value, digitos, args);
1851
2149
  }
1852
2150
 
2151
+ class JvsDisplayWithPipe {
2152
+ /**
2153
+ * Retorna una función compatible con [displayWith] de MatAutocomplete.
2154
+ * @param lista La lista de objetos donde buscar.
2155
+ * @param campoId El nombre del campo que coincide con el valor del control (ej: 'iCarreraId').
2156
+ * @param campoValue El nombre del campo (o campos) a mostrar (ej: 'cCarreraNombre' o ['cCodigo', 'cNombre']).
2157
+ * @param opcExtra Lista opcional de objetos extra donde buscar si no se encuentra en la lista principal.
2158
+ */
2159
+ transform(lista, campoId, campoValue, opcExtra) {
2160
+ return (idxSel) => {
2161
+ if (idxSel === null || idxSel === undefined || !lista) {
2162
+ return '';
2163
+ }
2164
+ const impDataMostrar = (vD) => {
2165
+ if (!vD)
2166
+ return '';
2167
+ if (Array.isArray(campoValue)) {
2168
+ return campoValue
2169
+ .map(field => vD[field] ?? '')
2170
+ .filter(val => !!val)
2171
+ .join(' - ');
2172
+ }
2173
+ return vD[campoValue]?.toString() || '';
2174
+ };
2175
+ // Buscar en la lista principal
2176
+ let item;
2177
+ if (campoId === '*object*' || campoId === '*objeto*') {
2178
+ item = lista.find(x => JSON.stringify(x).trim() === JSON.stringify(idxSel).trim());
2179
+ }
2180
+ else {
2181
+ item = lista.find(x => x[campoId] == idxSel);
2182
+ }
2183
+ // Si no se encuentra, buscar en opcExtra
2184
+ if (!item && opcExtra) {
2185
+ if (campoId === '*object*' || campoId === '*objeto*') {
2186
+ item = opcExtra.find(x => JSON.stringify(x).trim() === JSON.stringify(idxSel).trim());
2187
+ }
2188
+ else {
2189
+ item = opcExtra.find(x => x[campoId] == idxSel);
2190
+ }
2191
+ }
2192
+ return item ? impDataMostrar(item).trim() : '';
2193
+ };
2194
+ }
2195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsDisplayWithPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
2196
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: JvsDisplayWithPipe, isStandalone: true, name: "jvsDisplayWith" });
2197
+ }
2198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsDisplayWithPipe, decorators: [{
2199
+ type: Pipe,
2200
+ args: [{
2201
+ name: 'jvsDisplayWith',
2202
+ standalone: true
2203
+ }]
2204
+ }] });
2205
+
1853
2206
  // export * from './otros';
1854
2207
 
2208
+ class AutocompleteMatchValidatorDirective {
2209
+ opciones = input([], { alias: 'jvsAutocompleteMatch' });
2210
+ formControlName = input();
2211
+ idLista = input();
2212
+ validate(control) {
2213
+ const idLista = this.idLista() ?? this.formControlName();
2214
+ const value = control.value;
2215
+ const opciones = this.opciones();
2216
+ if (!value)
2217
+ return null;
2218
+ if (!opciones) {
2219
+ console.error('Debe definir opciones para el validador jvsAutocompleteMatch');
2220
+ return { itemSelected: true };
2221
+ }
2222
+ const encontrado = opciones.some(item => typeof value === 'object'
2223
+ ? item[idLista] === value[idLista]
2224
+ : item[idLista] === value);
2225
+ return encontrado ? null : { itemSelected: true };
2226
+ }
2227
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AutocompleteMatchValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2228
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.14", type: AutocompleteMatchValidatorDirective, isStandalone: true, selector: "[jvsAutocompleteMatch]", inputs: { opciones: { classPropertyName: "opciones", publicName: "jvsAutocompleteMatch", isSignal: true, isRequired: false, transformFunction: null }, formControlName: { classPropertyName: "formControlName", publicName: "formControlName", isSignal: true, isRequired: false, transformFunction: null }, idLista: { classPropertyName: "idLista", publicName: "idLista", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
2229
+ {
2230
+ provide: NG_VALIDATORS,
2231
+ useExisting: forwardRef(() => AutocompleteMatchValidatorDirective),
2232
+ multi: true,
2233
+ },
2234
+ ], ngImport: i0 });
2235
+ }
2236
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AutocompleteMatchValidatorDirective, decorators: [{
2237
+ type: Directive,
2238
+ args: [{
2239
+ selector: '[jvsAutocompleteMatch]',
2240
+ providers: [
2241
+ {
2242
+ provide: NG_VALIDATORS,
2243
+ useExisting: forwardRef(() => AutocompleteMatchValidatorDirective),
2244
+ multi: true,
2245
+ },
2246
+ ],
2247
+ standalone: true
2248
+ }]
2249
+ }] });
2250
+
2251
+ class JvsPopoverPanelComponent {
2252
+ content = input(null);
2253
+ templateContent = input(null);
2254
+ context = input(null);
2255
+ customClass = input('');
2256
+ baseClasses = 'bg-white shadow-md rounded border-sm border-gray-700 p-1 mat-elevation-z4';
2257
+ combinedClasses = computed(() => {
2258
+ return `${this.baseClasses} ${this.customClass()}`.trim();
2259
+ });
2260
+ isTemplate() {
2261
+ return !!this.templateContent();
2262
+ }
2263
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsPopoverPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2264
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.14", type: JvsPopoverPanelComponent, isStandalone: true, selector: "jvs-popover-panel", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, templateContent: { classPropertyName: "templateContent", publicName: "templateContent", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, customClass: { classPropertyName: "customClass", publicName: "customClass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2265
+ <div [class]="combinedClasses()">
2266
+ <ng-container *ngIf="isTemplate(); else htmlContent">
2267
+ <ng-container *ngTemplateOutlet="templateContent(); context: context()"></ng-container>
2268
+ </ng-container>
2269
+ <ng-template #htmlContent>
2270
+ <div [innerHTML]="content()"></div>
2271
+ </ng-template>
2272
+ </div>
2273
+ `, isInline: true, styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
2274
+ }
2275
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsPopoverPanelComponent, decorators: [{
2276
+ type: Component,
2277
+ args: [{ selector: 'jvs-popover-panel', standalone: true, imports: [CommonModule], template: `
2278
+ <div [class]="combinedClasses()">
2279
+ <ng-container *ngIf="isTemplate(); else htmlContent">
2280
+ <ng-container *ngTemplateOutlet="templateContent(); context: context()"></ng-container>
2281
+ </ng-container>
2282
+ <ng-template #htmlContent>
2283
+ <div [innerHTML]="content()"></div>
2284
+ </ng-template>
2285
+ </div>
2286
+ `, styles: [":host{display:block}\n"] }]
2287
+ }] });
2288
+
2289
+ class JvsPopoverService {
2290
+ overlay = inject(Overlay);
2291
+ overlayPositionBuilder = inject(OverlayPositionBuilder);
2292
+ overlayRef = null;
2293
+ open(element, content, options = {}) {
2294
+ this.close();
2295
+ const positionStrategy = this.overlayPositionBuilder
2296
+ .flexibleConnectedTo(element)
2297
+ .withPositions([
2298
+ {
2299
+ originX: 'center',
2300
+ originY: options.position === 'top' ? 'top' : 'bottom',
2301
+ overlayX: 'center',
2302
+ overlayY: options.position === 'top' ? 'bottom' : 'top',
2303
+ offsetY: options.position === 'top' ? -8 : 8
2304
+ }
2305
+ ]);
2306
+ this.overlayRef = this.overlay.create({
2307
+ positionStrategy,
2308
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
2309
+ hasBackdrop: false
2310
+ });
2311
+ const portal = new ComponentPortal(JvsPopoverPanelComponent, options.viewContainerRef);
2312
+ const containerRef = this.overlayRef.attach(portal);
2313
+ if (content instanceof TemplateRef) {
2314
+ containerRef.setInput('templateContent', content);
2315
+ }
2316
+ else {
2317
+ containerRef.setInput('content', content);
2318
+ }
2319
+ if (options.customClass) {
2320
+ containerRef.setInput('customClass', options.customClass);
2321
+ }
2322
+ if (options.context) {
2323
+ containerRef.setInput('context', options.context);
2324
+ }
2325
+ return this.overlayRef;
2326
+ }
2327
+ close() {
2328
+ if (this.overlayRef) {
2329
+ this.overlayRef.detach();
2330
+ this.overlayRef.dispose();
2331
+ this.overlayRef = null;
2332
+ }
2333
+ }
2334
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsPopoverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2335
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsPopoverService, providedIn: 'root' });
2336
+ }
2337
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsPopoverService, decorators: [{
2338
+ type: Injectable,
2339
+ args: [{
2340
+ providedIn: 'root'
2341
+ }]
2342
+ }] });
2343
+
2344
+ class JvsPopoverDirective {
2345
+ popoverService = inject(JvsPopoverService);
2346
+ elementRef = inject(ElementRef);
2347
+ viewContainerRef = inject(ViewContainerRef);
2348
+ content = input(null, { alias: 'jvsPopover' });
2349
+ context = input(null);
2350
+ trigger = input('hover');
2351
+ position = input('bottom');
2352
+ customClass = input('');
2353
+ onMouseEnter() {
2354
+ if (this.trigger() === 'hover') {
2355
+ this.show();
2356
+ }
2357
+ }
2358
+ onMouseLeave() {
2359
+ if (this.trigger() === 'hover') {
2360
+ this.hide();
2361
+ }
2362
+ }
2363
+ onClick() {
2364
+ if (this.trigger() === 'click') {
2365
+ this.show();
2366
+ }
2367
+ }
2368
+ onFocus() {
2369
+ if (this.trigger() === 'focus') {
2370
+ this.show();
2371
+ }
2372
+ }
2373
+ onBlur() {
2374
+ if (this.trigger() === 'focus') {
2375
+ this.hide();
2376
+ }
2377
+ }
2378
+ ngOnDestroy() {
2379
+ this.hide();
2380
+ }
2381
+ show() {
2382
+ const content = this.content();
2383
+ if (!content)
2384
+ return;
2385
+ this.popoverService.open(this.elementRef.nativeElement, content, {
2386
+ position: this.position(),
2387
+ customClass: this.customClass(),
2388
+ viewContainerRef: this.viewContainerRef,
2389
+ context: this.context()
2390
+ });
2391
+ }
2392
+ hide() {
2393
+ this.popoverService.close();
2394
+ }
2395
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsPopoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2396
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.14", type: JvsPopoverDirective, isStandalone: true, selector: "[jvsPopover]", inputs: { content: { classPropertyName: "content", publicName: "jvsPopover", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, customClass: { classPropertyName: "customClass", publicName: "customClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()", "click": "onClick()", "focusin": "onFocus()", "focusout": "onBlur()" } }, ngImport: i0 });
2397
+ }
2398
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsPopoverDirective, decorators: [{
2399
+ type: Directive,
2400
+ args: [{
2401
+ selector: '[jvsPopover]',
2402
+ standalone: true
2403
+ }]
2404
+ }], propDecorators: { onMouseEnter: [{
2405
+ type: HostListener,
2406
+ args: ['mouseenter']
2407
+ }], onMouseLeave: [{
2408
+ type: HostListener,
2409
+ args: ['mouseleave']
2410
+ }], onClick: [{
2411
+ type: HostListener,
2412
+ args: ['click']
2413
+ }], onFocus: [{
2414
+ type: HostListener,
2415
+ args: ['focusin']
2416
+ }], onBlur: [{
2417
+ type: HostListener,
2418
+ args: ['focusout']
2419
+ }] } });
2420
+
2421
+ class JvsPopoverListenerDirective {
2422
+ popoverService = inject(JvsPopoverService);
2423
+ elementRef = inject(ElementRef);
2424
+ trigger = input('hover');
2425
+ onMouseOver(event) {
2426
+ if (this.trigger() !== 'hover')
2427
+ return;
2428
+ const target = this.findPopoverTarget(event.target);
2429
+ if (target) {
2430
+ const content = target.getAttribute('data-jvs-popover');
2431
+ if (content) {
2432
+ this.popoverService.open(target, content, {
2433
+ position: target.getAttribute('data-jvs-position') || 'bottom',
2434
+ customClass: target.getAttribute('data-jvs-class') || ''
2435
+ });
2436
+ }
2437
+ }
2438
+ }
2439
+ onMouseOut(event) {
2440
+ if (this.trigger() !== 'hover')
2441
+ return;
2442
+ this.popoverService.close();
2443
+ }
2444
+ onClick(event) {
2445
+ if (this.trigger() !== 'click')
2446
+ return;
2447
+ const target = this.findPopoverTarget(event.target);
2448
+ if (target) {
2449
+ const content = target.getAttribute('data-jvs-popover');
2450
+ if (content) {
2451
+ this.popoverService.open(target, content, {
2452
+ position: target.getAttribute('data-jvs-position') || 'bottom',
2453
+ customClass: target.getAttribute('data-jvs-class') || ''
2454
+ });
2455
+ }
2456
+ }
2457
+ }
2458
+ findPopoverTarget(el) {
2459
+ while (el && el !== this.elementRef.nativeElement) {
2460
+ if (el.hasAttribute('data-jvs-popover')) {
2461
+ return el;
2462
+ }
2463
+ el = el.parentElement;
2464
+ }
2465
+ return null;
2466
+ }
2467
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsPopoverListenerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2468
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.14", type: JvsPopoverListenerDirective, isStandalone: true, selector: "[jvsPopoverListener]", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseover": "onMouseOver($event)", "mouseout": "onMouseOut($event)", "click": "onClick($event)" } }, ngImport: i0 });
2469
+ }
2470
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsPopoverListenerDirective, decorators: [{
2471
+ type: Directive,
2472
+ args: [{
2473
+ selector: '[jvsPopoverListener]',
2474
+ standalone: true
2475
+ }]
2476
+ }], propDecorators: { onMouseOver: [{
2477
+ type: HostListener,
2478
+ args: ['mouseover', ['$event']]
2479
+ }], onMouseOut: [{
2480
+ type: HostListener,
2481
+ args: ['mouseout', ['$event']]
2482
+ }], onClick: [{
2483
+ type: HostListener,
2484
+ args: ['click', ['$event']]
2485
+ }] } });
2486
+
2487
+ class JvsAutocompleteDirective {
2488
+ destroyRef = inject(DestroyRef);
2489
+ ngControl = inject(NgControl, { optional: true });
2490
+ focus$ = new Subject();
2491
+ // Signal-based Inputs
2492
+ data = input(null);
2493
+ fields = input('');
2494
+ typeReq = input('');
2495
+ queryService = input(null);
2496
+ minLength = input(3);
2497
+ dataExtra = input({});
2498
+ anonimo = input(false);
2499
+ debounce = input(300);
2500
+ // Modern Outputs
2501
+ filtered = output();
2502
+ loading = output();
2503
+ onFocus() {
2504
+ this.focus$.next();
2505
+ }
2506
+ ngOnInit() {
2507
+ const control = this.ngControl;
2508
+ if (!control || !control.valueChanges) {
2509
+ console.warn('JvsAutocompleteDirective: No se encontró ngControl o valueChanges. Asegúrate de que el input tenga un formControl o formControlName.');
2510
+ return;
2511
+ }
2512
+ // Si es data local, bajamos el debounce por defecto a 0 si no se ha especificado
2513
+ const isLocal = this.data() && !this.typeReq();
2514
+ const actualDebounce = (isLocal && this.debounce() === 300) ? 0 : this.debounce();
2515
+ merge(control.valueChanges.pipe(debounceTime(actualDebounce)), this.focus$).pipe(takeUntilDestroyed(this.destroyRef), startWith(control.value), distinctUntilChanged(), tap(() => {
2516
+ if (this.typeReq())
2517
+ this.loading.emit(true);
2518
+ }), switchMap(() => {
2519
+ const value = control.value;
2520
+ // Si el valor es un objeto (proviene de seleccionar una opción), no volvemos a filtrar
2521
+ if (typeof value === 'object' && value !== null) {
2522
+ if (this.typeReq())
2523
+ this.loading.emit(false);
2524
+ return of(null);
2525
+ }
2526
+ if (this.typeReq() && this.queryService()) {
2527
+ return this.fetchApi(value?.toString() || '');
2528
+ }
2529
+ else if (this.data()) {
2530
+ const res = this.filterLocal(value?.toString() || '');
2531
+ return of(res);
2532
+ }
2533
+ if (this.typeReq())
2534
+ this.loading.emit(false);
2535
+ return of([]);
2536
+ })).subscribe((result) => {
2537
+ if (result !== null) {
2538
+ this.filtered.emit(result);
2539
+ }
2540
+ });
2541
+ }
2542
+ filterLocal(value) {
2543
+ const data = this.data();
2544
+ if (!value)
2545
+ return data || [];
2546
+ const search = value.toLowerCase();
2547
+ const rawFields = this.fields();
2548
+ const fields = Array.isArray(rawFields) ? rawFields : [rawFields];
2549
+ return (data || []).filter(item => {
2550
+ return fields.some(field => {
2551
+ const val = item[field];
2552
+ return val && val.toString().toLowerCase().includes(search);
2553
+ });
2554
+ });
2555
+ }
2556
+ fetchApi(value) {
2557
+ const typeReq = this.typeReq();
2558
+ const queryService = this.queryService();
2559
+ if (!value || value.length < this.minLength()) {
2560
+ this.loading.emit(false);
2561
+ return of([]);
2562
+ }
2563
+ return queryService.getDataMethod('GET', typeReq, {
2564
+ ...this.dataExtra(),
2565
+ txtBuscar: value
2566
+ }, this.anonimo()).pipe(map$1((res) => res[typeReq] ?? []), finalize(() => this.loading.emit(false)));
2567
+ }
2568
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsAutocompleteDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2569
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.14", type: JvsAutocompleteDirective, isStandalone: true, selector: "[jvsAutocomplete]", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null }, typeReq: { classPropertyName: "typeReq", publicName: "typeReq", isSignal: true, isRequired: false, transformFunction: null }, queryService: { classPropertyName: "queryService", publicName: "queryService", isSignal: true, isRequired: false, transformFunction: null }, minLength: { classPropertyName: "minLength", publicName: "minLength", isSignal: true, isRequired: false, transformFunction: null }, dataExtra: { classPropertyName: "dataExtra", publicName: "dataExtra", isSignal: true, isRequired: false, transformFunction: null }, anonimo: { classPropertyName: "anonimo", publicName: "anonimo", isSignal: true, isRequired: false, transformFunction: null }, debounce: { classPropertyName: "debounce", publicName: "debounce", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filtered: "filtered", loading: "loading" }, host: { listeners: { "focus": "onFocus()" } }, ngImport: i0 });
2570
+ }
2571
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JvsAutocompleteDirective, decorators: [{
2572
+ type: Directive,
2573
+ args: [{
2574
+ selector: '[jvsAutocomplete]',
2575
+ standalone: true
2576
+ }]
2577
+ }], propDecorators: { onFocus: [{
2578
+ type: HostListener,
2579
+ args: ['focus']
2580
+ }] } });
2581
+
2582
+ class AutoSelectFirstDirective {
2583
+ ngControl = inject(NgControl, { optional: true });
2584
+ fcName = inject(FormControlName, { optional: true });
2585
+ toastr = inject(ToastrService, { optional: true });
2586
+ // Inputs using Signals
2587
+ options = input(null, { alias: 'jvsAutoSelectFirst' });
2588
+ jvsIdKey = input(null);
2589
+ jvsEmptyMsg = input('Lista sin elementos');
2590
+ onAutoSelect = output();
2591
+ constructor() {
2592
+ effect(() => {
2593
+ const currentOptions = this.options();
2594
+ const idKey = this.jvsIdKey();
2595
+ const emptyMsg = this.jvsEmptyMsg();
2596
+ if (currentOptions === null)
2597
+ return;
2598
+ const optionsArray = Array.isArray(currentOptions) ? currentOptions : [];
2599
+ if (optionsArray.length > 0) {
2600
+ untracked(() => {
2601
+ const control = this.ngControl?.control;
2602
+ const currentValue = control?.value;
2603
+ // Only autoselect if empty
2604
+ if (control && (currentValue === null || currentValue === undefined || currentValue === '')) {
2605
+ const firstItem = optionsArray[0];
2606
+ let valueToSet;
2607
+ // Resolve key
2608
+ let effectiveKey = idKey;
2609
+ // Default to formControlName if not provided
2610
+ if (!effectiveKey && this.fcName) {
2611
+ effectiveKey = this.fcName.name;
2612
+ }
2613
+ if (effectiveKey) {
2614
+ if (Array.isArray(effectiveKey)) {
2615
+ // Composite key: build an object with the specified keys
2616
+ valueToSet = {};
2617
+ effectiveKey.forEach(key => {
2618
+ valueToSet[key] = firstItem[key];
2619
+ });
2620
+ }
2621
+ else {
2622
+ valueToSet = firstItem[effectiveKey];
2623
+ }
2624
+ }
2625
+ else {
2626
+ // Use whole item if no key resolution possible
2627
+ valueToSet = firstItem;
2628
+ }
2629
+ control.setValue(valueToSet);
2630
+ this.onAutoSelect.emit(firstItem);
2631
+ }
2632
+ });
2633
+ }
2634
+ else if (emptyMsg && this.toastr) {
2635
+ this.toastr.warning(emptyMsg, 'Filtrado');
2636
+ }
2637
+ });
2638
+ }
2639
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AutoSelectFirstDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2640
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.14", type: AutoSelectFirstDirective, isStandalone: true, selector: "[jvsAutoSelectFirst]", inputs: { options: { classPropertyName: "options", publicName: "jvsAutoSelectFirst", isSignal: true, isRequired: false, transformFunction: null }, jvsIdKey: { classPropertyName: "jvsIdKey", publicName: "jvsIdKey", isSignal: true, isRequired: false, transformFunction: null }, jvsEmptyMsg: { classPropertyName: "jvsEmptyMsg", publicName: "jvsEmptyMsg", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onAutoSelect: "onAutoSelect" }, ngImport: i0 });
2641
+ }
2642
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AutoSelectFirstDirective, decorators: [{
2643
+ type: Directive,
2644
+ args: [{
2645
+ selector: '[jvsAutoSelectFirst]',
2646
+ standalone: true
2647
+ }]
2648
+ }], ctorParameters: () => [] });
2649
+
2650
+ class RelojService {
2651
+ // El offset es la diferencia: ServerTime - LocalTime (performance.now())
2652
+ offset = signal(0);
2653
+ STORAGE_KEY = 'srv_clock_offset';
2654
+ // Signal que cambia cada segundo para forzar la reactividad del reloj
2655
+ ticker = signal(0);
2656
+ // Hora actual calculada basándose en performance.now() para evitar drift y suspensión
2657
+ timestamp = computed(() => {
2658
+ this.ticker(); // Dependencia reactiva para que el computed se ejecute cada segundo
2659
+ // performance.now() es monotónico, Date.now() es manipulable por el usuario
2660
+ // Usamos Date.now() inicialmente pero ajustado por el offset calculado
2661
+ return Date.now() + this.offset();
2662
+ });
2663
+ date = computed(() => new Date(this.timestamp()));
2664
+ dateTime = computed(() => {
2665
+ return format(this.date(), "yyyy-MM-dd'T'HH:mm");
2666
+ });
2667
+ constructor() {
2668
+ this.cargarOffsetInicial();
2669
+ this.escucharCambiosOtrasPestanas();
2670
+ this.iniciarVigilanteWarp();
2671
+ // Iniciar el ticker que mantiene el reloj vivo
2672
+ setInterval(() => {
2673
+ this.ticker.update(n => n + 1);
2674
+ }, 1000);
2675
+ }
2676
+ /**
2677
+ * Sincroniza el reloj interno con una hora oficial del servidor.
2678
+ * Calculamos el offset respecto al tiempo local actual.
2679
+ */
2680
+ sincronizar(serverTime) {
2681
+ const srvTime = new Date(serverTime).getTime();
2682
+ const lclTime = Date.now();
2683
+ const nuevoOffset = srvTime - lclTime;
2684
+ this.offset.set(nuevoOffset);
2685
+ localStorage.setItem(this.STORAGE_KEY, nuevoOffset.toString());
2686
+ this.estaSincronizado.set(true);
2687
+ }
2688
+ /**
2689
+ * Fuerza que la próxima petición HTTP pida la hora al servidor.
2690
+ * Útil tras reconexiones de socket o detecciones de warp.
2691
+ */
2692
+ forzarSincronizacion() {
2693
+ this.estaSincronizado.set(false);
2694
+ }
2695
+ estaSincronizado = signal(false);
2696
+ get sincronizado() { return this.estaSincronizado(); }
2697
+ cargarOffsetInicial() {
2698
+ const guardado = localStorage.getItem(this.STORAGE_KEY);
2699
+ if (guardado) {
2700
+ this.offset.set(parseInt(guardado, 10));
2701
+ }
2702
+ }
2703
+ escucharCambiosOtrasPestanas() {
2704
+ window.addEventListener('storage', (event) => {
2705
+ if (event.key === this.STORAGE_KEY && event.newValue) {
2706
+ this.offset.set(parseInt(event.newValue, 10));
2707
+ }
2708
+ });
2709
+ }
2710
+ /**
2711
+ * Monitoriza saltos bruscos en el tiempo (suspensión o cambio manual de hora).
2712
+ * Si detectamos un "warp", podríamos pedir una sincronización forzada.
2713
+ */
2714
+ iniciarVigilanteWarp() {
2715
+ let lastPerformance = performance.now();
2716
+ let lastDate = Date.now();
2717
+ setInterval(() => {
2718
+ const currentPerformance = performance.now();
2719
+ const currentDate = Date.now();
2720
+ const perfDelta = currentPerformance - lastPerformance;
2721
+ const dateDelta = currentDate - lastDate;
2722
+ // Si la diferencia entre deltas es mayor a 2s (ajustable), hubo un warp
2723
+ if (Math.abs(perfDelta - dateDelta) > 2000) {
2724
+ console.warn('[RelojService] Salto temporal detectado (Warp). Se recomienda sincronizar.');
2725
+ this.forzarSincronizacion();
2726
+ }
2727
+ lastPerformance = currentPerformance;
2728
+ lastDate = currentDate;
2729
+ }, 5000);
2730
+ }
2731
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RelojService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2732
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RelojService, providedIn: 'root' });
2733
+ }
2734
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RelojService, decorators: [{
2735
+ type: Injectable,
2736
+ args: [{
2737
+ providedIn: 'root'
2738
+ }]
2739
+ }], ctorParameters: () => [] });
2740
+
2741
+ /*
2742
+ * Public API Surface of utils services
2743
+ */
2744
+
1855
2745
  /*
1856
2746
  * Public API Surface of utils
1857
2747
  */
1858
- // export * from './functions/common.function';
1859
- // export * from './lib/utils.component';
1860
- // export * from './lib/utils.service';
1861
2748
 
1862
2749
  /**
1863
2750
  * Generated bundle index. Do not edit.
1864
2751
  */
1865
2752
 
1866
- export { DataEnListaPipe, DataModel, DateDiffStringPipe, FiltroPipe, FormControlIsRequiredPipe, JsonParsePipe, NoSanitizePipe, TipoValorFuncionPipe, ZeroFillPipe, b64Decode, b64Encode, buscarPorCampo, changeSelect, changeSelectApi, changeSelectData, changeSelectDataApi, changeSelectReformateado, convertirBytes, dataEnLista, dateDiffString, decodeBase64Object, decodeBase64String, decodeBase64ToBlob, deepClone, deepMerge, delLocalStorage, desencriptar, downLoadFileStream, eliminarColumnaPorIndex, eliminarDuplicados, eliminarElementos, encodeBase64File, encodeBase64Object, encodeBase64String, encriptar, esNumero, establecerQuitarRequired, extraerDominio, formControlIsRequired, formatearFecha, formatearFechaCadena, formatearFechaFormato, generateRandomString, getBrowserName, getCambiarPwd, getDataArchivoFromPath, getFormValidationErrors, getLocalStorage, getUniqueValues, getUniqueValuesByProperty, groupBy, inicializarVariablesSessionStorage, isEmail, jwtToken, jwtTokenData, jwtTokenExpiracion, jwtTokenExpiracionFaltante, localStorageKeys, markAsTouchedWithoutEmitEvent, maskEmail, mensajeAlerta, mensajeConfirmacion, mensajeTimer, mensajeToast, mensajesDeError, mensajesErrorFormControl, mostrarValorEnBusqueda, nestGroupsBy, numberToWords, objectPropertiesBoolean, objectPropertiesToType, obtenerHostDesdeUrl, obtenerMimeType, obtenerUltimoOrden, ordenarArray, ordenarPorPropiedad, ordenarPorPropiedades, roundToDecimal, sanitizarNombreArchivo, seleccionarTextoInput, setCambiarPwd, setControlDesdeLista, setJwtTokenData, setLocalStorage, sumarObjetos, sumarPropiedades, tipoValorFuncion, toFormData, transformarFechasPorNombreDeCampo, verificarRUC, zeroFill };
2753
+ export { AutoSelectFirstDirective, AutocompleteMatchValidatorDirective, DataEnListaPipe, DataModel, DateDiffStringPipe, FiltroPipe, FormControlIsRequiredPipe, JsonParsePipe, JvsAutocompleteDirective, JvsDisplayWithPipe, JvsPopoverDirective, JvsPopoverListenerDirective, JvsPopoverPanelComponent, JvsPopoverService, NoSanitizePipe, RelojService, TipoValorFuncionPipe, ZeroFillPipe, b64Decode, b64Encode, buscarPorCampo, changeSelect, changeSelectApi, changeSelectData, changeSelectDataApi, changeSelectReformateado, convertirBytes, dataEnLista, dateDiffString, decodeBase64Object, decodeBase64String, decodeBase64ToBlob, deepClone, deepMerge, delLocalStorage, desencriptar, devError, devGroup, devGroupEnd, devLog, devTable, devTime, devTimeEnd, devWarn, downLoadFileStream, eliminarColumnaPorIndex, eliminarDuplicados, eliminarElementos, encodeBase64File, encodeBase64Object, encodeBase64String, encriptar, esNumero, esPromise, establecerQuitarRequired, extraerDominio, filtrarDatosLocal, formControlIsRequired, formatearFecha, formatearFechaCadena, formatearFechaFormato, generateRandomString, getBrowserName, getCambiarPwd, getDataArchivoFromPath, getFormValidationErrors, getLocalStorage, getUniqueValues, getUniqueValuesByProperty, getValueByPath, groupBy, inicializarVariablesSessionStorage, isEmail, isProduction, jwtToken, jwtTokenData, jwtTokenExpiracion, jwtTokenExpiracionFaltante, localStorageKeys, markAsTouchedWithoutEmitEvent, maskEmail, mensajeAlerta, mensajeConfirmacion, mensajeTimer, mensajeToast, mensajesDeError, mensajesErrorFormControl, mostrarValorEnBusqueda, nestGroupsBy, numberToWords, objectPropertiesBoolean, objectPropertiesToType, obtenerHostDesdeUrl, obtenerMimeType, obtenerUltimoOrden, ordenarArray, ordenarPorPropiedad, ordenarPorPropiedades, roundToDecimal, sanitizarNombreArchivo, seleccionarTextoInput, setCambiarPwd, setControlDesdeLista, setJwtTokenData, setLocalStorage, setProductionMode, sumarObjetos, sumarPropiedades, tipoValorFuncion, toFormData, transformarFechasPorNombreDeCampo, verificarRUC, zeroFill };
1867
2754
  //# sourceMappingURL=jvsoft-utils.mjs.map