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.
9.3 KiB
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)
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
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
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:
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
// 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:
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:
{
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
// ❌ MAL
views: {}
// ✅ BIEN
views: {
'vw:default': {
id: 'vw:default',
name: { es: 'Vista principal', en: 'Main view' },
visualConfig: { /* ... */ }
}
}
2. defaultView DEBE existir en views
// ❌ 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
// 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