diff --git a/src/app/core/layout/store-layout/store-header/store-header.component.html b/src/app/core/layout/store-layout/store-header/store-header.component.html
index 2a385e4..b8a3115 100644
--- a/src/app/core/layout/store-layout/store-header/store-header.component.html
+++ b/src/app/core/layout/store-layout/store-header/store-header.component.html
@@ -179,7 +179,7 @@
@if (checkoutRemainingTime(); as remainingTime) {
diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.html b/src/app/features/store/pages/checkout-page/checkout-page.component.html
index 57a7eb2..336fc3d 100644
--- a/src/app/features/store/pages/checkout-page/checkout-page.component.html
+++ b/src/app/features/store/pages/checkout-page/checkout-page.component.html
@@ -46,6 +46,17 @@
+ @if (checkoutRemainingTime(); as remainingTime) {
+
+ Tiempo restante de compra:
+ {{ remainingTime }}
+
+ }
+
{
@@ -129,6 +130,35 @@ describe('CheckoutPageComponent payment validation', () => {
return { fixture, component: fixture.componentInstance as any };
}
+ it('does not expose an active countdown until the purchase timing is loaded', async () => {
+ const countdown = TestBed.inject(CheckoutCountdownService);
+ countdown.synchronize({
+ expires_at: null,
+ expires_in_seconds: 600,
+ server_time: new Date().toISOString(),
+ });
+ checkoutServiceStub.getPurchase.mockResolvedValue({
+ id: 25,
+ status: 'created',
+ expires_at: '2026-08-27T15:10:00.000Z',
+ expires_in_seconds: 600,
+ server_time: '2026-08-27T15:00:00.000Z',
+ items: [],
+ subtotal: '0.00',
+ total: '0.00',
+ });
+ routeParamMap = convertToParamMap({ id: 25 });
+
+ const { component } = createComponent();
+
+ expect(component.checkoutRemainingTime()).toBeNull();
+
+ await Promise.resolve();
+
+ expect(countdown.remainingSeconds()).toBe(600);
+ expect(component.checkoutRemainingTime()).toBe('10:00');
+ });
+
it('polls QR after five seconds and navigates only when payment is paid', async () => {
checkoutServiceStub.getPurchase
.mockResolvedValueOnce({ status: 'pending_payment' })
diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.ts b/src/app/features/store/pages/checkout-page/checkout-page.component.ts
index 2802068..5a29c14 100644
--- a/src/app/features/store/pages/checkout-page/checkout-page.component.ts
+++ b/src/app/features/store/pages/checkout-page/checkout-page.component.ts
@@ -97,6 +97,28 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
protected readonly createdPurchase = signal(null);
protected readonly isLoadingPurchase = signal(true);
protected readonly checkoutStepIndex = signal(0);
+ protected readonly checkoutRemainingTime = computed(() => {
+ const purchase = this.createdPurchase();
+
+ if (
+ !purchase ||
+ (typeof purchase.expires_at !== 'string' &&
+ typeof purchase.expires_in_seconds !== 'number')
+ ) {
+ return null;
+ }
+
+ const remainingSeconds = this.checkoutCountdownService.remainingSeconds();
+
+ if (remainingSeconds === null) {
+ return null;
+ }
+
+ const minutes = Math.floor(remainingSeconds / 60);
+ const seconds = remainingSeconds % 60;
+
+ return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ });
protected readonly cartSubtotal = computed(() => {
const purchase = this.createdPurchase();
return purchase ? parseFloat(purchase.subtotal) : 0;
@@ -163,8 +185,6 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
}
ngOnInit(): void {
- this.checkoutCountdownService.clear();
-
const purchaseId = Number(this.route.snapshot.paramMap.get('id'));
if (!Number.isInteger(purchaseId) || purchaseId <= 0) {
void this.router.navigate(['/']);