# 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; // ✅ 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_}` | 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)