@jvsoft/utils 0.0.13-alpha.5 → 1.0.0-alpha.10

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} +543 -190
  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 +1142 -196
  23. package/fesm2022/jvsoft-utils.mjs.map +1 -1
  24. package/functions/base64.d.ts +87 -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 +27 -17
  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 -2
  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
- /**
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
270
  /**
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,54 +602,138 @@ function changeSelectReformateado(config) {
470
602
  objThis.filtrados[variableResultado] = data[tipoReq] ?? [];
471
603
  });
472
604
  }
605
+
606
+ function seleccionarTextoInput(event) {
607
+ event.target.select();
608
+ }
609
+
473
610
  /**
474
- * Alias para compatibilidad.
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
475
615
  */
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
- });
616
+ function b64Encode(val) {
617
+ return Buffer.from(val, 'binary').toString('base64');
489
618
  }
490
619
  /**
491
- * Alias para compatibilidad.
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
492
624
  */
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
- });
625
+ function b64Decode(val) {
626
+ return Buffer.from(val, 'base64').toString('binary');
504
627
  }
505
628
  /**
506
- * Comprueba si un valor es una Promesa.
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
+ * ```
507
638
  */
508
- function esPromise(valor) {
509
- return !!valor && typeof valor.then === 'function';
639
+ function encodeBase64String(str) {
640
+ const encoder = new TextEncoder();
641
+ const bytes = encoder.encode(str);
642
+ let binary = '';
643
+ bytes.forEach(b => binary += String.fromCharCode(b));
644
+ return btoa(binary);
510
645
  }
511
-
512
- function seleccionarTextoInput(event) {
513
- event.target.select();
646
+ /**
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
+ * ```
656
+ */
657
+ function decodeBase64String(b64) {
658
+ const binary = atob(b64);
659
+ const bytes = new Uint8Array(binary.length);
660
+ for (let i = 0; i < binary.length; i++) {
661
+ bytes[i] = binary.charCodeAt(i);
662
+ }
663
+ const decoder = new TextDecoder();
664
+ return decoder.decode(bytes);
514
665
  }
515
-
516
- function b64Encode(val) {
517
- return Buffer.from(val, 'binary').toString('base64');
666
+ /**
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
+ * ```
676
+ */
677
+ function encodeBase64Object(obj) {
678
+ return encodeBase64String(JSON.stringify(obj));
518
679
  }
519
- function b64Decode(val) {
520
- return Buffer.from(val, 'base64').toString('binary');
680
+ /**
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
+ * ```
690
+ */
691
+ function decodeBase64Object(b64) {
692
+ return JSON.parse(decodeBase64String(b64));
693
+ }
694
+ /**
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
+ * ```
705
+ */
706
+ function encodeBase64File(file) {
707
+ return new Promise((resolve, reject) => {
708
+ const reader = new FileReader();
709
+ reader.onload = () => {
710
+ const result = reader.result;
711
+ resolve(result.split(',')[1]); // quita el prefijo "data:...;base64,"
712
+ };
713
+ reader.onerror = reject;
714
+ reader.readAsDataURL(file);
715
+ });
716
+ }
717
+ /**
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
+ * ```
729
+ */
730
+ function decodeBase64ToBlob(b64, mimeType = 'application/octet-stream') {
731
+ const byteChars = atob(b64);
732
+ const byteNumbers = new Array(byteChars.length);
733
+ for (let i = 0; i < byteChars.length; i++) {
734
+ byteNumbers[i] = byteChars.charCodeAt(i);
735
+ }
736
+ return new Blob([new Uint8Array(byteNumbers)], { type: mimeType });
521
737
  }
522
738
 
523
739
  function getBrowserName() {
@@ -583,6 +799,147 @@ function formatearFechaCadena(fecha) {
583
799
  return new Date(year, month, day);
584
800
  }
585
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
+
586
943
  function maskEmail(email) {
587
944
  const [user, domain] = email.split("@");
588
945
  if (user.length <= 2) {
@@ -1791,18 +2148,607 @@ function zeroFill(value, digitos, ...args) {
1791
2148
  return new ZeroFillPipe().transform(value, digitos, args);
1792
2149
  }
1793
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
+
1794
2206
  // export * from './otros';
1795
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
+
1796
2745
  /*
1797
2746
  * Public API Surface of utils
1798
2747
  */
1799
- // export * from './functions/common.function';
1800
- // export * from './lib/utils.component';
1801
- // export * from './lib/utils.service';
1802
2748
 
1803
2749
  /**
1804
2750
  * Generated bundle index. Do not edit.
1805
2751
  */
1806
2752
 
1807
- export { DataEnListaPipe, DataModel, DateDiffStringPipe, FiltroPipe, FormControlIsRequiredPipe, JsonParsePipe, NoSanitizePipe, TipoValorFuncionPipe, ZeroFillPipe, b64Decode, b64Encode, buscarPorCampo, changeSelect, changeSelectApi, changeSelectData, changeSelectDataApi, changeSelectReformateado, convertirBytes, dataEnLista, dateDiffString, deepClone, deepMerge, delLocalStorage, desencriptar, downLoadFileStream, eliminarColumnaPorIndex, eliminarDuplicados, eliminarElementos, 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 };
1808
2754
  //# sourceMappingURL=jvsoft-utils.mjs.map