From f2fb6d24c6bc199efb1d50c49b17e71cd9f9ac88 Mon Sep 17 00:00:00 2001 From: Carlos Narro Date: Sat, 30 May 2026 19:58:18 +0200 Subject: [PATCH] =?UTF-8?q?Add=20c=C3=A1lculo=20de=20trial=20y=20badge=20d?= =?UTF-8?q?e=20plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mvp/b2c/src/lib/billing/plan.ts | 19 +++++++++++++++++++ mvp/b2c/tests/billing/plan.test.ts | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 mvp/b2c/src/lib/billing/plan.ts create mode 100644 mvp/b2c/tests/billing/plan.test.ts diff --git a/mvp/b2c/src/lib/billing/plan.ts b/mvp/b2c/src/lib/billing/plan.ts new file mode 100644 index 0000000..b580057 --- /dev/null +++ b/mvp/b2c/src/lib/billing/plan.ts @@ -0,0 +1,19 @@ +export function trialDaysRemaining(trialEndsAt: Date | null, now: Date = new Date()): number { + if (!trialEndsAt) return 0; + const ms = trialEndsAt.getTime() - now.getTime(); + if (ms <= 0) return 0; + return Math.ceil(ms / (24 * 60 * 60 * 1000)); +} + +export function formatPlanBadge( + planNombre: string | null, + status: string, + trialEndsAt: Date | null, + now: Date = new Date() +): string { + const plan = planNombre ? `Plan ${planNombre}` : 'Sin plan'; + if (status === 'trial') { + return `${plan} · trial, ${trialDaysRemaining(trialEndsAt, now)} días restantes`; + } + return `${plan} · ${status}`; +} diff --git a/mvp/b2c/tests/billing/plan.test.ts b/mvp/b2c/tests/billing/plan.test.ts new file mode 100644 index 0000000..18e56c3 --- /dev/null +++ b/mvp/b2c/tests/billing/plan.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from 'vitest'; +import { trialDaysRemaining, formatPlanBadge } from '@/lib/billing/plan'; + +describe('plan', () => { + it('calcula días de trial restantes (redondeo hacia arriba, nunca negativo)', () => { + const now = new Date('2026-05-30T12:00:00Z'); + expect(trialDaysRemaining(new Date('2026-06-05T12:00:00Z'), now)).toBe(6); + expect(trialDaysRemaining(new Date('2026-05-30T18:00:00Z'), now)).toBe(1); + expect(trialDaysRemaining(new Date('2026-05-29T12:00:00Z'), now)).toBe(0); + expect(trialDaysRemaining(null, now)).toBe(0); + }); + + it('formatea el badge según estado', () => { + const now = new Date('2026-05-30T12:00:00Z'); + expect(formatPlanBadge('Pro', 'trial', new Date('2026-06-05T12:00:00Z'), now)) + .toBe('Plan Pro · trial, 6 días restantes'); + expect(formatPlanBadge('Pro', 'activo', null, now)).toBe('Plan Pro · activo'); + expect(formatPlanBadge(null, 'trial', new Date('2026-06-05T12:00:00Z'), now)) + .toBe('Sin plan · trial, 6 días restantes'); + }); +});