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 @@ + + +
+ {@html iconHtml} +
+ + \ No newline at end of file diff --git a/src/lib/cmps/BtOption.svelte b/src/lib/cmps/BtOption.svelte index ee0940b..d0647ab 100644 --- a/src/lib/cmps/BtOption.svelte +++ b/src/lib/cmps/BtOption.svelte @@ -4,6 +4,7 @@ import {dataConfig} from "../data/DataConfig"; import {formatPrice} from "../utils/formats"; import {popupMessage} from "../data/PopsState.svelte"; + import Badget from "./Badget.svelte"; const imgPath : string = dataConfig.sources.imgValues @@ -76,35 +77,18 @@ > - {#if consultation.message} -
- ⚠ -
+ {/if} - {#if isSelected} -
- ✓ -
+ {/if} {#if optionValue?.thumb} - {optionValue.name} - {:else} - + {optionValue.name} {/if} @@ -112,29 +96,11 @@ {optionValue?.name || optionRef.id} - {#if isDisabled} -
- 🚫 -
- + {/if} - - - - {#if hasPrice}
@@ -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 @@ - - - - - - - \ 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 @@ -
+
{#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 @@ > -
+
\ 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 +

+
+ + +
+ Pavimento General +
+ + + + +
+ {#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} + {optionValue.name} + {/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} +
+
+
{view.name - - - {#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} - {optionValue.name} { - (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.