From c9d0e5ad01af10faa3802a87439d50c246cb178f Mon Sep 17 00:00:00 2001 From: ncoronel Date: Tue, 7 Jul 2026 16:40:04 -0300 Subject: [PATCH] refactor(cart): remove cart showcase component and related functionality --- .../reutilizables-test-page.component.html | 33 +- .../reutilizables-test-page.component.ts | 13 +- .../cart-copia/cart-copia.component.html | 138 -------- .../cart-copia/cart-copia.component.scss | 309 ------------------ .../cart-copia/cart-copia.component.spec.ts | 274 ---------------- .../cart-copia/cart-copia.component.ts | 214 ------------ 6 files changed, 2 insertions(+), 979 deletions(-) delete mode 100644 src/app/shared/components/cart-copia/cart-copia.component.html delete mode 100644 src/app/shared/components/cart-copia/cart-copia.component.scss delete mode 100644 src/app/shared/components/cart-copia/cart-copia.component.spec.ts delete mode 100644 src/app/shared/components/cart-copia/cart-copia.component.ts diff --git a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html index d1250b5..fd4cf52 100644 --- a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html +++ b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html @@ -455,38 +455,6 @@ } -
-
-
-

Cart copia

-

- Copia aislada del carrito actual para iterar el rediseƱo sin tocar el componente productivo. -

-
- - @if (!cartCopiaVisible) { - - Volver a mostrar cart copia - - } -
- - @if (cartCopiaVisible) { -
- -
- } -
@@ -685,3 +653,4 @@ + diff --git a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.ts b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.ts index 7b3af24..1f75211 100644 --- a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.ts +++ b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.ts @@ -163,18 +163,6 @@ export class ReutilizablesTestPageComponent { this.toastService.info('Se disparo el evento de cerrado del carrito mock.'); } - protected showCartCopia(): void { - this.cartCopiaVisible = true; - } - - protected hideCartCopia(): void { - this.cartCopiaVisible = false; - this.toastService.info('Se disparo el evento de cerrado del cart-copia mock.'); - } - - protected triggerCartCopiaCheckout(): void { - this.toastService.success('Se disparo la accion de compra del cart-copia mock.'); - } protected triggerToast(type: 'success' | 'danger' | 'info'): void { if (type === 'success') { @@ -273,3 +261,4 @@ export class ReutilizablesTestPageComponent { }); } } + diff --git a/src/app/shared/components/cart-copia/cart-copia.component.html b/src/app/shared/components/cart-copia/cart-copia.component.html deleted file mode 100644 index 29200e8..0000000 --- a/src/app/shared/components/cart-copia/cart-copia.component.html +++ /dev/null @@ -1,138 +0,0 @@ -
-
-

{{ title() }}

