# 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 ```typescript // 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 ```typescript // 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 ```typescript 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 ```typescript // 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 ```typescript 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 ```typescript // 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 ```typescript // 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) 3. **Refunds UI** (20-29 hrs) - Cumplimiento legal, UX completa 4. **Histórico Cambios Plan** (11-17 hrs) - Soporte + auditoría ### Fase 3 (2-3 semanas) 5. **Payment Intent Flow** (20-27 hrs) - Seguridad PCI-DSS 6. **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