diff --git a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.html b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.html
index 96f43aa..ac7b1b3 100644
--- a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.html
+++ b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.html
@@ -10,10 +10,12 @@
type="button"
class="attribute-selector__swatch"
[class.attribute-selector__swatch--selected]="hasSelectedOption(attribute, option)"
+ [class.attribute-selector__swatch--disabled]="!availableOptions()[attribute.id][option.id]"
[style.background-color]="getOptionSwatchColor(option)"
[attr.aria-label]="option.label"
[attr.aria-pressed]="hasSelectedOption(attribute, option)"
[title]="option.label"
+ [disabled]="!availableOptions()[attribute.id][option.id]"
(click)="selectAttributeOption(attribute, option)"
>
{{ option.label }}
@@ -23,7 +25,9 @@
type="button"
class="attribute-selector__text-option"
[class.attribute-selector__text-option--selected]="hasSelectedOption(attribute, option)"
+ [class.attribute-selector__text-option--disabled]="!availableOptions()[attribute.id][option.id]"
[attr.aria-pressed]="hasSelectedOption(attribute, option)"
+ [disabled]="!availableOptions()[attribute.id][option.id]"
(click)="selectAttributeOption(attribute, option)"
>
{{ option.label }}
diff --git a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.scss b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.scss
index 4d4a831..8812d34 100644
--- a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.scss
+++ b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.scss
@@ -45,6 +45,12 @@
0 0 0 2px rgba(255, 255, 255, 1),
0 0 0 3px var(--tenant-primary);
}
+
+ &--disabled {
+ opacity: 0.3;
+ cursor: not-allowed;
+ pointer-events: none;
+ }
}
&__text-option {
@@ -65,5 +71,13 @@
border-color: var(--tenant-primary);
color: #ffffff;
}
+
+ &--disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+ background-color: #f9f9f9;
+ color: #b0b0b0;
+ pointer-events: none;
+ }
}
}
diff --git a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.ts b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.ts
index d4f6bde..cd84bba 100644
--- a/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.ts
+++ b/src/app/features/store/components/product-attribute-selector/product-attribute-selector.component.ts
@@ -1,6 +1,7 @@
import {
ChangeDetectionStrategy,
Component,
+ computed,
effect,
input,
output,
@@ -32,6 +33,50 @@ export class ProductAttributeSelectorComponent {
protected readonly selectedAttributeOptions = signal>({});
+ protected readonly availableOptions = computed(() => {
+ const selections = this.selectedAttributeOptions();
+ const variants = this.variantsMap();
+ const attributes = this.attributes();
+
+ const availability: Record> = {};
+
+ for (const attribute of attributes) {
+ availability[attribute.id] = {};
+ for (const option of attribute.options) {
+ const optionNormalized = this.normalizeText(option.value || option.label);
+
+ const isAvailable = variants.some(variant => {
+ if (variant.stock <= 0) return false;
+
+ const variantAttrValue = this.getDefaultVariantAttributeValue(attribute, variant.attributes);
+ if (!variantAttrValue || this.normalizeText(variantAttrValue) !== optionNormalized) {
+ return false;
+ }
+
+ for (const otherAttr of attributes) {
+ if (otherAttr.id === attribute.id) continue;
+ const selectedOptionId = selections[otherAttr.id];
+ if (selectedOptionId !== undefined) {
+ const selectedOption = otherAttr.options.find(o => o.id === selectedOptionId);
+ if (selectedOption) {
+ const selectedNormalized = this.normalizeText(selectedOption.value || selectedOption.label);
+ const vAttrValue = this.getDefaultVariantAttributeValue(otherAttr, variant.attributes);
+ if (!vAttrValue || this.normalizeText(vAttrValue) !== selectedNormalized) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ });
+
+ availability[attribute.id][option.id] = isAvailable;
+ }
+ }
+
+ return availability;
+ });
+
constructor() {
effect(() => {
const attributes = this.attributes();
diff --git a/src/app/features/store/pages/product-detail-page/product-detail-page.component.html b/src/app/features/store/pages/product-detail-page/product-detail-page.component.html
index a919b5d..e927df7 100644
--- a/src/app/features/store/pages/product-detail-page/product-detail-page.component.html
+++ b/src/app/features/store/pages/product-detail-page/product-detail-page.component.html
@@ -69,6 +69,7 @@
type="button"
class="product-detail__quantity-button"
aria-label="Aumentar cantidad"
+ [disabled]="quantity() >= (selectedVariant()?.stock ?? 1)"
(click)="increaseQuantity()"
>
+
@@ -80,11 +81,12 @@
class="product-detail__cta"
variant="secondary"
type="button"
+ [disabled]="!selectedVariant()"
>
Agregar al carrito
-
+
Comprar
diff --git a/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts b/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts
index d4fdde9..e0ae664 100644
--- a/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts
+++ b/src/app/features/store/pages/product-detail-page/product-detail-page.component.ts
@@ -154,10 +154,14 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
protected onVariantChange(variant: ProductVariantMap | null): void {
this.selectedVariant.set(variant);
+ if (variant && this.quantity() > variant.stock) {
+ this.quantity.set(Math.max(1, variant.stock));
+ }
}
protected increaseQuantity(): void {
- this.quantity.update((current) => current + 1);
+ const currentStock = this.selectedVariant()?.stock ?? 1;
+ this.quantity.update((current) => current < currentStock ? current + 1 : current);
}
protected decreaseQuantity(): void {