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.
325 lines
6.9 KiB
325 lines
6.9 KiB
# Arquitectura Reactiva - Documentación
|
|
|
|
## 🏗️ Estructura Híbrida
|
|
|
|
La arquitectura separa **estado agnóstico del framework** de los **adapters específicos**:
|
|
|
|
```
|
|
src/
|
|
├── state/ # ← Estado puro (sin framework)
|
|
│ ├── SelectionState.ts
|
|
│ ├── ValidationState.ts
|
|
│ └── ConfigurationState.ts
|
|
│
|
|
└── adapters/ # ← Adapters por framework
|
|
└── svelte/
|
|
├── stores/ # Wrappers Svelte
|
|
└── components/ # Componentes UI
|
|
```
|
|
|
|
---
|
|
|
|
## 📦 Capa 1: Estado Agnóstico (`/state`)
|
|
|
|
### `SelectionState.ts`
|
|
|
|
Gestiona qué opciones están seleccionadas.
|
|
|
|
**Responsabilidades:**
|
|
- Almacenar selecciones: `Record<AttrID, OptionID>`
|
|
- Notificar cambios via listeners
|
|
- Historial de cambios (undo/redo)
|
|
- Persistencia (toJSON/fromJSON)
|
|
|
|
**API:**
|
|
```typescript
|
|
const selection = new SelectionState();
|
|
|
|
// Establecer valor
|
|
selection.set('at:suelo', 'op:parquet');
|
|
|
|
// Obtener valor
|
|
selection.get('at:suelo'); // → 'op:parquet'
|
|
|
|
// Limpiar
|
|
selection.clear('at:suelo');
|
|
|
|
// Suscribirse a cambios
|
|
const unsubscribe = selection.subscribe((event) => {
|
|
console.log(`${event.attrId} cambió a ${event.newValue}`);
|
|
});
|
|
|
|
// Deshacer
|
|
selection.undo();
|
|
```
|
|
|
|
---
|
|
|
|
### `ValidationState.ts`
|
|
|
|
Gestiona validaciones y violaciones usando el `RuleEngine`.
|
|
|
|
**Responsabilidades:**
|
|
- Validar selecciones contra reglas
|
|
- Detectar violaciones (errores/warnings)
|
|
- Verificar valores permitidos/prohibidos
|
|
- Determinar atributos requeridos
|
|
|
|
**API:**
|
|
```typescript
|
|
const validation = new ValidationState();
|
|
|
|
// Configurar reglas
|
|
validation.setRules(catalog.rules);
|
|
|
|
// Validar estado
|
|
const result = validation.validate(selectionState);
|
|
|
|
console.log(result.isValid); // → boolean
|
|
console.log(result.violations); // → Violation[]
|
|
|
|
// Helpers
|
|
validation.isValueAllowed('at:suelo', 'op:marmol', state);
|
|
validation.getAllowedValues('at:suelo', state);
|
|
validation.isRequired('at:material', state);
|
|
```
|
|
|
|
---
|
|
|
|
### `ConfigurationState.ts`
|
|
|
|
**Orquestador principal** que coordina todo.
|
|
|
|
**Responsabilidades:**
|
|
- Gestionar catálogo y objeto
|
|
- Coordinar SelectionState + ValidationState
|
|
- Navegación (sección/vista actual)
|
|
- Auto-validación en cambios
|
|
- Persistencia completa
|
|
|
|
**API:**
|
|
```typescript
|
|
const config = new ConfigurationState({
|
|
catalog: CATALOGO_VIVIENDAS,
|
|
objectId: 'ob:apartamento',
|
|
autoValidate: true
|
|
});
|
|
|
|
// Selección
|
|
config.selectOption('at:suelo', 'op:parquet');
|
|
config.getSelection('at:suelo');
|
|
|
|
// Navegación
|
|
config.setCurrentSection('sc:salon');
|
|
config.setCurrentView('vw:front');
|
|
|
|
// Validación
|
|
config.validate();
|
|
config.isValid();
|
|
config.getViolationsForAttribute('at:suelo');
|
|
|
|
// Helpers
|
|
config.getAllowedValues('at:suelo');
|
|
config.isRequired('at:material');
|
|
|
|
// Persistencia
|
|
const saved = config.toJSON();
|
|
config.fromJSON(saved);
|
|
|
|
// Utils
|
|
config.reset();
|
|
config.undo();
|
|
```
|
|
|
|
---
|
|
|
|
## 🔌 Capa 2: Adapters Svelte (`/adapters/svelte`)
|
|
|
|
### `stores/configuration.ts`
|
|
|
|
Wrapper que convierte `ConfigurationState` en Svelte store.
|
|
|
|
**Características:**
|
|
- ✅ Reactivo automático (subscribe)
|
|
- ✅ API idéntica a ConfigurationState
|
|
- ✅ Derived stores para valores específicos
|
|
- ✅ Type-safe
|
|
|
|
**Uso:**
|
|
```typescript
|
|
import { createConfigurationStore } from '@/adapters/svelte/stores';
|
|
|
|
const config = createConfigurationStore({
|
|
catalog: CATALOGO_VIVIENDAS,
|
|
objectId: 'ob:apartamento'
|
|
});
|
|
|
|
// En componentes Svelte
|
|
$: selection = $config.selection;
|
|
$: isValid = $config.isValid;
|
|
$: currentSection = $config.currentSection;
|
|
|
|
// Acciones
|
|
config.selectOption('at:suelo', 'op:parquet');
|
|
config.setCurrentSection('sc:salon');
|
|
```
|
|
|
|
**Derived stores:**
|
|
```typescript
|
|
const currentSection = deriveCurrentSection(config);
|
|
const validation = deriveValidation(config);
|
|
const isValid = deriveIsValid(config);
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 Capa 3: Componentes UI (`/adapters/svelte/components`)
|
|
|
|
### `Configurator.svelte`
|
|
|
|
Componente principal de ejemplo.
|
|
|
|
**Features:**
|
|
- ✅ Navegación de secciones
|
|
- ✅ Selector de vistas
|
|
- ✅ Panel de atributos
|
|
- ✅ Preview visual
|
|
- ✅ Validación en tiempo real
|
|
- ✅ Undo/Reset
|
|
|
|
**Uso:**
|
|
```svelte
|
|
<script>
|
|
import Configurator from '@/adapters/svelte/components/Configurator.svelte';
|
|
import { CATALOGO_VIVIENDAS } from './fixtures';
|
|
</script>
|
|
|
|
<Configurator
|
|
catalog={CATALOGO_VIVIENDAS}
|
|
objectId="ob:apartamento"
|
|
/>
|
|
```
|
|
|
|
---
|
|
|
|
## 🔄 Flujo de Datos
|
|
|
|
```
|
|
User Action (click)
|
|
↓
|
|
Component Handler
|
|
↓
|
|
Store Action (config.selectOption)
|
|
↓
|
|
ConfigurationState
|
|
↓
|
|
SelectionState.set()
|
|
↓
|
|
Notifica listeners
|
|
↓
|
|
ValidationState.validate() (si autoValidate)
|
|
↓
|
|
Notifica cambio
|
|
↓
|
|
Svelte Store actualiza
|
|
↓
|
|
UI re-renderiza (reactive $config)
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ Ventajas de esta arquitectura
|
|
|
|
### 1. **Separación de concerns**
|
|
- Estado = Lógica de negocio (testeable sin UI)
|
|
- Adapter = Integración con framework
|
|
- Components = Solo presentación
|
|
|
|
### 2. **Framework agnostic**
|
|
El 90% del código está en `/state` y no depende de Svelte.
|
|
|
|
Añadir React es fácil:
|
|
```
|
|
/adapters/react/
|
|
└── hooks/
|
|
└── useConfiguration.ts ← Wrapper con useState/useEffect
|
|
```
|
|
|
|
### 3. **Testing simple**
|
|
```typescript
|
|
// Test estado SIN montar componentes
|
|
const config = new ConfigurationState({...});
|
|
config.selectOption('at:suelo', 'op:parquet');
|
|
expect(config.getSelection('at:suelo')).toBe('op:parquet');
|
|
```
|
|
|
|
### 4. **Type-safety completo**
|
|
Todo tipado con TypeScript. El editor te ayuda en cada paso.
|
|
|
|
### 5. **Reactivo por defecto**
|
|
Cambias el estado → UI se actualiza automáticamente.
|
|
|
|
---
|
|
|
|
## 📝 Ejemplo completo
|
|
|
|
```svelte
|
|
<script lang="ts">
|
|
import { createConfigurationStore } from '@/adapters/svelte/stores';
|
|
|
|
const config = createConfigurationStore({
|
|
catalog: MY_CATALOG,
|
|
objectId: 'ob:producto'
|
|
});
|
|
|
|
// Reactivo automático
|
|
$: selection = $config.selection;
|
|
$: isValid = $config.isValid;
|
|
$: violations = $config.validation?.violations || [];
|
|
</script>
|
|
|
|
<!-- UI reactiva -->
|
|
<div>
|
|
<h1>{$config.object.name.es}</h1>
|
|
|
|
<select on:change={(e) => config.selectOption('at:color', e.target.value)}>
|
|
<option value="">Seleccionar color</option>
|
|
<option value="op:rojo">Rojo</option>
|
|
<option value="op:azul">Azul</option>
|
|
</select>
|
|
|
|
{#if !isValid}
|
|
<div class="errors">
|
|
{#each violations as v}
|
|
<p>{v.rule.message.es}</p>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
<button disabled={!isValid}>
|
|
Confirmar
|
|
</button>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 Próximos pasos
|
|
|
|
1. ✅ Estado agnóstico implementado
|
|
2. ✅ Adapter Svelte implementado
|
|
3. ✅ Componente de ejemplo creado
|
|
4. ⏳ Implementar renderizado de imágenes
|
|
5. ⏳ Añadir precio dinámico
|
|
6. ⏳ Implementar más componentes (AttributePanel, ValidationPanel, etc.)
|
|
7. ⏳ Testing de stores
|
|
8. ⏳ Documentación de componentes
|
|
|
|
---
|
|
|
|
## 📚 Archivos clave
|
|
|
|
- `/state/ConfigurationState.ts` - Orquestador principal
|
|
- `/adapters/svelte/stores/configuration.ts` - Svelte store
|
|
- `/adapters/svelte/components/Configurator.svelte` - Componente ejemplo
|
|
- `/docs/ARCHITECTURE.md` - Este archivo |