valtech-components 2.0.694 → 2.0.711

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.
@@ -50,7 +50,7 @@ import 'prismjs/components/prism-json';
50
50
  * Current version of valtech-components.
51
51
  * This is automatically updated during the publish process.
52
52
  */
53
- const VERSION = '2.0.694';
53
+ const VERSION = '2.0.711';
54
54
 
55
55
  /**
56
56
  * Servicio para gestionar presets de componentes.
@@ -6348,7 +6348,7 @@ class CheckboxRadioInputComponent {
6348
6348
  </ion-checkbox>
6349
6349
  }
6350
6350
  </div>
6351
- `, isInline: true, styles: [".checkbox-radio-group{display:flex;flex-direction:column;gap:12px}.checkbox-radio-group ion-checkbox{--size: 24px;--checkbox-background-checked: var(--ion-color-primary);--checkmark-color: var(--ion-color-primary-contrast)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }] }); }
6351
+ `, isInline: true, styles: [".checkbox-radio-group{display:flex;flex-direction:row;gap:12px;margin-top:.5rem}.checkbox-radio-group ion-checkbox{--size: 24px;--checkbox-background-checked: var(--ion-color-primary);--checkmark-color: var(--ion-color-primary-contrast)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }] }); }
6352
6352
  }
6353
6353
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CheckboxRadioInputComponent, decorators: [{
6354
6354
  type: Component,
@@ -6365,7 +6365,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6365
6365
  </ion-checkbox>
6366
6366
  }
6367
6367
  </div>
6368
- `, styles: [".checkbox-radio-group{display:flex;flex-direction:column;gap:12px}.checkbox-radio-group ion-checkbox{--size: 24px;--checkbox-background-checked: var(--ion-color-primary);--checkmark-color: var(--ion-color-primary-contrast)}\n"] }]
6368
+ `, styles: [".checkbox-radio-group{display:flex;flex-direction:row;gap:12px;margin-top:.5rem}.checkbox-radio-group ion-checkbox{--size: 24px;--checkbox-background-checked: var(--ion-color-primary);--checkmark-color: var(--ion-color-primary-contrast)}\n"] }]
6369
6369
  }], propDecorators: { props: [{
6370
6370
  type: Input
6371
6371
  }] } });
@@ -9058,18 +9058,28 @@ class SelectSearchComponent {
9058
9058
  return;
9059
9059
  }
9060
9060
  const controlValue = this.props.control.value;
9061
- if (controlValue === null || controlValue === undefined) {
9062
- this.selectedItems = [];
9063
- return;
9064
- }
9065
- // PERF: Use a Map for faster lookup if options are large
9066
- if (this.props.options && this.props.options.length > 0) {
9067
- const map = new Map(this.props.options.map(opt => [opt[this.valueProperty], opt]));
9068
- const selectedOption = map.get(controlValue);
9069
- this.selectedItems = selectedOption ? [selectedOption] : [];
9061
+ if (this.props.mode === 'legacy') {
9062
+ // Permisivo: muestra el valor aunque no esté en las opciones
9063
+ if (controlValue !== null && controlValue !== undefined) {
9064
+ this.displayValue = controlValue.toString();
9065
+ this.selectedItems = [];
9066
+ return;
9067
+ }
9070
9068
  }
9071
9069
  else {
9072
- this.selectedItems = [];
9070
+ // Estricto: solo muestra si coincide con una opción
9071
+ if (controlValue === null || controlValue === undefined) {
9072
+ this.selectedItems = [];
9073
+ return;
9074
+ }
9075
+ if (this.props.options && this.props.options.length > 0) {
9076
+ const map = new Map(this.props.options.map(opt => [opt[this.valueProperty], opt]));
9077
+ const selectedOption = map.get(controlValue);
9078
+ this.selectedItems = selectedOption ? [selectedOption] : [];
9079
+ }
9080
+ else {
9081
+ this.selectedItems = [];
9082
+ }
9073
9083
  }
9074
9084
  }
9075
9085
  applyDefaultValue() {
@@ -9161,6 +9171,10 @@ class SelectSearchComponent {
9161
9171
  return this.selectedItems.some(selectedItem => selectedItem[this.valueProperty] === item[this.valueProperty]);
9162
9172
  }
9163
9173
  updateDisplayValue() {
9174
+ if (this.props?.mode === 'legacy' && this.selectedItems.length === 0 && this.props?.control?.value) {
9175
+ this.displayValue = this.props.control.value.toString();
9176
+ return;
9177
+ }
9164
9178
  if (this.selectedItems.length === 0) {
9165
9179
  this.displayValue = '';
9166
9180
  return;
@@ -22682,8 +22696,6 @@ class FormComponent {
22682
22696
  this.onSelectChange = new EventEmitter();
22683
22697
  this.types = InputType;
22684
22698
  this.subscriptions = [];
22685
- // Cache para evitar crear nuevos objetos en cada ciclo de change detection
22686
- this.fieldPropsCache = new Map();
22687
22699
  this.actionsCache = [];
22688
22700
  }
22689
22701
  ngOnInit() {
@@ -22711,15 +22723,14 @@ class FormComponent {
22711
22723
  this.trackSelectChanges(field.name);
22712
22724
  });
22713
22725
  });
22714
- // Construir cache inicial
22715
- this.rebuildFieldPropsCache();
22726
+ this.syncFieldControlStates();
22716
22727
  this.updateActionsCache();
22717
22728
  this.previousState = this.props.state;
22718
22729
  }
22719
22730
  ngOnChanges(changes) {
22720
22731
  // Cuando props cambia, reconstruir el cache
22721
22732
  if (changes['props'] && this.form) {
22722
- this.rebuildFieldPropsCache();
22733
+ this.syncFieldControlStates();
22723
22734
  this.updateActionsCache();
22724
22735
  this.previousState = this.props.state;
22725
22736
  }
@@ -22732,14 +22743,11 @@ class FormComponent {
22732
22743
  }
22733
22744
  }
22734
22745
  /**
22735
- * Reconstruye el cache de props para cada field.
22736
- * Esto evita crear nuevos objetos en cada ciclo de change detection.
22746
+ * Synchronizes form control disabled/enabled state with field metadata.
22737
22747
  */
