trading-platform-frontend-v2/src/modules/payments/OQI-005-GAPS.md
Adrian Flores Cortes 42d18759b5 feat: Implement BLOCKER-001 proactive refresh + E2E video tests (frontend)
BLOCKER-001: Token Refresh Improvements (FASE 4 frontend)
- Proactive refresh scheduler: refresh 5min before token expiry
- Multi-tab synchronization with BroadcastChannel API
- Automatic scheduling on X-Token-Expires-At header reception
- Background token refresh to prevent user interruption

E2E Tests: Video Upload Module (frontend - 62 tests)
- Suite 1: Form tests (27 tests) - 3-step wizard validation
- Suite 2: Service tests (20 tests) - Multipart upload logic
- Suite 3: Integration tests (15 tests) - Complete flow validation

Test infrastructure:
- vitest.config.ts (NEW) - Vitest configuration with jsdom
- src/__tests__/setup.ts (NEW) - Global test setup and mocks
- Updated payments-stripe-elements.test.tsx to use Vitest (vi.mock)

Changes:
- apiClient.ts: Proactive refresh scheduler + BroadcastChannel sync
- payments-stripe-elements.test.tsx: Migrated from Jest to Vitest

Tests created:
- video-upload-form.test.tsx (27 tests) - Component validation
- video-upload-service.test.ts (20 tests) - Service logic validation
- video-upload-integration.test.tsx (15 tests) - Integration flow

Additional documentation:
- Module README.md files for assistant, auth, education, investment, payments, portfolio, trading
- Investment module: Analysis, contracts, gaps, delivery documentation
- Payments module: Stripe integration, wallet specification

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 01:44:40 -06:00

18 KiB

OQI-005: Análisis de Gaps - Funcionalidades Faltantes

Módulo: OQI-005 (pagos-stripe) Fecha: 2026-01-25 Criticidad: Las siguientes features no están implementadas pero serían valiosas para UX completa


Gap 1: Refunds UI (Devoluciones)

Prioridad: Alta Complejidad: Media Impacto: UX, Legal, Soporte

Descripción del Gap

Actualmente no existe:

  • UI para iniciar devoluciones de pagos
  • Panel de historial de reembolsos procesados
  • Validación de período de devolución (ej: 30 días)
  • Estados de devolución (pending, processing, completed, failed)
  • Notificaciones al usuario cuando devolución se procesa

Casos de Uso

1. Usuario solicita reembolso de suscripción
   ├── Verifica si está dentro de período permitido (14 días)
   ├── Selecciona razón de devolución
   ├── Backend procesa via Stripe API
   ├── Estado se actualiza a "pending"
   └── Usuario notificado cuando se completa (1-3 días)

2. Usuario ve historial de reembolsos
   ├── Tabla en Billing.tsx → Nueva tab "Reembolsos"
   ├── Muestra:
   │   ├── Fecha de solicitud
   │   ├── Monto reembolsado
   │   ├── Motivo
   │   ├── Estado (pending/completed/failed)
   │   └── Método devolución (mismo método pago original)
   └── Filtros: status, fecha range

3. Administrador revisa reembolso rechazado
   ├── Dashboard de admin ver motivo rechazo
   ├── Opción de re-procesar
   └── Notificar usuario

Componentes Necesarios

// 1. RefundRequestModal (modal para solicitar devolución)
interface RefundRequestModalProps {
  isOpen: boolean;
  onClose: () => void;
  subscriptionId: string;
  daysRemaining: number;              // Días dentro período permitido
  refundableAmount: number;
  onSuccess?: () => void;
}

// 2. RefundList (tabla historial reembolsos)
interface RefundListProps {
  subscriptionId?: string;
  compact?: boolean;
  itemsPerPage?: number;
}

// 3. RefundDetail (modal detalle reembolso)
interface RefundDetailProps {
  refundId: string;
  onClose: () => void;
}

Endpoint Backend Requerido

POST /payments/refunds
├── Body: { subscriptionId, amount, reason, requestedAt }
├── Valida: Dentro de período permitido (30 días default)
├── Respuesta: { id, status, amount, reason, processedAt? }
└── Webhook: refund.completed → Actualizar wallet usuario

