diff --git a/angular.json b/angular.json
index c445018..7b19bb0 100644
--- a/angular.json
+++ b/angular.json
@@ -2,7 +2,8 @@
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
- "packageManager": "npm"
+ "packageManager": "npm",
+ "analytics": false
},
"newProjectRoot": "projects",
"projects": {
diff --git a/public/images/pantalon-scuba.png b/public/images/pantalon-scuba.png
new file mode 100644
index 0000000..61ffe1e
Binary files /dev/null and b/public/images/pantalon-scuba.png differ
diff --git a/public/images/zapatillas-running.png b/public/images/zapatillas-running.png
new file mode 100644
index 0000000..fbe1916
Binary files /dev/null and b/public/images/zapatillas-running.png differ
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 fa8f3ab..7d1373d 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
@@ -256,4 +256,31 @@
+
+
+
+
Reusable Component
+
Tarjetas de Producto (ProductCard)
+
+
+ Las tarjetas se adaptan al tamaño del contenedor padre. A continuación se presentan dentro de una grilla de Bootstrap.
+
+
+
+ @for (product of testProducts; track product.title) {
+
+ }
+
+
+
+
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 b212cf6..48e0ef3 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
@@ -1,11 +1,12 @@
import { Component, inject } from '@angular/core';
import { ButtonComponent } from '../../../../shared/components/button/button.component';
import { InputComponent } from '../../../../shared/components/input/input.component';
+import { ProductCardComponent } from '../../../../shared/components/product-card/product-card.component';
import { TenantService } from '../../../../core/services/tenant.service';
@Component({
selector: 'app-reutilizables-test-page',
- imports: [ButtonComponent, InputComponent],
+ imports: [ButtonComponent, InputComponent, ProductCardComponent],
templateUrl: './reutilizables-test-page.component.html',
styleUrl: './reutilizables-test-page.component.scss'
})
@@ -28,6 +29,35 @@ export class ReutilizablesTestPageComponent {
protected clearTrigger = 0;
protected editableDisabled = true;
+ protected readonly testProducts = [
+ {
+ title: 'Pantalón recto de scuba negro',
+ originalPrice: 95000,
+ discount: 20,
+ transferPrice: 74800,
+ imageUrl: '/images/pantalon-scuba.png'
+ },
+ {
+ title: 'Zapatillas de running azul',
+ originalPrice: 120000,
+ discount: 15,
+ transferPrice: 98000,
+ imageUrl: '/images/zapatillas-running.png'
+ },
+ {
+ title: 'Remera básica blanca (Sin Descuento)',
+ originalPrice: 32000,
+ discount: null,
+ transferPrice: 30000,
+ imageUrl: null
+ }
+ ];
+
+ protected onProductBuy(productTitle: string): void {
+ console.log(`Comprando: ${productTitle}`);
+ alert(`Comprando: ${productTitle}`);
+ }
+
protected onFileChange(file: File | null): void {
this.fileName = file?.name ?? '';
}
@@ -37,3 +67,4 @@ export class ReutilizablesTestPageComponent {
this.clearTrigger += 1;
}
}
+
diff --git a/src/app/shared/components/product-card/product-card.component.html b/src/app/shared/components/product-card/product-card.component.html
new file mode 100644
index 0000000..837e4a3
--- /dev/null
+++ b/src/app/shared/components/product-card/product-card.component.html
@@ -0,0 +1,50 @@
+
+
+
+ @if (imageUrl()) {
+
![]()
+ } @else {
+
+
+
+ }
+
+
+ @if (discount() && discount()! > 0) {
+
+ -{{ discount() }}%
+
+ }
+
+
+
+
+
+
{{ title() }}
+
+
+
+
+ {{ formattedDiscountedPrice() }}
+
+
+ @if (discount() && discount()! > 0) {
+ {{ formattedOriginalPrice() }}
+ }
+
+
+
+
+ {{ formattedTransferPrice() }}
+ con transferencia
+
+
+
+
+
+ {{ buttonText() }}
+
+
+
+
+
diff --git a/src/app/shared/components/product-card/product-card.component.scss b/src/app/shared/components/product-card/product-card.component.scss
new file mode 100644
index 0000000..6219253
--- /dev/null
+++ b/src/app/shared/components/product-card/product-card.component.scss
@@ -0,0 +1,76 @@
+:host {
+ display: block;
+ width: 100%;
+ height: 100%;
+}
+
+.product-card {
+ width: 100%;
+ height: 100%;
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.08) !important;
+ }
+
+ &__image-container {
+ width: 100%;
+ aspect-ratio: 1 / 1;
+ overflow: hidden;
+ }
+
+ &__discount-badge {
+ background-color: var(--tenant-primary) !important;
+ font-size: 13px;
+ font-weight: 325;
+ z-index: 10;
+ }
+
+ &__title {
+ font-size: 12px;
+ font-weight: 325;
+ letter-spacing: 0.5px;
+ line-height: 1.4;
+ }
+
+ &__price-discounted {
+ color: var(--tenant-primary) !important;
+ font-size: 24px;
+ font-weight: 425;
+ }
+
+
+ &__price-original {
+ font-size: 11px;
+ font-weight: 325;
+ color: #DDDDDD;
+ }
+
+ &__price-transfer {
+ font-size: 12px;
+ color: #6c757d;
+ }
+
+ &__price-transfer-value {
+ font-weight: 400;
+ }
+
+ &__price-transfer-label {
+ font-weight: 300;
+ }
+
+ &__action {
+ app-button {
+ display: block;
+ width: 100%;
+
+ ::ng-deep .btn {
+ width: 100%;
+ min-width: 100%;
+ border-radius: 8px;
+ }
+ }
+ }
+}
+
diff --git a/src/app/shared/components/product-card/product-card.component.ts b/src/app/shared/components/product-card/product-card.component.ts
new file mode 100644
index 0000000..33976c4
--- /dev/null
+++ b/src/app/shared/components/product-card/product-card.component.ts
@@ -0,0 +1,69 @@
+import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
+import { ButtonComponent } from '../button/button.component';
+
+@Component({
+ selector: 'app-product-card',
+ standalone: true,
+ imports: [ButtonComponent],
+ templateUrl: './product-card.component.html',
+ styleUrl: './product-card.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ProductCardComponent {
+ // Configurable inputs
+ readonly imageUrl = input(null);
+ readonly title = input('');
+ readonly originalPrice = input(0);
+ readonly discount = input(null);
+ readonly transferPrice = input(null);
+ readonly buttonText = input('Comprar');
+
+ // Interactive events
+ readonly buy = output();
+
+ // Computed discounted price
+ readonly discountedPrice = computed(() => {
+ const original = this.originalPrice();
+ const disc = this.discount();
+ if (!disc || disc <= 0) {
+ return original;
+ }
+ return original * (1 - disc / 100);
+ });
+
+ // Computed transfer price
+ readonly computedTransferPrice = computed(() => {
+ const givenTransfer = this.transferPrice();
+ if (givenTransfer !== null) {
+ return givenTransfer;
+ }
+ // Fallback if not specified: use the discounted price
+ return this.discountedPrice();
+ });
+
+ // Formatted string for discounted price: "$ 76.000"
+ readonly formattedDiscountedPrice = computed(() => {
+ return this.formatCurrency(this.discountedPrice());
+ });
+
+ // Formatted string for original price (if has discount): "$ 95.000"
+ readonly formattedOriginalPrice = computed(() => {
+ return this.formatCurrency(this.originalPrice());
+ });
+
+ // Formatted string for transfer price: "$ 74.800"
+ readonly formattedTransferPrice = computed(() => {
+ return this.formatCurrency(this.computedTransferPrice());
+ });
+
+ /**
+ * Helper method to format currency numbers to Argentine Pesos style "$ XX.XXX".
+ * This manual implementation is safe from runtime/SSR locale variations.
+ */
+ private 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(',')}`;
+ }
+}