22738
- rebuildFieldPropsCache() {
22739
- this.fieldPropsCache.clear();
22748
+ syncFieldControlStates() {
22740
22749
  this.props.sections.forEach(section => {
22741
22750
  section.fields.forEach(field => {
22742
- const token = field.token || `input-${field.type}-${field.name}`;
22743
22751
  if (field.type === this.types.NUMBER_FROM_TO) {
22744
22752
  const fromControl = this.getControl(`${field.name}_from`);
22745
22753
  const toControl = this.getControl(`${field.name}_to`);
@@ -22751,13 +22759,6 @@ class FormComponent {
22751
22759
  fromControl.enable({ emitEvent: false });
22752
22760
  toControl.enable({ emitEvent: false });
22753
22761
  }
22754
- this.fieldPropsCache.set(field.name, {
22755
- ...field,
22756
- token,
22757
- fromControl,
22758
- toControl,
22759
- control: undefined,
22760
- });
22761
22762
  }
22762
22763
  else {
22763
22764
  const control = this.getControl(field.name);
@@ -22767,11 +22768,6 @@ class FormComponent {
22767
22768
  else {
22768
22769
  control.enable({ emitEvent: false });
22769
22770
  }
22770
- this.fieldPropsCache.set(field.name, {
22771
- ...field,
22772
- token,
22773
- control,
22774
- });
22775
22771
  }
22776
22772
  });
22777
22773
  });
@@ -22810,50 +22806,16 @@ class FormComponent {
22810
22806
  return this.Form.get(field);
22811
22807
  }
22812
22808
  getFieldProp(field) {
22813
- // Retornar del cache para evitar crear nuevos objetos en cada ciclo de change detection
22814
- const cached = this.fieldPropsCache.get(field.name);
22815
- if (cached) {
22816
- return cached;
22809
+ if (!field.token) {
22810
+ field.token = `input-${field.type}-${field.name}`;
22817
22811
  }
22818
- // Fallback: generar y cachear si no existe (no debería ocurrir normalmente)
22819
- const token = field.token || `input-${field.type}-${field.name}`;
22820
22812
  if (field.type === this.types.NUMBER_FROM_TO) {
22821
22813
  const fromControl = this.getControl(`${field.name}_from`);
22822
22814
  const toControl = this.getControl(`${field.name}_to`);
22823
- if (field.state === ComponentStates.DISABLED) {
22824
- fromControl.disable({ emitEvent: false });
22825
- toControl.disable({ emitEvent: false });
22826
- }
22827
- else {
22828
- fromControl.enable({ emitEvent: false });
22829
- toControl.enable({ emitEvent: false });
22830
- }
22831
- const props = {
22832
- ...field,
22833
- token,
22834
- fromControl,
22835
- toControl,
22836
- control: undefined,
22837
- };
22838
- this.fieldPropsCache.set(field.name, props);
22839
- return props;
22840
- }
22841
- else {
22842
- const control = this.getControl(field.name);
22843
- if (field.state === ComponentStates.DISABLED) {
22844
- control.disable({ emitEvent: false });
22845
- }
22846
- else {
22847
- control.enable({ emitEvent: false });
22848
- }
22849
- const props = {
22850
- ...field,
22851
- token,
22852
- control,
22853
- };
22854
- this.fieldPropsCache.set(field.name, props);
22855
- return props;
22815
+ return { ...field, fromControl, toControl, control: undefined };
22856
22816
  }
22817
+ const control = this.getControl(field.name);
22818
+ return { ...field, control };
22857
22819
  }
22858
22820
  get isAtEndOfForm() {
22859
22821
  return isAtEnd(this.elementRef);
@@ -28467,6 +28429,38 @@ class TypedCollection {
28467
28429
  }
28468
28430
  return this.firestore.deleteDoc(this.collectionPath, id);
28469
28431
  }
28432
+ /**
28433
+ * Actualiza múltiples documentos en una sola operación atómica.
28434
+ */
28435
+ async batchUpdate(ids, data) {
28436
+ if (ids.length === 0)
28437
+ return;
28438
+ const BATCH_LIMIT = 500; // Firestore batch limit
28439
+ for (let i = 0; i < ids.length; i += BATCH_LIMIT) {
28440
+ const chunk = ids.slice(i, i + BATCH_LIMIT);
28441
+ await this.firestore.batch(batch => {
28442
+ for (const id of chunk) {
28443
+ batch.update(`${this.collectionPath}/${id}`, data);
28444
+ }
28445
+ });
28446
+ }
28447
+ }
28448
+ /**
28449
+ * Elimina múltiples documentos en una sola operación atómica.
28450
+ */
28451
+ async batchDelete(ids) {
28452
+ if (ids.length === 0)
28453
+ return;
28454
+ const BATCH_LIMIT = 500;
28455
+ for (let i = 0; i < ids.length; i += BATCH_LIMIT) {
28456
+ const chunk = ids.slice(i, i + BATCH_LIMIT);
28457
+ await this.firestore.batch(batch => {
28458
+ for (const id of chunk) {
28459
+ batch.delete(`${this.collectionPath}/${id}`);
28460
+ }
28461
+ });
28462
+ }
28463
+ }
28470
28464
  /**
28471
28465
  * Restaura un documento soft-deleted.
28472
28466
  */
@@ -29909,6 +29903,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
29909
29903
  * Se auto-inicializa cuando AuthService tiene un usuario autenticado.
29910
29904
  * También puede inicializarse manualmente con `initialize(userId)`.
29911
29905
  */
29906
+ /** Límite por defecto de notificaciones a cargar */
29907
+ const DEFAULT_LIMIT = 50;
29912
29908
  /**
29913
29909
  * Servicio para leer notificaciones desde Firestore.
29914
29910
  *
@@ -29986,7 +29982,9 @@ class NotificationsService {
29986
29982
  // Path relativo - FirestoreService agrega automáticamente apps/{appId}/
29987
29983
  // NO agregar apps/ aquí para evitar doble prefijo
29988
29984
  const basePath = `users/${userId}/notifications`;
29989
- this.collection = this.collectionFactory.create(basePath, { timestamps: true });
29985
+ this.collection = this.collectionFactory.create(basePath, {
29986
+ timestamps: true,
29987
+ });
29990
29988
  console.log('[Notifications] Initialized with path:', basePath);
29991
29989
  }
29992
29990
  /**
@@ -30005,39 +30003,59 @@ class NotificationsService {
30005
30003
  // LECTURA
30006
30004
  // ===========================================================================
30007
30005
  /**
30008
- * Obtiene todas las notificaciones ordenadas por fecha descendente.
30009
- * Real-time: se actualiza automáticamente cuando cambian los datos.
30006
+ * Obtiene notificaciones ordenadas por fecha descendente (real-time).
30007
+ * Se actualiza automáticamente cuando cambian los datos.
30008
+ *
30009
+ * @param limit - Máximo de notificaciones a cargar (default: 50)
30010
30010
  */
30011
- getAll() {
30011
+ getAll(limit = DEFAULT_LIMIT) {
30012
30012
  if (!this.collection)
30013
30013
  return of([]);
30014
30014
  return this.collection.watchAll({
30015
30015
  orderBy: [{ field: 'createdAt', direction: 'desc' }],
30016
+ limit,
30016
30017
  });
30017
30018
  }
30018
30019
  /**
30019
- * Obtiene todas las notificaciones (one-time fetch sin listener).
30020
+ * Obtiene notificaciones (one-time fetch sin listener).
30020
30021
  * Útil para cargas iniciales sin necesidad de updates en tiempo real.
30022
+ *
30023
+ * @param limit - Máximo de notificaciones a cargar (default: 50)
30021
30024
  */
30022
- async getAllOnce() {
30025
+ async getAllOnce(limit = DEFAULT_LIMIT) {
30023
30026
  if (!this.collection)
30024
30027
  return [];
30025
30028
  return this.collection.getAllOnce({
30026
30029
  orderBy: [{ field: 'createdAt', direction: 'desc' }],
30030
+ limit,
30027
30031
  });
30028
30032
  }
30029
30033
  /**
30030
- * Obtiene solo notificaciones no leídas.
30034
+ * Obtiene solo notificaciones no leídas (real-time, filtrado server-side).
30035
+ *
30036
+ * @param limit - Máximo de notificaciones (default: 50)
30031
30037
  */
30032
- getUnread() {
30033
- return this.getAll().pipe(map$1(notifications => notifications.filter(n => !n.isRead)));
30038
+ getUnread(limit = DEFAULT_LIMIT) {
30039
+ if (!this.collection)
30040
+ return of([]);
30041
+ return this.collection.watchAll({
30042
+ where: [{ field: 'isRead', operator: '==', value: false }],
30043
+ orderBy: [{ field: 'createdAt', direction: 'desc' }],
30044
+ limit,
30045
+ });
30034
30046
  }
30035
30047
  /**
30036
- * Cuenta notificaciones no leídas.
30048
+ * Cuenta notificaciones no leídas (real-time, filtrado server-side).
30037
30049
  * Útil para badges en UI.
30038
30050
  */
30039
30051
  getUnreadCount() {
30040
- return this.getUnread().pipe(map$1(n => n.length));
30052
+ if (!this.collection)
30053
+ return of(0);
30054
+ return this.collection
30055
+ .watchAll({
30056
+ where: [{ field: 'isRead', operator: '==', value: false }],
30057
+ })
30058
+ .pipe(map$1(n => n.length));
30041
30059
  }
30042
30060
  /**
30043
30061
  * Obtiene una notificación por ID.
@@ -30059,7 +30077,7 @@ class NotificationsService {
30059
30077
  await this.collection.update(notificationId, { isRead: true });
30060
30078
  }
30061
30079
  /**
30062
- * Marca todas las notificaciones como leídas.
30080
+ * Marca todas las notificaciones no leídas como leídas (batch write).
30063
30081
  */
30064
30082
  async markAllAsRead() {
30065
30083
  if (!this.collection)
@@ -30067,7 +30085,8 @@ class NotificationsService {
30067
30085
  const unread = await this.collection.query({
30068
30086
  where: [{ field: 'isRead', operator: '==', value: false }],
30069
30087
  });
30070
- await Promise.all(unread.map(n => this.collection.update(n.id, { isRead: true })));
30088
+ const ids = unread.map(n => n.id).filter(Boolean);
30089
+ await this.collection.batchUpdate(ids, { isRead: true });
30071
30090
  }
30072
30091
  /**
30073
30092
  * Elimina una notificación.
@@ -30078,13 +30097,14 @@ class NotificationsService {
30078
30097
  await this.collection.delete(notificationId);
30079
30098
  }
30080
30099
  /**
30081
- * Elimina todas las notificaciones del usuario.
30100
+ * Elimina todas las notificaciones del usuario (batch delete).
30082
30101
  */
30083
30102
  async deleteAll() {
30084
30103
  if (!this.collection)
30085
30104
  return;
30086
30105
  const all = await this.collection.getAll();
30087
- await Promise.all(all.map(n => this.collection.delete(n.id)));
30106
+ const ids = all.map(n => n.id).filter(Boolean);
30107
+ await this.collection.batchDelete(ids);
30088
30108
  }
30089
30109
  /**
30090
30110
  * Limpia el estado del servicio.