You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

389 lines
13 KiB

8 months ago
import { dataConfig } from './DataConfig';
import type {DataConfig, Rules, OptionGroup, View} from './DataConfigTypes';
import type { AppState } from './AppStateTypes';
class DataConfigStore {
imgSrc : string = dataConfig.sources.imgViewsHi
8 months ago
8 months ago
// ============================================
// ESTADO REACTIVO
// ============================================
selection = $state<AppState>({
currentViewId: '',
general: {},
views: {}
});
8 months ago
// ============================================
// DEBUGGING
// ============================================
private debugLog(method: string, data: any) {
console.log(`[AppState.${method}]`, {
timestamp: new Date().toISOString(),
currentViewId: this.selection.currentViewId,
...data
});
}
8 months ago
/*
8 months ago
// Método público para inspeccionar el estado completo
inspect() {
console.group('🔍 AppState Inspector');
console.log('Current View ID:', this.selection.currentViewId);
console.log('Current View:', this.currentView);
console.log('General Options:', this.selection.general);
console.log('All Views State:', this.selection.views);
console.log('Available Views:', Object.keys(dataConfig.options.views));
console.groupEnd();
return this.selection;
}
8 months ago
*/
8 months ago
// ============================================
// CONSTRUCTOR E INICIALIZACIÓN
// ============================================
constructor() {
this.init();
8 months ago
// Exponer en window para debug desde consola
if (typeof window !== 'undefined') {
(window as any).appState = this;
console.log('✅ appState disponible en window.appState');
8 months ago
console.log('🔍 Usa: window.appState.inspect() para ver el estado');
8 months ago
}
8 months ago
}
private init() {
const viewIds = Object.keys(dataConfig.options.views);
if (viewIds.length === 0) {
throw new Error('Configuración inválida: debe existir al menos una vista');
}
const state: AppState = {
currentViewId: viewIds[0],
general: {},
views: {}
};
// Inicializar generales con defaults
for (const [key, group] of Object.entries(dataConfig.options.general)) {
const g = group as OptionGroup;
state.general[key] = g.default;
}
// Inicializar vistas con defaults
Object.keys(dataConfig.options.views).forEach((viewId) => {
const view = dataConfig.options.views[viewId];
state.views[viewId] = {}
view.options.forEach((group: OptionGroup) => {
state.views[viewId][group.id] = group.default;
})
})
this.selection = state;
8 months ago
this.debugLog('init', {
initialViewId: state.currentViewId,
viewsCount: viewIds.length,
availableViews: viewIds
});
8 months ago
}
// ============================================
// ACCIONES PÚBLICAS (Cambios de Estado)
// ============================================
/** Cambiar de habitación/estancia */
setView(viewId: string) {
8 months ago
const previousViewId = this.selection.currentViewId;
8 months ago
if (!dataConfig.options.views[viewId]) {
8 months ago
console.error(`❌ Vista ${viewId} no existe`);
this.debugLog('setView', { error: 'View not found', viewId });
8 months ago
return;
}
8 months ago
8 months ago
this.selection.currentViewId = viewId;
8 months ago
this.debugLog('setView', {
from: previousViewId,
to: viewId,
success: true
});
8 months ago
}
/** Cambiar una opción (General o de Vista) */
setOption(viewId: string | 'general', groupId: string, valueId: string) {
if (viewId === 'general') {
if (!(groupId in dataConfig.options.general)) {
console.warn(`Grupo ${groupId} no existe en general`);
return;
}
8 months ago
const previousValue = this.selection.general[groupId];
8 months ago
this.selection.general[groupId] = valueId;
8 months ago
this.debugLog('setOption', {
type: 'general',
groupId,
from: previousValue,
to: valueId
});
8 months ago
this.revalidateAll();
} else {
if (!(viewId in this.selection.views)) {
console.warn(`Vista ${viewId} no existe`);
return;
}
if (!(groupId in this.selection.views[viewId])) {
console.warn(`Grupo ${groupId} no existe en vista ${viewId}`);
return;
}
8 months ago
const previousValue = this.selection.views[viewId][groupId];
8 months ago
this.selection.views[viewId][groupId] = valueId;
8 months ago
this.debugLog('setOption', {
type: 'view',
viewId,
groupId,
from: previousValue,
to: valueId
});
8 months ago
this.revalidateView(viewId);
}
}
/** Resetear todo o una vista específica */
reset(viewId?: string) {
if (viewId) {
const view = dataConfig.options.views[viewId];
if (!view) {
console.warn(`Vista ${viewId} no existe`);
return;
}
view.options.forEach((group: OptionGroup) => {
this.selection.views[viewId][group.id] = group.default;
});
8 months ago
this.debugLog('reset', { viewId, type: 'single' });
8 months ago
} else {
this.init();
8 months ago
this.debugLog('reset', { type: 'all' });
8 months ago
}
}
// ============================================
// GETTERS PÚBLICOS (Datos Derivados)
// ============================================
get currentView(): View {
const view = dataConfig.options.views[this.selection.currentViewId];
if (!view) {
8 months ago
console.error(`❌ Vista ${this.selection.currentViewId} no existe en la configuración`);
8 months ago
throw new Error(`Vista ${this.selection.currentViewId} no existe en la configuración`);
}
return view;
}
/** Nombre de la imagen de la vista ACTUAL */
get currentFileName() {
const viewId = this.selection.currentViewId;
if (!dataConfig.options.views[viewId]) {
console.error(`Vista ${viewId} no existe`);
return 'error.jpg';
}
const parts: string[] = [viewId];
// 1. Generales
Object.keys(dataConfig.options.general).forEach(genKey => {
parts.push(this.selection.general[genKey]);
});
// 2. Específicos de la vista actual
const viewConfig = dataConfig.options.views[viewId];
if (viewConfig?.options) {
viewConfig.options.forEach((group: OptionGroup) => {
const val = this.selection.views[viewId][group.id];
if (val) parts.push(val);
});
}
return this.imgSrc + `${parts.join('_')}.jpg`;
}
/** Obtener el OptionValue completo por ID */
getOptionValue(optionId: string) {
return dataConfig.options.values[optionId];
}
/** Obtener todas las opciones de un grupo con sus valores completos */
getGroupOptions(viewId: string | 'general', groupId: string) {
let group: OptionGroup | undefined;
if (viewId === 'general') {
group = dataConfig.options.general[groupId];
} else {
const view = dataConfig.options.views[viewId];
group = view?.options.find(g => g.id === groupId);
}
if (!group) return [];
return group.values.map(ref => ({
...ref,
...this.getOptionValue(ref.id),
isValid: this.isValid(ref.rules)
}));
}
/** Verificar si una opción requiere consulta */
requiresConsultation(viewId: string, groupId: string, optionId: string): {
required: boolean;
message?: string;
} {
const view = dataConfig.options.views[viewId];
const group = view?.options.find(g => g.id === groupId);
const option = group?.values.find(v => v.id === optionId);
return {
8 months ago
required: !!option?.warn || option?.amount === 'consultar',
8 months ago
message: option?.warn
};
}
/** Obtener el valor actual de una opción */
getCurrentOption(viewId: string | 'general', groupId: string): string | undefined {
if (viewId === 'general') {
return this.selection.general[groupId];
}
return this.selection.views[viewId]?.[groupId];
}
8 months ago
/** Obtener todas las vistas disponibles */
getAllViews(): View[] {
return Object.values(dataConfig.options.views);
}
/** Obtener los IDs de todas las vistas */
getAllViewIds(): string[] {
return Object.keys(dataConfig.options.views);
}
8 months ago
// ============================================
// VALIDACIÓN
// ============================================
/** Validar reglas de una opción */
isValid(rules?: Rules): boolean {
if (!rules) return true;
// Validar reglas generales
if (rules.general) {
for (const [key, constraint] of Object.entries(rules.general)) {
const currentVal = this.selection.general[key];
if (constraint.forbid?.includes(currentVal)) return false;
if (constraint.allow && !constraint.allow.includes(currentVal)) return false;
}
}
// Validar reglas de vistas
if (rules.views) {
for (const [viewId, groupConstraints] of Object.entries(rules.views)) {
for (const [groupId, constraint] of Object.entries(groupConstraints)) {
const currentVal = this.selection.views[viewId]?.[groupId];
if (!currentVal) continue;
if (constraint.forbid?.includes(currentVal)) return false;
if (constraint.allow && !constraint.allow.includes(currentVal)) return false;
}
}
}
return true;
}
// ============================================
// MÉTODOS PRIVADOS (Helpers Internos)
// ============================================
/** Revalidar todas las vistas */
private revalidateAll() {
8 months ago
this.debugLog('revalidateAll', { trigger: 'general option changed' });
8 months ago
Object.keys(dataConfig.options.views).forEach(vId => {
this.revalidateView(vId);
});
}
/** Revalidar solo una vista específica */
private revalidateView(viewId: string) {
const view = dataConfig.options.views[viewId];
if (!view) return;
8 months ago
const changes: any[] = [];
8 months ago
view.options.forEach((group: OptionGroup) => {
const currentId = this.selection.views[viewId][group.id];
8 months ago
const currentOpt = group.values.find(v => v.id === currentId);
// Si la opción actual no es válida
if (currentOpt?.rules && !this.isValid(currentOpt.rules)) {
// Buscar la primera opción válida en el grupo
const validOption = this.findFirstValidOption(group);
if (validOption) {
changes.push({
group: group.id,
from: currentId,
to: validOption,
reason: 'rule validation failed'
});
this.selection.views[viewId][group.id] = validOption;
} else {
// Si no hay ninguna opción válida, usar default como fallback
console.warn(`No se encontró ninguna opción válida para ${viewId}.${group.id}, usando default`);
changes.push({
group: group.id,
from: currentId,
to: group.default,
reason: 'no valid option found, using default'
});
this.selection.views[viewId][group.id] = group.default;
}
8 months ago
}
});
8 months ago
if (changes.length > 0) {
this.debugLog('revalidateView', { viewId, changes });
}
8 months ago
}
8 months ago
/** Encontrar la primera opción válida en un grupo */
private findFirstValidOption(group: OptionGroup): string | null {
// Primero intentar con el default si es válido
const defaultOpt = group.values.find(v => v.id === group.default);
if (defaultOpt && this.isValid(defaultOpt.rules)) {
return group.default;
}
// Si el default no es válido, buscar la primera opción válida
for (const opt of group.values) {
if (this.isValid(opt.rules)) {
return opt.id;
}
}
// Si no hay ninguna opción válida, retornar null
return null;
}
8 months ago
}
export const appState = new DataConfigStore();

Powered by TurnKey Linux.