GET /payments/refunds
├── Query params: subscriptionId, status, dateRange
└── Respuesta: Paginated list

GET /payments/refunds/{id}
├── Detalle de reembolso
└── Historial de actualizaciones

DELETE /payments/refunds/{id}
├── Solo si status=pending
└── Cancelar devolución pendiente

Lógica de Negocio

Período Devolución: 14 días desde primera facturación
├── Free trial: +7 días adicionales
├── Payment failed: No aplica devolución
└── Upgrade/downgrade: Proporcional a días usados

Estados:
├── pending: Enviada a Stripe, sin procesar
├── processing: Stripe la está procesando
├── completed: Completada, fondos devueltos
├── failed: Rechazada por banco/Stripe
└── cancelled: Usuario canceló solicitud

Reglas:
├── 1 reembolso/cliente/30 días (máximo)
├── Monto: hasta 100% de suscripción
└── Notificación: Email cuando se complete

Estimación Esfuerzo

  • Backend: 8-12 horas (Stripe API integration, webhook, DB)
  • Frontend UI: 6-8 horas (modal, tabla, detalle)
  • Testing: 4-6 horas
  • Documentación: 2-3 horas
  • Total: ~20-29 horas

Gap 2: Histórico de Cambios de Plan

Prioridad: Media Complejidad: Baja Impacto: UX, Auditoría, Soporte

Descripción del Gap

Actualmente no existe:

  • Visualización del historial de cambios de plan
  • Fechas de cambio y razones
  • Comparativa plan anterior vs nuevo
  • Créditos/cargos aplicados en cada cambio
  • Timeline visual de evolución de suscripción

Casos de Uso

1. Usuario revisa su historial de planes
   ├── Nueva tab en Billing: "Historial de Planes"
   ├── Timeline/tabla mostrando:
   │   ├── Fecha cambio
   │   ├── Plan anterior → Plan nuevo
   │   ├── Precio anterior → Precio nuevo
   │   ├── Crédito prorrateo (si aplica)
   │   ├── Cargo adicional (si aplica)
   │   └── Razón del cambio (upgrade/downgrade/etc)
   └── Botones: Ver detalles, Descargar recibo

2. Usuario entiende su evolución de suscripción
   ├── Cuándo cambió de Free → Pro
   ├── Cuándo hizo upgrade Pro → Enterprise
   └── Visualizar inversión total en plataforma

3. Soporte consulta historial cliente
   ├── Dashboard admin con historia completa
   ├── Auditoría de cambios (quién, cuándo)
   └── Facilita resolución de disputas

Componentes Necesarios

// 1. PlanHistoryTimeline (visualización timeline)
interface PlanHistoryTimelineProps {
  subscriptionId: string;
  compact?: boolean;
}

// 2. PlanChangeDetail (modal detalle de cambio)
interface PlanChangeDetailProps {
  changeId: string;
  onClose: () => void;
}

Estructura de Datos

interface SubscriptionPlanChange {
  id: string;
  subscriptionId: string;
  planIdFrom: string;
  planNameFrom: string;
  planIdTo: string;
  planNameTo: string;
  changeType: 'upgrade' | 'downgrade' | 'lateral_change';
  billingCycleFrom: 'monthly' | 'yearly';
  billingCycleTo: 'monthly' | 'yearly';
  amountFrom: number;
  amountTo: number;
  proratedCredit: number;
  chargeAmount: number;
  netAmount: number;
  effectiveDate: string;
  reason?: string;
  initiatedBy: 'user' | 'admin' | 'system';
  invoiceId?: string;
  createdAt: string;
}

Endpoint Backend Requerido

GET /payments/subscription/changes
├── Query params: subscriptionId, limit, offset
├── Respuesta: Paginated list of changes
└── Incluye: amountBefore, amountAfter, credits, invoiceId

GET /payments/subscription/changes/{id}
├── Detalle cambio
├── Incluye: invoiceDetails si aplica
└── Historial de estados (si cambio fue procesado en fases)

Lógica de Negocio

