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.
338 lines
9.3 KiB
338 lines
9.3 KiB
# Sistema de Vistas - Integración Completa
|
|
|
|
## 📐 Conceptos clave
|
|
|
|
Cada **Section** tiene múltiples **Views** (vistas) que representan diferentes ángulos o perspectivas visuales de esa sección.
|
|
|
|
```
|
|
Section (ej: Salón)
|
|
├── View: Front (frontal)
|
|
├── View: Side (lateral)
|
|
└── View: Top (cenital)
|
|
```
|
|
|
|
## 🏗️ Estructura
|
|
|
|
### Section (actualizada)
|
|
|
|
```typescript
|
|
interface Section {
|
|
id: SectionID;
|
|
name: I18nString;
|
|
attrs: Attribute[];
|
|
|
|
// ✅ NUEVO: Vistas disponibles (SIEMPRE presente)
|
|
views: Record<ViewID, SectionView>;
|
|
|
|
// ✅ NUEVO: Vista por defecto (SIEMPRE presente)
|
|
defaultView: ViewID;
|
|
|
|
availability: SectionAvailability;
|
|
}
|
|
```
|
|
|
|
### SectionView
|
|
|
|
```typescript
|
|
interface SectionView {
|
|
id: ViewID; // 'vw:front', 'vw:side', 'vw:top'
|
|
name: I18nString;
|
|
description?: I18nString;
|
|
visualConfig: SectionVisualConfig; // Configuración ESPECÍFICA de esta vista
|
|
order?: number;
|
|
icon?: string;
|
|
}
|
|
```
|
|
|
|
## 📝 Ejemplo completo
|
|
|
|
```typescript
|
|
import type { Section, SectionView } from '@/types';
|
|
|
|
const VIEW_FRONT : ViewID = 'vw:front';
|
|
const VIEW_SIDE : ViewID = 'vw:side';
|
|
const VIEW_TOP : ViewID = 'vw:top';
|
|
|
|
const seccionSalon: Section = {
|
|
id: 'sc:salon',
|
|
name: { es: 'Salón', en: 'Living room' },
|
|
description: { es: 'Salón - comedor principal', en: 'Main living - dining room' },
|
|
|
|
attrs: [
|
|
{
|
|
id: 'at:suelo',
|
|
name: { es: 'Suelo', en: 'Flooring' },
|
|
type: 'dynamic',
|
|
dataType: 'reference',
|
|
defaultValue: 'op:suelo_ceramica',
|
|
options: [
|
|
{ optionId: 'op:suelo_ceramica' },
|
|
{ optionId: 'op:suelo_parquet' }
|
|
],
|
|
display: { uiVisible: true, affectsVisual: true, affectsPrice: true }
|
|
},
|
|
{
|
|
id: 'at:pared',
|
|
name: { es: 'Color paredes', en: 'Wall color' },
|
|
type: 'dynamic',
|
|
dataType: 'reference',
|
|
defaultValue: 'op:pared_blanco',
|
|
options: [
|
|
{ optionId: 'op:pared_blanco' },
|
|
{ optionId: 'op:pared_gris' }
|
|
],
|
|
display: { uiVisible: true, affectsVisual: true }
|
|
}
|
|
],
|
|
|
|
// ✅ VISTAS: Diferentes ángulos de la misma sección
|
|
views: {
|
|
[VIEW_FRONT]: {
|
|
id: VIEW_FRONT,
|
|
name: { es: 'Vista frontal', en: 'Front view' },
|
|
description: { es: 'Vista de frente del salón', en: 'Front view of living room' },
|
|
visualConfig: {
|
|
strategy: 'static_image',
|
|
// Template incluye {view} para diferenciar ángulos
|
|
imageUrlTemplate: '{basePath}/{section}/{view}/{at_suelo}/{at_pared}.jpg',
|
|
basePath: '/renders/vivienda',
|
|
globalAttrDependencies: ['at:calidad'],
|
|
sectionAttrDependencies: ['at:suelo', 'at:pared']
|
|
},
|
|
order: 1,
|
|
icon: 'camera-front'
|
|
},
|
|
|
|
[VIEW_SIDE]: {
|
|
id: VIEW_SIDE,
|
|
name: { es: 'Vista lateral', en: 'Side view' },
|
|
description: { es: 'Vista lateral del salón', en: 'Side view of living room' },
|
|
visualConfig: {
|
|
strategy: 'static_image',
|
|
imageUrlTemplate: '{basePath}/{section}/{view}/{at_suelo}.jpg',
|
|
basePath: '/renders/vivienda',
|
|
globalAttrDependencies: ['at:calidad'],
|
|
// Solo el suelo afecta esta vista
|
|
sectionAttrDependencies: ['at:suelo']
|
|
},
|
|
order: 2,
|
|
icon: 'camera-side'
|
|
},
|
|
|
|
[VIEW_TOP]: {
|
|
id: VIEW_TOP,
|
|
name: { es: 'Vista cenital', en: 'Top view' },
|
|
description: { es: 'Vista desde arriba', en: 'Top-down view' },
|
|
visualConfig: {
|
|
strategy: 'static_image',
|
|
imageUrlTemplate: '{basePath}/{section}/{view}/{at_suelo}/{at_muebles}.jpg',
|
|
basePath: '/renders/vivienda',
|
|
globalAttrDependencies: ['at:calidad'],
|
|
// Vista cenital muestra suelo y distribución de muebles
|
|
sectionAttrDependencies: ['at:suelo', 'at:muebles']
|
|
},
|
|
order: 3,
|
|
icon: 'camera-top'
|
|
}
|
|
},
|
|
|
|
// ✅ Vista por defecto (la primera que se muestra al usuario)
|
|
defaultView: VIEW_FRONT,
|
|
|
|
availability: { mode: 'required' }
|
|
};
|
|
```
|
|
|
|
## 🔄 Generación de URLs
|
|
|
|
### Placeholders en `imageUrlTemplate`
|
|
|
|
El template soporta estos placeholders:
|
|
|
|
| Placeholder | Reemplazado por | Ejemplo |
|
|
|-------------|----------------|---------|
|
|
| `{basePath}` | basePath de la config | `/renders/vivienda` |
|
|
| `{section}` | ID de sección sin prefijo | `salon` (de `sc:salon`) |
|
|
| `{view}` | ID de vista sin prefijo | `front` (de `vw:front`) |
|
|
| `{at_<id>}` | Valor del atributo normalizado | `parquet` (de `at:suelo` = `op:suelo_parquet`) |
|
|
|
|
### Ejemplo de resolución
|
|
|
|
**Estado:**
|
|
```typescript
|
|
const state = {
|
|
'at:suelo': 'op:suelo_parquet',
|
|
'at:pared': 'op:pared_gris',
|
|
'at:calidad': 'op:calidad_premium'
|
|
};
|
|
```
|
|
|
|
**Template:**
|
|
```
|
|
{basePath}/{section}/{view}/{at_suelo}/{at_pared}.jpg
|
|
```
|
|
|
|
**URL resultante para vista frontal:**
|
|
```
|
|
/renders/vivienda/salon/front/parquet/gris.jpg
|
|
```
|
|
|
|
**URL resultante para vista lateral:**
|
|
```
|
|
/renders/vivienda/salon/side/parquet.jpg
|
|
```
|
|
|
|
## 🎨 Uso en el UI
|
|
|
|
```typescript
|
|
// Obtener sección
|
|
const salon = apartamento.sections['sc:salon'];
|
|
|
|
// Listar todas las vistas disponibles
|
|
const vistasFront = Object.values(salon.views)
|
|
.sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
|
|
console.log(vistasFront);
|
|
// [
|
|
// { id: 'vw:front', name: { es: 'Vista frontal' }, ... },
|
|
// { id: 'vw:side', name: { es: 'Vista lateral' }, ... },
|
|
// { id: 'vw:top', name: { es: 'Vista cenital' }, ... }
|
|
// ]
|
|
|
|
// Vista activa (por defecto)
|
|
const vistaActual = salon.views[salon.defaultView];
|
|
|
|
// Cambiar vista
|
|
const nuevaVista = salon.views['vw:side'];
|
|
|
|
// Generar URL de la vista actual
|
|
const urlImagen = generarImagenUrl(
|
|
salon.id,
|
|
vistaActual.id,
|
|
vistaActual.visualConfig,
|
|
state
|
|
);
|
|
```
|
|
|
|
## 🏭 Estrategias de renderizado por vista
|
|
|
|
Cada vista puede tener su propia estrategia:
|
|
|
|
```typescript
|
|
views: {
|
|
'vw:front': {
|
|
id: 'vw:front',
|
|
name: { es: 'Frontal', en: 'Front' },
|
|
visualConfig: {
|
|
strategy: 'static_image', // ← Imagen estática
|
|
imageUrlTemplate: '...'
|
|
}
|
|
},
|
|
'vw:360': {
|
|
id: 'vw:360',
|
|
name: { es: 'Vista 360º', en: '360º view' },
|
|
visualConfig: {
|
|
strategy: 'three_d', // ← Modelo 3D interactivo
|
|
threeDConfig: {
|
|
modelUrl: '/models/salon.glb',
|
|
textureAttrs: [
|
|
{ attrId: 'at:suelo', scope: 'section' },
|
|
{ attrId: 'at:pared', scope: 'section' }
|
|
]
|
|
}
|
|
}
|
|
},
|
|
'vw:ar': {
|
|
id: 'vw:ar',
|
|
name: { es: 'Realidad Aumentada', en: 'AR' },
|
|
visualConfig: {
|
|
strategy: 'api_generated', // ← API externa
|
|
apiConfig: {
|
|
endpoint: 'https://ar.example.com/generate',
|
|
method: 'POST',
|
|
timeout: 10000
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 📱 Vistas contextuales
|
|
|
|
Las vistas pueden mostrarse condicionalmente:
|
|
|
|
```typescript
|
|
{
|
|
id: 'vw:detail_plumbing',
|
|
name: { es: 'Detalle fontanería', en: 'Plumbing detail' },
|
|
visualConfig: {
|
|
strategy: 'static_image',
|
|
imageUrlTemplate: '{basePath}/{section}/detail/{at_sanitario}.jpg'
|
|
},
|
|
// Nota: No hay campo 'visible' aquí porque las vistas
|
|
// siempre están disponibles. El UI decide si mostrarlas.
|
|
order: 10 // Ordenar al final
|
|
}
|
|
```
|
|
|
|
## 🔑 Claves de diseño
|
|
|
|
### 1. Cada sección DEBE tener al menos una vista
|
|
|
|
```typescript
|
|
// ❌ MAL
|
|
views: {}
|
|
|
|
// ✅ BIEN
|
|
views: {
|
|
'vw:default': {
|
|
id: 'vw:default',
|
|
name: { es: 'Vista principal', en: 'Main view' },
|
|
visualConfig: { /* ... */ }
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. defaultView DEBE existir en views
|
|
|
|
```typescript
|
|
// ❌ MAL
|
|
views: { 'vw:front': { /* ... */ } },
|
|
defaultView: 'vw:side' // ← No existe!
|
|
|
|
// ✅ BIEN
|
|
views: {
|
|
'vw:front': { /* ... */ },
|
|
'vw:side': { /* ... */ }
|
|
},
|
|
defaultView: 'vw:front' // ← Existe
|
|
```
|
|
|
|
### 3. Los atributos que afectan CUALQUIER vista deben estar en dependencies
|
|
|
|
```typescript
|
|
// Vista frontal usa: suelo, pared
|
|
// Vista lateral usa: suelo
|
|
// Vista cenital usa: suelo, muebles
|
|
|
|
// En la sección, marca affectsVisual: true para todos
|
|
attrs: [
|
|
{ id: 'at:suelo', display: { affectsVisual: true } }, // ✅ Afecta 3 vistas
|
|
{ id: 'at:pared', display: { affectsVisual: true } }, // ✅ Afecta 1 vista
|
|
{ id: 'at:muebles', display: { affectsVisual: true } } // ✅ Afecta 1 vista
|
|
]
|
|
```
|
|
|
|
## 🎯 Ventajas del sistema de vistas
|
|
|
|
✅ **Flexibilidad**: Cada vista puede tener diferente estrategia de renderizado
|
|
✅ **Optimización**: Solo cargar assets de la vista activa
|
|
✅ **Claridad**: Separación explícita de ángulos/perspectivas
|
|
✅ **Escalabilidad**: Añadir nuevas vistas sin modificar la estructura
|
|
✅ **UX**: Usuario puede cambiar de vista según necesidad
|
|
|
|
## 📚 Recursos
|
|
|
|
- [Modelo completo](./MODEL.md)
|
|
- [Visual Rendering](./RENDERING.md)
|
|
- [URL Builder](./URL_BUILDER.md) |