- - @if (showClose()) { - - } -
- -
-
- @for (item of items(); track item.productVariantId || item.product + item.discountedPrice; let idx = $index) { -
-
- @if (item.discountPercentage && item.discountPercentage > 0) { - -{{ item.discountPercentage }}% - } - - @if (item.imageUrl) { - - } @else { - - } -
- -
-
-

{{ item.product }}

- -
- @if (item.originalPrice !== null) { - {{ formatCurrency(item.originalPrice) }} - } - - {{ formatCurrency(item.discountedPrice) }} -
-
- -
- @for (attribute of item.attributes; track attribute.label + attribute.value) { -
- {{ attribute.label }}: - {{ attribute.value }} -
- } -
- -
-
- - - {{ getItemQuantity(item) }} - - -
- - -
-
-
- } -
-
- -
-
- Subtotal: - {{ formattedSubtotal() }} -
- -
- Descuento: - {{ formattedDiscount() }} -
- -
- -
- TOTAL: - {{ formattedTotal() }} -
- - @if (showActions()) { - - } -
-
diff --git a/src/app/shared/components/cart-copia/cart-copia.component.scss b/src/app/shared/components/cart-copia/cart-copia.component.scss deleted file mode 100644 index 3588bf9..0000000 --- a/src/app/shared/components/cart-copia/cart-copia.component.scss +++ /dev/null @@ -1,309 +0,0 @@ -:host { - display: block; - width: 100%; - height: 100%; -} - -.cart-copia { - display: grid; - grid-template-rows: auto minmax(0, 1fr) auto; - height: 100%; - border: 1px solid #f0f0f0; - border-radius: 10px; - overflow: hidden; - background: #ffffff; - box-shadow: 0 18px 40px rgba(24, 30, 48, 0.08); -} - -.cart-copia__header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 16px 16px 30px; - background: transparent; - border-bottom: 1px solid #f2f2f2; -} - -.cart-copia__title { - margin: 0; - color: #a6a6a6; - font-size: 13px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.cart-copia__close-button { - width: 24px; - height: 24px; - color: #ababab; - font-size: 16px; - transition: color 0.2s ease; - - &:hover { - color: #7b7b7b; - } -} - -.cart-copia__body { - min-height: 0; - background: #f8f8f8; -} - -.cart-copia__items { - display: grid; - gap: 18px; - height: 100%; - min-height: 0; - overflow-y: auto; - padding: 16px 14px 20px 30px; - scrollbar-color: #d1d1d1 transparent; - scrollbar-width: thin; - - &::-webkit-scrollbar { - width: 6px; - } - - &::-webkit-scrollbar-thumb { - background: #d1d1d1; - border-radius: 999px; - } -} - -.cart-copia__item { - display: grid; - grid-template-columns: 88px minmax(0, 1fr); - gap: 14px; - padding-bottom: 18px; - border-bottom: 1px solid #ffffff; -} - -.cart-copia__media { - position: relative; - width: 88px; - height: 98px; - border-radius: 5px 0 0 5px; - overflow: hidden; - background: #d8d8d8; - - img, - .cart-copia__media-placeholder { - display: block; - width: 100%; - height: 100%; - } - - img { - object-fit: cover; - } -} - -.cart-copia__media-placeholder { - background: linear-gradient(180deg, #d9d9d9, #cecece); -} - -.cart-copia__discount-badge { - position: absolute; - top: 0; - left: 0; - z-index: 1; - padding: 4px 8px 3px; - background: var(--tenant-primary); - color: #ffffff; - font-size: 8px; - font-weight: 500; - line-height: 1; -} - -.cart-copia__content { - display: grid; - gap: 10px; - min-width: 0; -} - -.cart-copia__top-row { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 12px; - align-items: start; -} - -.cart-copia__product { - margin: 0; - color: #7a7a7a; - font-size: 13px; - font-weight: 400; - line-height: 1.28; - text-transform: uppercase; -} - -.cart-copia__prices { - display: grid; - justify-items: end; - gap: 2px; - white-space: nowrap; - line-height: 1; -} - -.cart-copia__original-price { - color: #b2b2b2; - font-size: 10px; - font-weight: 300; - text-decoration: line-through; -} - -.cart-copia__discounted-price { - color: #909090; - font-size: 14px; - font-weight: 700; -} - -.cart-copia__attributes { - display: grid; - gap: 4px; -} - -.cart-copia__attribute { - color: #727272; - font-size: 12px; - line-height: 1.2; -} - -.cart-copia__attribute-label { - font-weight: 400; -} - -.cart-copia__attribute-value { - font-weight: 700; -} - -.cart-copia__item-actions { - display: flex; - align-items: center; - justify-content: flex-end; - gap: 10px; - margin-top: 2px; -} - -.cart-copia__quantity-selector { - display: inline-grid; - grid-template-columns: 16px 1fr 16px; - align-items: center; - width: 52px; - height: 23px; - border: 1px solid #cdcdcd; - border-radius: 4px; - background: #ffffff; -} - -.cart-copia__quantity-button { - width: 100%; - height: 100%; - background: transparent; - color: #6f6f6f; - font-size: 12px; - font-weight: 700; - line-height: 1; - - &:disabled { - color: #c4c4c4; - cursor: not-allowed; - } -} - -.cart-copia__quantity-value { - color: #666666; - font-size: 11px; - font-weight: 700; - line-height: 1; - text-align: center; - user-select: none; -} - -.cart-copia__remove-button { - width: 18px; - height: 18px; - color: #aaaaaa; - font-size: 14px; - transition: color 0.2s ease; - - &:hover { - color: #7b7b7b; - } -} - -.cart-copia__footer { - display: grid; - gap: 6px; - padding: 20px 30px 28px; - background: #ffffff; - border-top: 1px solid #f2f2f2; -} - -.cart-copia__summary-row, -.cart-copia__total-row { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 12px; -} - -.cart-copia__summary-label, -.cart-copia__summary-value { - color: #a9a9a9; - font-size: 13px; - line-height: 1.2; -} - -.cart-copia__summary-label { - font-weight: 300; -} - -.cart-copia__summary-value { - font-weight: 700; -} - -.cart-copia__divider { - height: 1px; - margin: 6px 0 2px; - background: #efefef; -} - -.cart-copia__total-row { - margin-top: 2px; -} - -.cart-copia__total-label, -.cart-copia__total-value { - color: #666666; - font-size: 16px; - font-weight: 700; - line-height: 1.2; -} - -.cart-copia__footer-app-button { - min-height: 42px; - font-size: 14px; -} - -@media (max-width: 520px) { - .cart-copia__header, - .cart-copia__footer { - padding-left: 18px; - padding-right: 18px; - } - - .cart-copia__items { - padding-left: 18px; - padding-right: 10px; - } - - .cart-copia__item { - grid-template-columns: 76px minmax(0, 1fr); - } - - .cart-copia__media { - width: 76px; - height: 88px; - } -} diff --git a/src/app/shared/components/cart-copia/cart-copia.component.spec.ts b/src/app/shared/components/cart-copia/cart-copia.component.spec.ts deleted file mode 100644 index 8339fde..0000000 --- a/src/app/shared/components/cart-copia/cart-copia.component.spec.ts +++ /dev/null @@ -1,274 +0,0 @@ -import '@angular/compiler'; -import { signal } from '@angular/core'; -import { TestBed, getTestBed } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { - BrowserTestingModule, - platformBrowserTesting -} from '@angular/platform-browser/testing'; -import { of, throwError } from 'rxjs'; -import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; - -import { CartService } from '../../../core/services/cart/cart.service'; -import { ModalService } from '../../../core/services/modal.service'; -import { ToastService } from '../../../core/services/toast.service'; -import { CartCopiaComponent } from './cart-copia.component'; - -describe('CartCopiaComponent', () => { - beforeAll(() => { - try { - getTestBed().initTestEnvironment( - BrowserTestingModule, - platformBrowserTesting() - ); - } catch { - // Test environment may already be initialized by another setup entrypoint. - } - }); - - afterEach(() => { - TestBed.resetTestingModule(); - }); - - it('opens a confirm delete modal before removing an item', async () => { - const removeItem = vi.fn().mockReturnValue(of({ message: 'Producto eliminado.' })); - const openConfirmDelete = vi.fn().mockReturnValue(of(true)); - - await TestBed.configureTestingModule({ - imports: [CartCopiaComponent], - providers: [ - { - provide: CartService, - useValue: { - cart: signal(null).asReadonly(), - updateItemQuantity: vi.fn(), - removeItem - } - }, - { - provide: ModalService, - useValue: { - openConfirmDelete - } - }, - { - provide: ToastService, - useValue: { - success: vi.fn(), - info: vi.fn(), - danger: vi.fn() - } - } - ] - }).compileComponents(); - - const fixture = TestBed.createComponent(CartCopiaComponent); - fixture.componentRef.setInput('items', [ - { - productVariantId: 10, - imageUrl: null, - product: 'Producto de prueba', - originalPrice: null, - discountedPrice: 1000, - discountPercentage: null, - attributes: [], - quantity: 1 - } - ]); - fixture.detectChanges(); - - fixture.debugElement.query(By.css('.cart-copia__remove-button')).triggerEventHandler('click'); - - expect(openConfirmDelete).toHaveBeenCalledWith({ - title: 'Eliminar producto', - content: - 'Se eliminara "Producto de prueba" del carrito. Esta accion no se puede deshacer.', - confirmLabel: 'Eliminar', - cancelLabel: 'Cancelar' - }); - expect(removeItem).toHaveBeenCalledWith(10); - }); - - it('does not remove the item when the delete confirmation is cancelled', async () => { - const removeItem = vi.fn().mockReturnValue(of({})); - const openConfirmDelete = vi.fn().mockReturnValue(of(false)); - - await TestBed.configureTestingModule({ - imports: [CartCopiaComponent], - providers: [ - { - provide: CartService, - useValue: { - cart: signal(null).asReadonly(), - updateItemQuantity: vi.fn(), - removeItem - } - }, - { - provide: ModalService, - useValue: { - openConfirmDelete - } - }, - { - provide: ToastService, - useValue: { - success: vi.fn(), - info: vi.fn(), - danger: vi.fn() - } - } - ] - }).compileComponents(); - - const fixture = TestBed.createComponent(CartCopiaComponent); - fixture.componentRef.setInput('items', [ - { - productVariantId: 10, - imageUrl: null, - product: 'Producto de prueba', - originalPrice: null, - discountedPrice: 1000, - discountPercentage: null, - attributes: [], - quantity: 1 - } - ]); - fixture.detectChanges(); - - fixture.debugElement.query(By.css('.cart-copia__remove-button')).triggerEventHandler('click'); - - expect(openConfirmDelete).toHaveBeenCalled(); - expect(removeItem).not.toHaveBeenCalled(); - }); - - it('optimistically updates quantity and rolls back on error', async () => { - vi.useFakeTimers(); - const updateItemQuantity = vi.fn().mockReturnValue(throwError(() => new Error('Error'))); - const danger = vi.fn(); - - await TestBed.configureTestingModule({ - imports: [CartCopiaComponent], - providers: [ - { - provide: CartService, - useValue: { - cart: signal(null).asReadonly(), - updateItemQuantity, - removeItem: vi.fn() - } - }, - { - provide: ModalService, - useValue: {} - }, - { - provide: ToastService, - useValue: { - success: vi.fn(), - info: vi.fn(), - danger - } - } - ] - }).compileComponents(); - - const fixture = TestBed.createComponent(CartCopiaComponent); - const component = fixture.componentInstance; - - const item = { - productVariantId: 10, - imageUrl: null, - product: 'Producto de prueba', - originalPrice: null, - discountedPrice: 1000, - discountPercentage: null, - attributes: [], - quantity: 1 - }; - - fixture.componentRef.setInput('items', [item]); - fixture.detectChanges(); - - const increaseButton = fixture.debugElement.query(By.css('.cart-copia__quantity-button--increase')); - increaseButton.triggerEventHandler('click'); - fixture.detectChanges(); - increaseButton.triggerEventHandler('click'); - fixture.detectChanges(); - - expect((component as any).getItemQuantity(item)).toBe(3); - - vi.advanceTimersByTime(1000); - fixture.detectChanges(); - - expect((component as any).getItemQuantity(item)).toBe(1); - expect(danger).toHaveBeenCalled(); - - vi.useRealTimers(); - }); - - it('optimistically updates quantity and clears override on success', async () => { - vi.useFakeTimers(); - const updateItemQuantity = vi.fn().mockReturnValue(of({ message: 'Success', data: {} })); - const success = vi.fn(); - - await TestBed.configureTestingModule({ - imports: [CartCopiaComponent], - providers: [ - { - provide: CartService, - useValue: { - cart: signal(null).asReadonly(), - updateItemQuantity, - removeItem: vi.fn() - } - }, - { - provide: ModalService, - useValue: {} - }, - { - provide: ToastService, - useValue: { - success, - info: vi.fn(), - danger: vi.fn() - } - } - ] - }).compileComponents(); - - const fixture = TestBed.createComponent(CartCopiaComponent); - const component = fixture.componentInstance; - - const item = { - productVariantId: 10, - imageUrl: null, - product: 'Producto de prueba', - originalPrice: null, - discountedPrice: 1000, - discountPercentage: null, - attributes: [], - quantity: 1 - }; - - fixture.componentRef.setInput('items', [item]); - fixture.detectChanges(); - - const increaseButton = fixture.debugElement.query(By.css('.cart-copia__quantity-button--increase')); - increaseButton.triggerEventHandler('click'); - fixture.detectChanges(); - increaseButton.triggerEventHandler('click'); - fixture.detectChanges(); - - expect((component as any).getItemQuantity(item)).toBe(3); - - vi.advanceTimersByTime(1000); - fixture.detectChanges(); - - expect((component as any).getItemQuantity(item)).toBe(1); - expect(success).toHaveBeenCalled(); - - vi.useRealTimers(); - }); -}); diff --git a/src/app/shared/components/cart-copia/cart-copia.component.ts b/src/app/shared/components/cart-copia/cart-copia.component.ts deleted file mode 100644 index d41e083..0000000 --- a/src/app/shared/components/cart-copia/cart-copia.component.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { ChangeDetectionStrategy, Component, computed, inject, input, output, signal } from '@angular/core'; -import { HttpErrorResponse } from '@angular/common/http'; -import { Subject, EMPTY } from 'rxjs'; -import { catchError, debounceTime, groupBy, mergeMap, switchMap, tap } from 'rxjs'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import type { CartItemAttribute } from '../cart-item/cart-item.component'; -import { ButtonComponent } from '../button/button.component'; -import { ModalService } from '../../../core/services/modal.service'; -import { CartService } from '../../../core/services/cart/cart.service'; -import { ToastService } from '../../../core/services/toast.service'; - -export interface CartCopiaItemMock { - productVariantId?: number; - imageUrl: string | null; - product: string; - originalPrice: number | null; - discountedPrice: number; - discountPercentage: number | null; - attributes: CartItemAttribute[]; - quantity: number; -} - -@Component({ - selector: 'app-cart-copia', - standalone: true, - imports: [ButtonComponent], - templateUrl: './cart-copia.component.html', - styleUrl: './cart-copia.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class CartCopiaComponent { - private readonly cartService = inject(CartService); - private readonly modalService = inject(ModalService); - private readonly toastService = inject(ToastService); - private readonly quantityUpdates$ = new Subject<{ productVariantId: number; quantity: number }>(); - - readonly title = input('CARRITO'); - readonly showClose = input(false); - readonly items = input([]); - readonly subtotal = input(0); - readonly discount = input(0); - readonly total = input(0); - readonly backgroundColor = input('#ffffff'); - readonly primaryActionLabel = input('Comprar'); - readonly secondaryActionLabel = input('Seguir comprando'); - readonly showActions = input(true); - - readonly closed = output(); - readonly primaryAction = output(); - readonly secondaryAction = output(); - - protected readonly quantityOverrides = signal>({}); - - constructor() { - this.quantityUpdates$.pipe( - groupBy(update => update.productVariantId), - mergeMap(group$ => group$.pipe( - debounceTime(1000), - switchMap(update => this.cartService.updateItemQuantity(update.productVariantId, update.quantity).pipe( - tap({ - next: (res) => { - const msg = res.message || 'Cantidad de producto actualizada.'; - this.toastService.success(msg); - this.clearOverride(update.productVariantId); - }, - error: (err: HttpErrorResponse) => { - console.error('Error updating cart quantity', err); - const msg = err.error?.message || 'Error al actualizar la cantidad del producto.'; - this.toastService.danger(msg); - this.clearOverride(update.productVariantId); - } - }), - catchError(() => EMPTY) - )) - )), - takeUntilDestroyed() - ).subscribe(); - } - - protected getItemQuantity(item: CartCopiaItemMock): number { - if (item.productVariantId !== undefined && this.quantityOverrides()[item.productVariantId] !== undefined) { - return this.quantityOverrides()[item.productVariantId]; - } - return item.quantity; - } - - protected increaseItemQuantity(index: number): void { - const item = this.items()[index]; - - if (!item) { - return; - } - - this.onItemQuantityChange(index, this.getItemQuantity(item) + 1); - } - - protected decreaseItemQuantity(index: number): void { - const item = this.items()[index]; - - if (!item) { - return; - } - - const nextQuantity = Math.max(1, this.getItemQuantity(item) - 1); - - if (nextQuantity !== this.getItemQuantity(item)) { - this.onItemQuantityChange(index, nextQuantity); - } - } - - private clearOverride(productVariantId: number): void { - this.quantityOverrides.update((overrides) => { - const copy = { ...overrides }; - delete copy[productVariantId]; - return copy; - }); - } - - protected onItemQuantityChange(index: number, newQuantity: number): void { - const mockItem = this.items()[index]; - const productVariantId = mockItem?.productVariantId; - if (productVariantId) { - this.quantityOverrides.update((overrides) => ({ - ...overrides, - [productVariantId]: newQuantity - })); - this.quantityUpdates$.next({ - productVariantId, - quantity: newQuantity - }); - } else { - const item = this.cartService.cart()?.items[index]; - if (item) { - this.quantityOverrides.update((overrides) => ({ - ...overrides, - [item.product_variant_id]: newQuantity - })); - this.quantityUpdates$.next({ - productVariantId: item.product_variant_id, - quantity: newQuantity - }); - } - } - } - - protected onItemRemove(index: number): void { - const target = this.resolveRemoveTarget(index); - - if (!target) { - return; - } - - this.modalService.openConfirmDelete({ - title: 'Eliminar producto', - content: `Se eliminara "${target.productName}" del carrito. Esta accion no se puede deshacer.`, - confirmLabel: 'Eliminar', - cancelLabel: 'Cancelar' - }).subscribe((confirmed) => { - if (confirmed) { - this.removeItem(target.productVariantId); - } - }); - } - - protected readonly formattedSubtotal = computed(() => this.formatCurrency(this.subtotal())); - protected readonly formattedDiscount = computed(() => this.formatCurrency(this.discount())); - protected readonly formattedTotal = computed(() => this.formatCurrency(this.total())); - - protected formatCurrency(value: number): string { - const rounded = Math.round(value); - const parts = rounded.toString().split('.'); - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, '.'); - return `$ ${parts.join(',')}`; - } - - private resolveRemoveTarget( - index: number - ): { productVariantId: number; productName: string } | null { - const mockItem = this.items()[index]; - const productVariantId = mockItem?.productVariantId; - - if (productVariantId) { - return { - productVariantId, - productName: mockItem.product - }; - } - - const item = this.cartService.cart()?.items[index]; - - if (!item) { - return null; - } - - return { - productVariantId: item.product_variant_id, - productName: item.product?.nombre ?? 'este producto' - }; - } - - private removeItem(productVariantId: number): void { - this.cartService.removeItem(productVariantId).subscribe({ - next: (res) => { - const msg = res.message || 'Producto eliminado del carrito.'; - this.toastService.info(msg); - }, - error: (err: HttpErrorResponse) => { - console.error('Error removing item from cart', err); - const msg = err.error?.message || 'Error al eliminar el producto del carrito.'; - this.toastService.danger(msg); - } - }); - } -}