Cambio de Plan:
├── Fecha: Inmediata (upgrade) o fin de período (downgrade)
├── Crédito: Prorrateo por días no usados del plan anterior
├── Cargo: Diferencia (si upgrade) o crédito (si downgrade)
├── Factura: Nueva línea en siguiente ciclo o invoice immediata
└── Notificación: Confirmación al usuario

Datos Calculados:
├── changeType: Compara precio plan anterior vs nuevo
├── Prorration: (daysRemaining / daysInPeriod) * planPrice
└── Timeline: Mostrar cuando cambios se aplicaron

Estimación Esfuerzo

  • Backend: 4-6 horas (queries, DB si no existe tabla)
  • Frontend UI: 4-6 horas (timeline, tabla, modal)
  • Testing: 2-3 horas
  • Documentación: 1-2 horas
  • Total: ~11-17 horas

Gap 3: Preview de Factura

Prioridad: Media Complejidad: Baja Impacto: UX, Confianza del Usuario

Descripción del Gap

Actualmente no existe:

  • Preview de factura ANTES de completar pago
  • Visualización de items que se facturarán
  • Cálculo claro de subtotal, impuestos, descuentos
  • Confirmación de direcciones de facturación
  • Mostrar si hay cambios desde checkout anterior

Casos de Uso

1. Usuario en checkout ve qué va a pagar
   ├── Antes de hacer click "Pagar"
   ├── Muestra:
   │   ├── Plan seleccionado
   │   ├── Precio
   │   ├── Período (1 mes, 1 año)
   │   ├── Subtotal
   │   ├── Impuestos calculados (si aplica)
   │   ├── Descuentos (cupones)
   │   ├── Total a pagar
   │   ├── Ciclo de facturación
   │   ├── Dirección de facturación
   │   └── Método de pago (últimos 4 dígitos)
   └── Botón: "Confirmar y Pagar"

2. Usuario cambia información de facturación
   ├── Actualiza dirección
   ├── Sistema recalcula impuestos
   ├── Preview se actualiza automáticamente
   └── Muestra cambio en total (si aplica)

3. Usuario aplica cupón
   ├── Ingresa código
   ├── Preview actualiza con nuevo total
   ├── Muestra descuento claramente
   └── Botón confirmar disponible

Componentes Necesarios

// 1. InvoicePreview (modal/drawer con preview factura)
interface InvoicePreviewProps {
  planId: string;
  billingCycle: 'monthly' | 'yearly';
  couponCode?: string;
  billingInfo?: BillingInfo;
  paymentMethodId?: string;
  onConfirm?: () => void;
  onCancel?: () => void;
}

// Integrado en CheckoutFlow (modal/step adicional)

Estructura de Datos

interface InvoicePreview {
  planId: string;
  planName: string;
  description: string;
  subtotal: number;
  tax: number;
  taxRate?: number;
  discount: number;
  discountReason?: string;
  total: number;
  currency: string;
  billingCycle: 'monthly' | 'yearly';
  itemizedBreakdown: {
    description: string;
    quantity: number;
    unitPrice: number;
    amount: number;
  }[];
  billingInfo: {
    name: string;
    email: string;
    address: string;
  };
  paymentMethod: {
    type: string;
    last4: string;
    brand: string;
  };
  nextBillingDate: string;
  estimatedTotal12Months?: number;  // Si yearly, show equiv annual
}

Endpoint Backend Requerido

