diff --git a/public/img/hi/edificio.jpg b/public/img/hi/edificio.jpg
new file mode 100644
index 0000000..cfe0ae5
Binary files /dev/null and b/public/img/hi/edificio.jpg differ
diff --git a/src/lib/cmps/Badget.svelte b/src/lib/cmps/Badget.svelte
new file mode 100644
index 0000000..c19848a
--- /dev/null
+++ b/src/lib/cmps/Badget.svelte
@@ -0,0 +1,79 @@
+
+
+
@@ -145,21 +111,4 @@
{/if}
-
-
-
-
-
\ No newline at end of file
diff --git a/src/lib/cmps/BtOptionGeneral.svelte b/src/lib/cmps/BtOptionGeneral.svelte
deleted file mode 100644
index ee0940b..0000000
--- a/src/lib/cmps/BtOptionGeneral.svelte
+++ /dev/null
@@ -1,165 +0,0 @@
-
-
-
-
-
-
-
- {#if consultation.message}
-
- ⚠
-
- {/if}
-
-
-
- {#if isSelected}
-
- ✓
-
- {/if}
-
-
- {#if optionValue?.thumb}
-
- {:else}
-
- {/if}
-
-
-
- {optionValue?.name || optionRef.id}
-
-
-
-
- {#if isDisabled}
-
- 🚫
-
-
- {/if}
-
-
-
-
-
-
- {#if hasPrice}
-
-
- + {formatPrice(optionRef.amount)}
-
-
- {/if}
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/lib/cmps/BtViewAccordion.svelte b/src/lib/cmps/BtViewAccordion.svelte
index 6aec4d9..c83f569 100644
--- a/src/lib/cmps/BtViewAccordion.svelte
+++ b/src/lib/cmps/BtViewAccordion.svelte
@@ -12,7 +12,7 @@
-
+
openHandler()}
@@ -32,7 +32,7 @@
{#if isOpen}
-
+
{@render children()}
{/if}
diff --git a/src/lib/cmps/HotspotModal.svelte b/src/lib/cmps/HotspotModal.svelte
index b663d34..39c7b96 100644
--- a/src/lib/cmps/HotspotModal.svelte
+++ b/src/lib/cmps/HotspotModal.svelte
@@ -140,7 +140,7 @@
>
-
+
{#if hotspot.groupType === 'general'}
-
+
Opción general para toda la vivienda
{/if}
diff --git a/src/lib/cmps/PnGroup.svelte b/src/lib/cmps/PnGroup.svelte
index 40fb86b..1895af7 100644
--- a/src/lib/cmps/PnGroup.svelte
+++ b/src/lib/cmps/PnGroup.svelte
@@ -45,15 +45,6 @@
{/each}
-
-
-
\ No newline at end of file
diff --git a/src/lib/cmps/PnGroupGeneral.svelte b/src/lib/cmps/PnGroupGeneral.svelte
new file mode 100644
index 0000000..9c331cc
--- /dev/null
+++ b/src/lib/cmps/PnGroupGeneral.svelte
@@ -0,0 +1,54 @@
+
+
+
+
+
+ El pavimento de la vivienda es el mismo en todas las estancias, por lo que la opción elegida será la que se instale también en el resto de la vivienda.
+
+
+ {#each options as option (option.id) }
+
+ {@const isSelected = selectedValue === option.id}
+
+
+
+ {/each}
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/cmps/PnSelGeneral.svelte b/src/lib/cmps/PnSelGeneral.svelte
new file mode 100644
index 0000000..ec89534
--- /dev/null
+++ b/src/lib/cmps/PnSelGeneral.svelte
@@ -0,0 +1,88 @@
+
+
+
e.key === 'Enter' && handleClick()}
+>
+
+
+
+
+
+
+
+ Opciones Generales
+
+
+
+
+
+
+
+
+
+
+
+
+ {#each Object.entries(dataConfig.options.general) as [key, group]}
+ {@const currentOptionId = appState.getCurrentOption('general', key)}
+ {@const optionValue = currentOptionId ? appState.getOptionValue(currentOptionId) : null}
+
+
+
+ {group.name || group.id}
+
+
+ {optionValue?.name || currentOptionId || 'No seleccionado'}
+
+
+ {#if optionValue?.thumb}
+
+ {/if}
+
+ {/each}
+
+
+
+
+
+
diff --git a/src/lib/cmps/pnView.svelte b/src/lib/cmps/PnSelView.svelte
similarity index 50%
rename from src/lib/cmps/pnView.svelte
rename to src/lib/cmps/PnSelView.svelte
index e029bcf..c2292d9 100644
--- a/src/lib/cmps/pnView.svelte
+++ b/src/lib/cmps/PnSelView.svelte
@@ -1,7 +1,9 @@
e.key === 'Enter' && handleClick()}
>
+
+
+
+
+
+
+ {view.name || view.id}
+
+
+
+ {#if hasWarnings}
+
+ {/if}
+
+
+
-
-
- {#if hasWarnings}
-
- ⚠️ Revisar
-
- {/if}
-
-
-
- {view.name || view.id}
-
-
@@ -128,17 +73,30 @@
{@const currentOptionId = appState.getCurrentOption(view.id, group.id)}
{@const optionValue = currentOptionId ? appState.getOptionValue(currentOptionId) : null}
{@const requiresConsult = currentOptionId ? appState.requiresConsultation(view.id, group.id, currentOptionId) : { required: false }}
+ {@const optionRef = group.values.find(v => v.id === currentOptionId)}
+ {@const hasPrice = optionRef?.amount && typeof optionRef.amount === 'number' && optionRef.amount > 0}
{group.name || group.id}
-
+
+
{optionValue?.name || currentOptionId || 'No seleccionado'}
{#if requiresConsult.required}
- ⚠️
+ ⚠️
+ {/if}
+
+ {#if hasPrice}
+
+ {formatPrice(optionRef.amount)}
+
+ {:else if optionRef?.amount === dataConfig.texts.with_cost}
+
+ {dataConfig.texts.with_cost}
+
{/if}
@@ -152,12 +110,12 @@
{#if total > 0}
-
+
- Extras:
+ Total opciones
- {formatearPrecio(total)}
+ {formatPrice(total)}
diff --git a/src/lib/data/AppState.svelte.ts b/src/lib/data/AppState.svelte.ts
index f515bdc..70f6e25 100644
--- a/src/lib/data/AppState.svelte.ts
+++ b/src/lib/data/AppState.svelte.ts
@@ -1,6 +1,6 @@
import { dataConfig } from './DataConfig';
import type {DataConfig, Rules, OptionGroup, View} from './DataConfigTypes';
-import type { AppState } from './AppStateTypes';
+import type {AppState, Incidencia} from './AppStateTypes';
class DataConfigStore {
@@ -386,4 +386,155 @@ class DataConfigStore {
}
}
-export const appState = new DataConfigStore();
\ No newline at end of file
+
+export const appState = new DataConfigStore();
+
+
+
+
+// Función para obtener la URL de la imagen de la vista
+export function getViewImageUrl(viewId: string): string {
+ const parts: string[] = [viewId];
+
+ // Agregar opciones generales
+ Object.keys(dataConfig.options.general).forEach(genKey => {
+ const value = appState.selection.general[genKey];
+ if (value) parts.push(value);
+ });
+
+ // Agregar opciones específicas de la vista
+ const viewConfig = dataConfig.options.views[viewId];
+ if (viewConfig?.options) {
+ viewConfig.options.forEach((group) => {
+ const value = appState.selection.views[viewId]?.[group.id];
+ if (value) parts.push(value);
+ });
+ }
+
+ return appState.imgSrc + `${parts.join('_')}.jpg`;
+}
+
+// Función para calcular el total de la vista
+export function getViewTotal(view : View): number {
+ let total = 0;
+
+ for (const group of view.options) {
+ const currentOptionId = appState.getCurrentOption(view.id, group.id);
+ if (currentOptionId) {
+ const option = group.values.find(v => v.id === currentOptionId);
+ if (option?.amount && typeof option.amount === 'number') {
+ total += option.amount;
+ }
+ }
+ }
+
+ return total;
+}
+
+
+// Verificar si hay incidencias en esta vista
+export function hasIncidencias(view: View): boolean {
+ for (const group of view.options) {
+ const currentOptionId = appState.getCurrentOption(view.id, group.id);
+ if (currentOptionId) {
+ const consultaInfo = appState.requiresConsultation(view.id, group.id, currentOptionId);
+ if (consultaInfo.required) return true;
+ }
+ }
+ return false;
+}
+
+
+
+// Función para obtener las incidencias
+export function getIncidencias(): Record
{
+ const incidenciasPorView: Record = {};
+
+ for (const viewId of appState.getAllViewIds()) {
+ const view = dataConfig.options.views[viewId];
+
+ for (const group of view.options) {
+ const currentOptionId = appState.getCurrentOption(viewId, group.id);
+
+ if (currentOptionId) {
+ const consultaInfo = appState.requiresConsultation(viewId, group.id, currentOptionId);
+
+ if (consultaInfo.required) {
+ if (!incidenciasPorView[viewId]) {
+ incidenciasPorView[viewId] = [];
+ }
+
+ const optionValue = appState.getOptionValue(currentOptionId);
+
+ incidenciasPorView[viewId].push({
+ viewId,
+ viewName: view.name || view.id,
+ groupId: group.id,
+ groupName: group.name || group.id,
+ optionId: currentOptionId,
+ optionName: optionValue?.name || currentOptionId,
+ mensaje: consultaInfo.message || 'Esta opción requiere consulta'
+ });
+ }
+ }
+ }
+ }
+
+ return incidenciasPorView;
+}
+
+// Función para calcular el total de una vista
+export function getTotalView(viewId: string): number {
+ let total = 0;
+ const view = dataConfig.options.views[viewId];
+
+ for (const group of view.options) {
+ const currentOptionId = appState.getCurrentOption(viewId, group.id);
+ if (currentOptionId) {
+ const option = group.values.find(v => v.id === currentOptionId);
+ if (option?.amount && typeof option.amount === 'number') {
+ total += option.amount;
+ }
+ }
+ }
+
+ return total;
+}
+
+// Función para calcular el total general
+export function getTotalGeneral(): number {
+ let total = 0;
+ for (const viewId of appState.getAllViewIds()) {
+ total += getTotalView(viewId);
+ }
+ return total;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/data/AppStateTypes.ts b/src/lib/data/AppStateTypes.ts
index 3114963..029be5a 100644
--- a/src/lib/data/AppStateTypes.ts
+++ b/src/lib/data/AppStateTypes.ts
@@ -18,4 +18,17 @@ export interface AppState {
* }
*/
views: Record>;
+}
+
+
+
+// Tipos para incidencias
+export interface Incidencia {
+ viewId: string;
+ viewName: string;
+ groupId: string;
+ groupName: string;
+ optionId: string;
+ optionName: string;
+ mensaje: string;
}
\ No newline at end of file
diff --git a/src/lib/data/DataConfig.ts b/src/lib/data/DataConfig.ts
index b934a7d..bc0f085 100644
--- a/src/lib/data/DataConfig.ts
+++ b/src/lib/data/DataConfig.ts
@@ -470,7 +470,7 @@ export const dataConfig : DataConfig = {
{
groupId: 'alicatado',
groupType: 'view',
- x: 72,
+ x: 92,
y: 55,
},
],
@@ -625,5 +625,3 @@ export const dataConfig : DataConfig = {
}
-
-
diff --git a/src/lib/utils/uix.ts b/src/lib/utils/uix.ts
new file mode 100644
index 0000000..cb6345b
--- /dev/null
+++ b/src/lib/utils/uix.ts
@@ -0,0 +1,8 @@
+// uix.ts
+
+
+export function handleImageStyleError(e: Event) {
+ const target = e.currentTarget as HTMLImageElement;
+ target.onerror = null; // evita bucle
+ target.style.display = 'none';
+}
diff --git a/src/lib/views/VwConfig.svelte b/src/lib/views/VwConfig.svelte
index 4303cd8..e48ebe5 100644
--- a/src/lib/views/VwConfig.svelte
+++ b/src/lib/views/VwConfig.svelte
@@ -7,6 +7,7 @@
import BtViewAccordion from "../cmps/BtViewAccordion.svelte";
import {navigationState} from "../data/NavState.svelte";
import Hotspots from "../cmps/Hotspots.svelte";
+ import PnGroupGeneral from "../cmps/PnGroupGeneral.svelte";
let isLoading = $state(true);
@@ -50,42 +51,6 @@
-
-
-
@@ -97,27 +62,21 @@
-
-
-
-
-
-
-
+
{#each Object.values(dataConfig.options.general) as group (group.id)}
{/each}
diff --git a/src/lib/views/VwGallery.svelte b/src/lib/views/VwGallery.svelte
index c3dba5a..2989872 100644
--- a/src/lib/views/VwGallery.svelte
+++ b/src/lib/views/VwGallery.svelte
@@ -1,10 +1,8 @@
+
+
+
+
@@ -128,44 +67,131 @@
+
+
{#each appState.getAllViews() as view}
-
+
{/each}
+
+
+
+
+
+
+ Costes de personalización
+
+
+
+
+
+
+
+
+ En el resumen final de elecciones de cada vivienda se incluirá un concepto correspondiente a la elaboración de documentación técnica y gestión de la personalización , cuyo importe se determinará en función del grado de modificación solicitado por el cliente.
+
+
+
Los importes aplicables serán los siguientes:
+
+
+ Grado 0: 0,00 €
+ Grado 1: 500,00 €
+ Grado 2: 1.000,00 €
+
+
+
+ El grado asignado dependerá del alcance y complejidad de los cambios realizados respecto al proyecto estándar.
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if totalGeneral > -1}
+
+
+
+
+
+ Total Configuración
+
+
+
+ {#if hasIncidencias}
+
+ {/if}
+
+
+ {#each appState.getAllViews() as view}
+ {@const total = getTotalView(view.id)}
+ {#if total > 0}
+
+ {view.name || view.id}
+ {formatPrice(total)}
+
+ {/if}
+ {/each}
+
+
+
+
+ TOTAL
+ {formatPrice(totalGeneral)}
+
+
+
+
+ {/if}
+
+
{#if hasIncidencias}
-
-
-
-
- ⚠️
- Opciones que requieren consulta
+
+
+
+
+
+ ⚠️
+ Hay opciones seleccionadas que requieren consulta
+
+
+
{#each Object.entries(incidencias) as [viewId, listaInc]}
handleViewClick(viewId)}
onkeydown={(e) => e.key === 'Enter' && handleViewClick(viewId)}
>
-
+
{listaInc[0].viewName}
-
+
{listaInc.length} {listaInc.length === 1 ? 'opción' : 'opciones'}
{#each listaInc as inc}
-
+
- {inc.groupName}: {inc.optionName}
+ {inc.groupName}: {inc.optionName}
-
{inc.mensaje}
+
{inc.mensaje}
{/each}
@@ -173,86 +199,18 @@
{/each}
-
- {/if}
-
-
- {#if totalGeneral > 0}
-
-
-
-
-
- Total Extras
-
-
-
- {#each appState.getAllViews() as view}
- {@const total = getTotalView(view.id)}
- {#if total > 0}
-
- {view.name || view.id}
- {formatearPrecio(total)}
-
- {/if}
- {/each}
-
-
-
-
- TOTAL
- {formatearPrecio(totalGeneral)}
-
-
-
-
+
{/if}
-
- {#if showGeneralOptions}
-
-
-
- {dataConfig.texts.title_options_general || 'Opciones Generales'}
-
-
-
- {#each Object.entries(dataConfig.options.general) as [key, group]}
- {@const currentOptionId = appState.getCurrentOption('general', key)}
- {@const optionValue = currentOptionId ? appState.getOptionValue(currentOptionId) : null}
-
-
-
- {group.name || group.id}
-
-
- {optionValue?.name || currentOptionId || 'No seleccionado'}
-
-
- {#if optionValue?.thumb}
-
{
- (e.target as HTMLImageElement).style.display = 'none'; }}
- />
- {/if}
-
- {/each}
-
-
-
- {/if}
-
-
+
+
ℹ️ Las imágenes son orientativas y pueden no corresponder exactamente con el resultado final.
Los acabados están sujetos a disponibilidad.