slicejs-web-framework 3.3.6 → 3.3.8

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.
@@ -1,369 +1,420 @@
1
- /**
2
- * ContextManager - Sistema de contextos compartidos para Slice.js
3
- * Ubicación: /Slice/Components/Structural/ContextManager/ContextManager.js
4
- *
5
- * Características:
6
- * - Estado compartido entre componentes
7
- * - Usa EventManager internamente para notificaciones
8
- * - Selectores para observar campos específicos
9
- * - Auto-limpieza cuando componentes se destruyen
10
- * - Persistencia opcional en localStorage
11
- */
12
- /**
13
- * @typedef {Object} ContextOptions
14
- * @property {boolean} [persist]
15
- * @property {string} [storageKey]
16
- */
17
-
18
- export default class ContextManager {
19
- constructor() {
20
- // Map<contextName, { state, options }>
21
- this.contexts = new Map();
22
- }
23
-
24
- init() {
25
- return true;
26
- }
27
-
28
- // ============================================
29
- // CREAR CONTEXTO
30
- // ============================================
31
-
32
- /**
33
- * Crear un nuevo contexto
34
- * @param {string} name - Nombre unico del contexto
35
- * @param {Object} [initialState] - Estado inicial
36
- * @param {ContextOptions} [options] - Opciones: { persist, storageKey }
37
- * @returns {boolean}
38
- *
39
- * @example
40
- * slice.context.create('auth', {
41
- * isLoggedIn: false,
42
- * user: null
43
- * });
44
- *
45
- * // Con persistencia en localStorage
46
- * slice.context.create('preferences', {
47
- * theme: 'light',
48
- * language: 'es'
49
- * }, { persist: true });
50
- */
51
- create(name, initialState = {}, options = {}) {
52
- if (this.contexts.has(name)) {
53
- slice.logger.logWarning('ContextManager', `El contexto "${name}" ya existe`);
54
- return false;
55
- }
56
-
57
- // Cargar estado persistido si existe
58
- let state = initialState;
59
- if (options.persist) {
60
- const storageKey = options.storageKey || `slice_context_${name}`;
61
- const persisted = this._loadFromStorage(storageKey);
62
- if (persisted !== null) {
63
- state = persisted;
64
- }
65
- }
66
-
67
- this.contexts.set(name, {
68
- state,
69
- options: {
70
- persist: options.persist || false,
71
- storageKey: options.storageKey || `slice_context_${name}`,
72
- },
73
- });
74
-
75
- slice.logger.logInfo('ContextManager', `Contexto "${name}" creado`);
76
-
77
- return true;
78
- }
79
-
80
- // ============================================
81
- // LEER ESTADO
82
- // ============================================
83
-
84
- /**
85
- * Obtener el estado actual de un contexto
86
- * @param {string} name - Nombre del contexto
87
- * @returns {any|null} Estado actual o null si no existe
88
- *
89
- * @example
90
- * const auth = slice.context.getState('auth');
91
- * console.log(auth.user.name);
92
- */
93
- getState(name) {
94
- if (!this.contexts.has(name)) {
95
- slice.logger.logError('ContextManager', `El contexto "${name}" no existe`);
96
- return null;
97
- }
98
-
99
- return this.contexts.get(name).state;
100
- }
101
-
102
- // ============================================
103
- // ACTUALIZAR ESTADO
104
- // ============================================
105
-
106
- /**
107
- * Actualizar el estado de un contexto
108
- * @param {string} name - Nombre del contexto
109
- * @param {Object|Function} updater - Nuevo estado o funcion (prevState) => newState
110
- * @returns {void}
111
- *
112
- * @example
113
- * // Reemplazar con objeto
114
- * slice.context.setState('auth', {
115
- * isLoggedIn: true,
116
- * user: { name: 'Juan' }
117
- * });
118
- *
119
- * // Usar funcion para acceder al estado anterior
120
- * slice.context.setState('cart', (prev) => ({
121
- * ...prev,
122
- * items: [...prev.items, nuevoProducto],
123
- * total: prev.total + nuevoProducto.price
124
- * }));
125
- */
126
- setState(name, updater) {
127
- if (!this.contexts.has(name)) {
128
- slice.logger.logError('ContextManager', `El contexto "${name}" no existe`);
129
- return;
130
- }
131
-
132
- const context = this.contexts.get(name);
133
- const prevState = context.state;
134
-
135
- // Calcular nuevo estado
136
- let newState;
137
- if (typeof updater === 'function') {
138
- newState = updater(prevState);
139
- } else {
140
- newState = updater;
141
- }
142
-
143
- // Guardar nuevo estado
144
- context.state = newState;
145
-
146
- // Persistir si está habilitado
147
- if (context.options.persist) {
148
- this._saveToStorage(name, newState, context.options.storageKey);
149
- }
150
-
151
- // Emitir evento para notificar a los watchers
152
- slice.events.emit(`context:${name}`, newState, prevState);
153
-
154
- slice.logger.logInfo('ContextManager', `Contexto "${name}" actualizado`);
155
- }
156
-
157
- // ============================================
158
- // WATCH (OBSERVAR CAMBIOS)
159
- // ============================================
160
-
161
- /**
162
- * Observar cambios en un contexto
163
- * @param {string} name - Nombre del contexto
164
- * @param {HTMLElement} component - Componente Slice para auto-cleanup
165
- * @param {(value: any) => void} callback - Funcion a ejecutar cuando cambia
166
- * @param {(state: any) => any} [selector] - Opcional. Funcion para seleccionar campos
167
- * @returns {string|null} subscriptionId
168
- *
169
- * @example
170
- * // Observar todo el estado
171
- * slice.context.watch('auth', this, (state) => {
172
- * this.render(state);
173
- * });
174
- *
175
- * // Observar un campo especifico
176
- * slice.context.watch('auth', this, (name) => {
177
- * this.$name.textContent = name;
178
- * }, state => state.user.name);
179
- *
180
- * // Observar multiples campos
181
- * slice.context.watch('auth', this, (data) => {
182
- * this.$name.textContent = data.name;
183
- * this.$avatar.src = data.avatar;
184
- * }, state => ({
185
- * name: state.user.name,
186
- * avatar: state.user.avatar
187
- * }));
188
- *
189
- * // Valores computados
190
- * slice.context.watch('cart', this, (total) => {
191
- * this.$total.textContent = `${total}`;
192
- * }, state => state.items.reduce((sum, i) => sum + i.price, 0));
193
- */
194
- watch(name, component, callback, selector = null) {
195
- if (!this.contexts.has(name)) {
196
- slice.logger.logError('ContextManager', `El contexto "${name}" no existe`);
197
- return null;
198
- }
199
-
200
- if (!component?.sliceId) {
201
- slice.logger.logError('ContextManager', 'watch() requiere un componente Slice válido');
202
- return null;
203
- }
204
-
205
- if (typeof callback !== 'function') {
206
- slice.logger.logError('ContextManager', 'El callback debe ser una función');
207
- return null;
208
- }
209
-
210
- // Guardar el valor anterior del selector para comparar
211
- let lastSelectedValue = selector ? this._getSelectedValue(name, selector) : this.getState(name);
212
-
213
- // Crear wrapper que aplica el selector y compara
214
- const wrappedCallback = (newState, prevState) => {
215
- if (selector) {
216
- const newSelectedValue = this._applySelector(newState, selector);
217
- const prevSelectedValue = this._applySelector(prevState, selector);
218
-
219
- // Solo ejecutar si el valor seleccionado cambió
220
- if (!this._isEqual(newSelectedValue, prevSelectedValue)) {
221
- lastSelectedValue = newSelectedValue;
222
- callback(newSelectedValue);
223
- }
224
- } else {
225
- // Sin selector, siempre ejecutar
226
- callback(newState);
227
- }
228
- };
229
-
230
- // Suscribirse al evento del contexto
231
- const subscriptionId = slice.events.subscribe(`context:${name}`, wrappedCallback, { component });
232
-
233
- slice.logger.logInfo('ContextManager', `Watch registrado en "${name}" para ${component.sliceId}`);
234
-
235
- return subscriptionId;
236
- }
237
-
238
- // ============================================
239
- // UTILIDADES
240
- // ============================================
241
-
242
- /**
243
- * Verificar si un contexto existe
244
- * @param {string} name - Nombre del contexto
245
- * @returns {boolean}
246
- */
247
- has(name) {
248
- return this.contexts.has(name);
249
- }
250
-
251
- /**
252
- * Eliminar un contexto
253
- * @param {string} name - Nombre del contexto
254
- * @returns {boolean}
255
- */
256
- destroy(name) {
257
- if (!this.contexts.has(name)) {
258
- return false;
259
- }
260
-
261
- const context = this.contexts.get(name);
262
-
263
- // Limpiar storage si existe
264
- if (context.options.persist) {
265
- this._removeFromStorage(context.options.storageKey);
266
- }
267
-
268
- this.contexts.delete(name);
269
-
270
- slice.logger.logInfo('ContextManager', `Contexto "${name}" eliminado`);
271
-
272
- return true;
273
- }
274
-
275
- /**
276
- * Obtener lista de todos los contextos
277
- * @returns {string[]}
278
- */
279
- list() {
280
- return Array.from(this.contexts.keys());
281
- }
282
-
283
- // ============================================
284
- // MÉTODOS PRIVADOS
285
- // ============================================
286
-
287
- /**
288
- * Aplicar selector al estado
289
- */
290
- _applySelector(state, selector) {
291
- try {
292
- return selector(state);
293
- } catch (error) {
294
- slice.logger.logWarning('ContextManager', 'Error al aplicar selector', error);
295
- return undefined;
296
- }
297
- }
298
-
299
- /**
300
- * Obtener valor seleccionado del estado actual
301
- */
302
- _getSelectedValue(name, selector) {
303
- const state = this.getState(name);
304
- return this._applySelector(state, selector);
305
- }
306
-
307
- /**
308
- * Comparar dos valores (shallow)
309
- */
310
- _isEqual(a, b) {
311
- // Mismo valor primitivo o referencia
312
- if (a === b) return true;
313
-
314
- // Si alguno es null/undefined
315
- if (a == null || b == null) return false;
316
-
317
- // Si no son objetos, ya sabemos que son diferentes
318
- if (typeof a !== 'object' || typeof b !== 'object') return false;
319
-
320
- // Comparación shallow de objetos
321
- const keysA = Object.keys(a);
322
- const keysB = Object.keys(b);
323
-
324
- if (keysA.length !== keysB.length) return false;
325
-
326
- for (const key of keysA) {
327
- if (a[key] !== b[key]) return false;
328
- }
329
-
330
- return true;
331
- }
332
-
333
- /**
334
- * Cargar estado desde localStorage
335
- */
336
- _loadFromStorage(storageKey) {
337
- try {
338
- const data = localStorage.getItem(storageKey);
339
- if (data) {
340
- return JSON.parse(data);
341
- }
342
- } catch (error) {
343
- slice.logger.logWarning('ContextManager', `Error cargando "${storageKey}" de localStorage`, error);
344
- }
345
- return null;
346
- }
347
-
348
- /**
349
- * Guardar estado en localStorage
350
- */
351
- _saveToStorage(name, state, storageKey) {
352
- try {
353
- localStorage.setItem(storageKey, JSON.stringify(state));
354
- } catch (error) {
355
- slice.logger.logWarning('ContextManager', `Error guardando "${name}" en localStorage`, error);
356
- }
357
- }
358
-
359
- /**
360
- * Eliminar estado de localStorage
361
- */
362
- _removeFromStorage(storageKey) {
363
- try {
364
- localStorage.removeItem(storageKey);
365
- } catch (error) {
366
- // Ignorar errores al eliminar
367
- }
368
- }
369
- }
1
+ /**
2
+ * ContextManager - Sistema de contextos compartidos para Slice.js
3
+ * Ubicación: /Slice/Components/Structural/ContextManager/ContextManager.js
4
+ *
5
+ * Características:
6
+ * - Estado compartido entre componentes
7
+ * - Usa EventManager internamente para notificaciones
8
+ * - Selectores para observar campos específicos
9
+ * - Auto-limpieza cuando componentes se destruyen
10
+ * - Persistencia opcional en localStorage
11
+ */
12
+ /**
13
+ * @typedef {Object} ContextOptions
14
+ * @property {boolean} [persist]
15
+ * @property {string} [storageKey]
16
+ */
17
+
18
+ export default class ContextManager {
19
+ constructor() {
20
+ // Map<contextName, { state, options }>
21
+ this.contexts = new Map();
22
+ }
23
+
24
+ init() {
25
+ return true;
26
+ }
27
+
28
+ // ============================================
29
+ // CREAR CONTEXTO
30
+ // ============================================
31
+
32
+ /**
33
+ * Crear un nuevo contexto
34
+ * @param {string} name - Nombre unico del contexto
35
+ * @param {Object} [initialState] - Estado inicial
36
+ * @param {ContextOptions} [options] - Opciones: { persist, storageKey }
37
+ * @returns {boolean}
38
+ *
39
+ * @example
40
+ * slice.context.create('auth', {
41
+ * isLoggedIn: false,
42
+ * user: null
43
+ * });
44
+ *
45
+ * // Con persistencia en localStorage
46
+ * slice.context.create('preferences', {
47
+ * theme: 'light',
48
+ * language: 'es'
49
+ * }, { persist: true });
50
+ */
51
+ create(name, initialState = {}, options = {}) {
52
+ if (this.contexts.has(name)) {
53
+ slice.logger.logWarning('ContextManager', `El contexto "${name}" ya existe`);
54
+ return false;
55
+ }
56
+
57
+ // Cargar estado persistido si existe
58
+ let state = initialState;
59
+ if (options.persist) {
60
+ const storageKey = options.storageKey || `slice_context_${name}`;
61
+ const persisted = this._loadFromStorage(storageKey);
62
+ if (persisted !== null) {
63
+ state = persisted;
64
+ }
65
+ }
66
+
67
+ this.contexts.set(name, {
68
+ state,
69
+ options: {
70
+ persist: options.persist || false,
71
+ storageKey: options.storageKey || `slice_context_${name}`,
72
+ },
73
+ });
74
+
75
+ slice.logger.logInfo('ContextManager', `Contexto "${name}" creado`);
76
+
77
+ return true;
78
+ }
79
+
80
+ // ============================================
81
+ // LEER ESTADO
82
+ // ============================================
83
+
84
+ /**
85
+ * Obtener el estado actual de un contexto
86
+ * @param {string} name - Nombre del contexto
87
+ * @returns {any|null} Estado actual o null si no existe
88
+ *
89
+ * @example
90
+ * const auth = slice.context.getState('auth');
91
+ * console.log(auth.user.name);
92
+ */
93
+ getState(name) {
94
+ if (!this.contexts.has(name)) {
95
+ slice.logger.logError('ContextManager', `El contexto "${name}" no existe`);
96
+ return null;
97
+ }
98
+
99
+ return this.contexts.get(name).state;
100
+ }
101
+
102
+ // ============================================
103
+ // ACTUALIZAR ESTADO
104
+ // ============================================
105
+
106
+ /**
107
+ * Actualizar el estado de un contexto
108
+ * @param {string} name - Nombre del contexto
109
+ * @param {Object|Function} updater - Nuevo estado o funcion (prevState) => newState
110
+ * @returns {void}
111
+ *
112
+ * @example
113
+ * // Reemplazar con objeto
114
+ * slice.context.setState('auth', {
115
+ * isLoggedIn: true,
116
+ * user: { name: 'Juan' }
117
+ * });
118
+ *
119
+ * // Usar funcion para acceder al estado anterior
120
+ * slice.context.setState('cart', (prev) => ({
121
+ * ...prev,
122
+ * items: [...prev.items, nuevoProducto],
123
+ * total: prev.total + nuevoProducto.price
124
+ * }));
125
+ */
126
+ setState(name, updater) {
127
+ if (!this.contexts.has(name)) {
128
+ slice.logger.logError('ContextManager', `El contexto "${name}" no existe`);
129
+ return;
130
+ }
131
+
132
+ const context = this.contexts.get(name);
133
+ const prevState = context.state;
134
+
135
+ // Calcular nuevo estado
136
+ let newState;
137
+ if (typeof updater === 'function') {
138
+ newState = updater(prevState);
139
+ } else {
140
+ newState = updater;
141
+ }
142
+
143
+ // Guardar nuevo estado
144
+ context.state = newState;
145
+
146
+ // Persistir si está habilitado
147
+ if (context.options.persist) {
148
+ this._saveToStorage(name, newState, context.options.storageKey);
149
+ }
150
+
151
+ // Emitir evento para notificar a los watchers
152
+ slice.events.emit(`context:${name}`, newState, prevState);
153
+
154
+ slice.logger.logInfo('ContextManager', `Contexto "${name}" actualizado`);
155
+ }
156
+
157
+ /**
158
+ * Merge a partial object into the context's state (first-level merge).
159
+ * `setState` REPLACES the whole state; `patch` keeps the existing fields and
160
+ * overrides only the keys you pass — the common "update one field" case.
161
+ * @param {string} name - Nombre del contexto
162
+ * @param {Object} partial - Campos a fusionar sobre el estado actual
163
+ * @returns {void}
164
+ *
165
+ * @example
166
+ * slice.context.patch('cart', { discount: 0.1 }); // items/total se conservan
167
+ */
168
+ patch(name, partial) {
169
+ if (!this.contexts.has(name)) {
170
+ slice.logger.logError('ContextManager', `El contexto "${name}" no existe`);
171
+ return;
172
+ }
173
+ if (!partial || typeof partial !== 'object' || Array.isArray(partial)) {
174
+ slice.logger.logError('ContextManager', `patch("${name}") requiere un objeto parcial`);
175
+ return;
176
+ }
177
+ return this.setState(name, (prev) => ({ ...prev, ...partial }));
178
+ }
179
+
180
+ /**
181
+ * Return a handle bound to a single context so you call its methods without
182
+ * repeating the name on every call.
183
+ * @param {string} name - Nombre del contexto
184
+ * @returns {{ get: Function, set: Function, patch: Function, watch: Function, bind: Function, has: Function, destroy: Function }}
185
+ *
186
+ * @example
187
+ * const cart = slice.context.use('cart');
188
+ * cart.get(); // estado actual
189
+ * cart.patch({ discount: 0.1 }); // merge (conserva el resto)
190
+ * cart.watch(this, (s) => this.render(s));
191
+ * // watch + render inicial en una línea:
192
+ * cart.bind(this, (count) => { this.$badge.textContent = count; }, (s) => s.items.length);
193
+ */
194
+ use(name) {
195
+ return {
196
+ get: () => this.getState(name),
197
+ set: (updater) => this.setState(name, updater),
198
+ patch: (partial) => this.patch(name, partial),
199
+ watch: (component, callback, selector = null) => this.watch(name, component, callback, selector),
200
+ // watch + an immediate call with the current value (initial render in one line)
201
+ bind: (component, callback, selector = null) => {
202
+ const state = this.getState(name);
203
+ callback(selector ? selector(state) : state);
204
+ return this.watch(name, component, callback, selector);
205
+ },
206
+ has: () => this.has(name),
207
+ destroy: () => this.destroy(name),
208
+ };
209
+ }
210
+
211
+ // ============================================
212
+ // WATCH (OBSERVAR CAMBIOS)
213
+ // ============================================
214
+
215
+ /**
216
+ * Observar cambios en un contexto
217
+ * @param {string} name - Nombre del contexto
218
+ * @param {HTMLElement} component - Componente Slice para auto-cleanup
219
+ * @param {(value: any) => void} callback - Funcion a ejecutar cuando cambia
220
+ * @param {(state: any) => any} [selector] - Opcional. Funcion para seleccionar campos
221
+ * @returns {string|null} subscriptionId
222
+ *
223
+ * @example
224
+ * // Observar todo el estado
225
+ * slice.context.watch('auth', this, (state) => {
226
+ * this.render(state);
227
+ * });
228
+ *
229
+ * // Observar un campo especifico
230
+ * slice.context.watch('auth', this, (name) => {
231
+ * this.$name.textContent = name;
232
+ * }, state => state.user.name);
233
+ *
234
+ * // Observar multiples campos
235
+ * slice.context.watch('auth', this, (data) => {
236
+ * this.$name.textContent = data.name;
237
+ * this.$avatar.src = data.avatar;
238
+ * }, state => ({
239
+ * name: state.user.name,
240
+ * avatar: state.user.avatar
241
+ * }));
242
+ *
243
+ * // Valores computados
244
+ * slice.context.watch('cart', this, (total) => {
245
+ * this.$total.textContent = `${total}`;
246
+ * }, state => state.items.reduce((sum, i) => sum + i.price, 0));
247
+ */
248
+ watch(name, component, callback, selector = null) {
249
+ if (!this.contexts.has(name)) {
250
+ slice.logger.logError('ContextManager', `El contexto "${name}" no existe`);
251
+ return null;
252
+ }
253
+
254
+ if (!component?.sliceId) {
255
+ slice.logger.logError('ContextManager', 'watch() requiere un componente Slice válido');
256
+ return null;
257
+ }
258
+
259
+ if (typeof callback !== 'function') {
260
+ slice.logger.logError('ContextManager', 'El callback debe ser una función');
261
+ return null;
262
+ }
263
+
264
+ // Guardar el valor anterior del selector para comparar
265
+ let lastSelectedValue = selector ? this._getSelectedValue(name, selector) : this.getState(name);
266
+
267
+ // Crear wrapper que aplica el selector y compara
268
+ const wrappedCallback = (newState, prevState) => {
269
+ if (selector) {
270
+ const newSelectedValue = this._applySelector(newState, selector);
271
+ const prevSelectedValue = this._applySelector(prevState, selector);
272
+
273
+ // Solo ejecutar si el valor seleccionado cambió
274
+ if (!this._isEqual(newSelectedValue, prevSelectedValue)) {
275
+ lastSelectedValue = newSelectedValue;
276
+ callback(newSelectedValue);
277
+ }
278
+ } else {
279
+ // Sin selector, siempre ejecutar
280
+ callback(newState);
281
+ }
282
+ };
283
+
284
+ // Suscribirse al evento del contexto
285
+ const subscriptionId = slice.events.subscribe(`context:${name}`, wrappedCallback, { component });
286
+
287
+ slice.logger.logInfo('ContextManager', `Watch registrado en "${name}" para ${component.sliceId}`);
288
+
289
+ return subscriptionId;
290
+ }
291
+
292
+ // ============================================
293
+ // UTILIDADES
294
+ // ============================================
295
+
296
+ /**
297
+ * Verificar si un contexto existe
298
+ * @param {string} name - Nombre del contexto
299
+ * @returns {boolean}
300
+ */
301
+ has(name) {
302
+ return this.contexts.has(name);
303
+ }
304
+
305
+ /**
306
+ * Eliminar un contexto
307
+ * @param {string} name - Nombre del contexto
308
+ * @returns {boolean}
309
+ */
310
+ destroy(name) {
311
+ if (!this.contexts.has(name)) {
312
+ return false;
313
+ }
314
+
315
+ const context = this.contexts.get(name);
316
+
317
+ // Limpiar storage si existe
318
+ if (context.options.persist) {
319
+ this._removeFromStorage(context.options.storageKey);
320
+ }
321
+
322
+ this.contexts.delete(name);
323
+
324
+ slice.logger.logInfo('ContextManager', `Contexto "${name}" eliminado`);
325
+
326
+ return true;
327
+ }
328
+
329
+ /**
330
+ * Obtener lista de todos los contextos
331
+ * @returns {string[]}
332
+ */
333
+ list() {
334
+ return Array.from(this.contexts.keys());
335
+ }
336
+
337
+ // ============================================
338
+ // MÉTODOS PRIVADOS
339
+ // ============================================
340
+
341
+ /**
342
+ * Aplicar selector al estado
343
+ */
344
+ _applySelector(state, selector) {
345
+ try {
346
+ return selector(state);
347
+ } catch (error) {
348
+ slice.logger.error('ContextManager', 'Error applying selector', error);
349
+ throw error;
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Obtener valor seleccionado del estado actual
355
+ */
356
+ _getSelectedValue(name, selector) {
357
+ const state = this.getState(name);
358
+ return this._applySelector(state, selector);
359
+ }
360
+
361
+ /**
362
+ * Comparar dos valores (shallow)
363
+ */
364
+ _isEqual(a, b) {
365
+ // Mismo valor primitivo o referencia
366
+ if (a === b) return true;
367
+
368
+ // Si alguno es null/undefined
369
+ if (a == null || b == null) return false;
370
+
371
+ // Si no son objetos, ya sabemos que son diferentes
372
+ if (typeof a !== 'object' || typeof b !== 'object') return false;
373
+
374
+ // Comparación shallow de objetos
375
+ const keysA = Object.keys(a);
376
+ const keysB = Object.keys(b);
377
+
378
+ if (keysA.length !== keysB.length) return false;
379
+
380
+ for (const key of keysA) {
381
+ if (a[key] !== b[key]) return false;
382
+ }
383
+
384
+ return true;
385
+ }
386
+
387
+ /**
388
+ * Cargar estado desde localStorage
389
+ */
390
+ _loadFromStorage(storageKey) {
391
+ try {
392
+ const data = localStorage.getItem(storageKey);
393
+ if (data) {
394
+ return JSON.parse(data);
395
+ }
396
+ } catch (error) {
397
+ slice.logger.error('ContextManager', `Error loading data from localStorage "${storageKey}"`, error);
398
+ }
399
+ return null;
400
+ }
401
+
402
+ _saveToStorage(name, state, storageKey) {
403
+ try {
404
+ localStorage.setItem(storageKey, JSON.stringify(state));
405
+ } catch (error) {
406
+ slice.logger.error('ContextManager', `Error saving "${name}" to localStorage`, error);
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Eliminar estado de localStorage
412
+ */
413
+ _removeFromStorage(storageKey) {
414
+ try {
415
+ localStorage.removeItem(storageKey);
416
+ } catch (error) {
417
+ slice.logger.warn('ContextManager', `Error removing "${storageKey}" from localStorage`, error);
418
+ }
419
+ }
420
+ }