POST /payments/invoice/preview
├── Body: {
│   planId,
│   billingCycle,
│   couponCode?,
│   billingInfo?,
│   paymentMethodId?
├── Valida: País (para impuestos), cupón (si existe)
├── Calcula: Impuestos según dirección billing
└── Respuesta: InvoicePreview completo

GET /payments/taxes/estimate
├── Query: country, state, zipcode
├── Respuesta: { taxRate, taxName }
└── Usado para cálculos en tiempo real

Lógica de Negocio

Cálculo de Impuestos:
├── Por país/estado
├── VAT en EU (21% típicamente)
├── Sales tax en USA (5-10% según estado)
├── GST en CA (5%)
├── Integración con TaxJar/Avalara (opcional)
└── Almacenar en factura final

Créditos (si aplica):
├── Trial period: Mostrar cuando se cobra
├── Prorated: Crédito plan anterior (si existe)
├── Coupon: Descuento aplicado
└── One-time: Bonificación admin

Preview Real-time:
├── Usuario actualiza dirección → Recalcula impuestos
├── Usuario escribe cupón → Valida y recalcula
├── Usuario cambia billing cycle → Recalcula todo
└── Debounce requests para no sobrecargar backend

Estimación Esfuerzo

  • Backend: 6-8 horas (cálculo impuestos, endpoints)
  • Frontend UI: 4-6 horas (modal, forms, updates)
  • Tax Integration: 2-4 horas (si usa servicio externo)
  • Testing: 3-4 horas
  • Documentación: 1-2 horas
  • Total: ~16-24 horas

Gap 4: Payment Intent Flow (Completo)

Prioridad: Alta (Seguridad) Complejidad: Alta Impacto: Seguridad, PCI-DSS Compliance

Descripción del Gap

Actualmente:

  • Usa Stripe Hosted Checkout (seguro, delegado)
  • NO implementa Payment Intent flow en cliente
  • Métodos de pago sin tokenización Stripe.js
  • NO soporta 3D Secure / SCA en cliente (delegado a backend)
  • NO maneja tarjetas declinadas gracefully

Casos de Uso

1. Usuario completa checkout con tarjeta nueva (Hosted Checkout)
   ├── Redirección a Stripe
   ├── Stripe maneja validación, 3DS, etc
   ├── Redirige back a CheckoutSuccess
   └── ✅ Funciona (implementado)

2. Usuario agrega método de pago desde Billing
   ├── Abre formulario PaymentMethodForm
   ├── ❌ Envía número de tarjeta SIN tokenizar
   ├── Backend debería rechazar (PCI risk)
   └── Debería usar Stripe.js Elements

3. Usuario paga con saved payment method
   ├── No aplica (solo checkout con hosted)
   └── Si se implementa: requiere Payment Intent

4. 3D Secure required
   ├── Backend detecta SCA required
   ├── Devuelve clientSecret
   ├── ❌ Frontend no maneja confirmación
   ├── Debería usar Stripe.js confirmCardPayment
   └── Mostrar modal de autenticación

Componentes Necesarios

// 1. StripeProvider (wrappear app)
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/js';

const stripePromise = loadStripe(STRIPE_PUBLIC_KEY);

// 2. PaymentMethodForm refactorizado
interface PaymentMethodFormProps {
  onSuccess: (paymentMethodId: string) => void;
  onCancel?: () => void;
  onError?: (error: string) => void;
  setAsDefault?: boolean;
}

// 3. PaymentIntentModal (para confirmar 3DS)
interface PaymentIntentModalProps {
  clientSecret: string;
  onSuccess: () => void;
  onError: (error: string) => void;
}

Cambios Necesarios

// BEFORE (Inseguro)
const handleSubmit = async (e) => {
  const result = await addPaymentMethod({
    type: 'card',
    card: {
      number: cardNumber,       // ❌ PCI risk
      exp_month: expiry.split('/')[0],
      exp_year: 2000 + parseInt(expiry.split('/')[1]),
      cvc: cvc,                 // ❌ PCI risk
    },
  });
};

// AFTER (Seguro con Stripe.js)
const { stripe, elements } = useStripe();

const handleSubmit = async (e) => {
  const cardElement = elements.getElement(CardElement);

  // Crear payment method en Stripe (tokenizado)
  const { error, paymentMethod } = await stripe.createPaymentMethod({
    type: 'card',
    card: cardElement,
  });

  if (!error) {
    // Enviar token, no tarjeta
    const result = await addPaymentMethod(paymentMethod.id);
  }
};

Endpoint Backend Requerido

POST /payments/methods
├── Body: { paymentMethodId, setAsDefault }
├── paymentMethodId: Token Stripe (ej: pm_1234567890)
├── Almacena en Stripe + DB
└── ✅ Compliant PCI-DSS

POST /payments/subscriptions/{id}/confirm-payment
├── Body: { clientSecret, paymentMethodId }
├── Si requiere SCA: Procesa autenticación
├── Respuesta: { success, paymentStatus }
└── Usado para 3D Secure flow

Estimación Esfuerzo

  • Backend: 4-6 horas (validar tokens, SCA handling)
  • Frontend Stripe.js: 8-10 horas (Elements, intent confirmation)
  • Testing (incluye SCA test): 6-8 horas
  • Documentación: 2-3 horas
  • Total: ~20-27 horas

Gap 5: Apple Pay / Google Pay

Prioridad: Baja Complejidad: Media Impacto: UX, Conversión (móvil)

Descripción del Gap

Actualmente no existe soporte para:

  • Apple Pay (en iOS/macOS)
  • Google Pay (en Android/Chrome)
  • Botones "Pay with X" en checkout

Estimación Esfuerzo

  • Backend: 2-4 horas (endpoint validation)
  • Frontend: 6-8 horas (Stripe Payment Request Button)
  • Testing: 4-6 horas (necesita dispositivos)
  • Total: ~12-18 horas

Gap 6: Retry Logic para Pagos Fallidos

Prioridad: Media Complejidad: Media Impacto: Retención, Ingresos

Descripción del Gap

Actualmente:

  • No hay reintentos automáticos de pagos fallidos
  • No hay notificación al usuario cuando falla
  • No hay UI para re-procesar pago manual
  • No hay webhook handling para payment_intent.payment_failed

Casos de Uso

1. Suscripción tiene status "past_due"
   ├── Payment falló (tarjeta rechazada, fondos insuficientes)
   ├── Sistema envía email al usuario
   ├── Usuario puede ver deuda pendiente en Billing
   ├── Usuario puede:
   │   ├── Actualizar método de pago
   │   └── Click "Reintentar pago"
   └── Stripe reintenta automáticamente (3 veces en 3 días)

2. Dashboard muestra estado "Pago Pendiente"
   ├── Badge rojo en SubscriptionCard
   ├── Info: "Tenemos un problema con tu pago"
   ├── Botones:
   │   ├── Actualizar método de pago
   │   └── Reintentar ahora
   └── Link a portal Stripe

Endpoints Requeridos

POST /payments/subscription/{id}/retry-payment
├── Reintenta el pago pendiente
├── Requiere valid payment method
└── Respuesta: { success, newStatus, nextRetryDate? }

GET /payments/failed-payments
├── Listar pagos fallidos del usuario
└── Respuesta: Array de intentos fallidos con razones

Estimación Esfuerzo

  • Backend: 4-6 horas (retry logic, webhooks)
  • Frontend: 3-4 horas (UI, forms)
  • Testing: 2-3 horas
  • Total: ~9-13 horas

Priorización Recomendada

Fase 1 (MVP - 2-3 semanas)

  1. Preview de Factura (16-24 hrs) - Mejor UX antes de pagar
  2. Retry Logic (9-13 hrs) - Aumenta retención

Fase 2 (3-4 semanas)

  1. Refunds UI (20-29 hrs) - Cumplimiento legal, UX completa
  2. Histórico Cambios Plan (11-17 hrs) - Soporte + auditoría

Fase 3 (2-3 semanas)

  1. Payment Intent Flow (20-27 hrs) - Seguridad PCI-DSS
  2. Apple Pay / Google Pay (12-18 hrs) - Conversión móvil

Impacto de No Implementar

Gap Riesgo Impacto
Refunds Legal/Cumplimiento Posibles multas/disputas
Historial Cambios Soporte Tickets más complejos
Preview Factura UX Menor confianza en checkout
Payment Intent Seguridad PCI-DSS non-compliant
Apple/Google Pay Conversión Pérdida clientes móviles
Retry Logic Ingresos Pérdida 5-10% suscriptores

Checklist de Implementación

Cuando se implementen estos gaps:

  • Crear rama feature separada por gap
  • Actualizar tipos TypeScript en payment.types.ts
  • Documentar endpoints en OQI-005-CONTRATOS-API.md
  • Agregar tests unitarios + integración
  • Actualizar paymentStore.ts con nuevos métodos
  • Crear nuevos componentes en components/payments/
  • Integrar en Billing.tsx con nuevas tabs
  • Validar con Stripe test keys
  • Documentar en nuevos archivos OQI-005-*.md
  • Rebasar a main una vez QA aprobado
  • Desplegar a producción con feature flags