'use client'; import { useEffect, useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle, Button } from '@horux/shared-ui'; import { CheckCircle2, Server, Cloud, Clock, ExternalLink } from 'lucide-react'; import { apiClient } from '@/lib/api/client'; import { subscribeMe, changeMyPlan, cancelMySubscription } from '@/lib/api/subscription'; type Despachoplan = 'trial' | 'business_control' | 'business_cloud' | 'mi_empresa' | 'mi_empresa_plus'; type PaidPlan = 'business_control' | 'business_cloud' | 'mi_empresa' | 'mi_empresa_plus'; interface SubscriptionInfo { status: string; plan: string; amount: number; currentPeriodStart: string | null; currentPeriodEnd: string | null; } interface PlanInfo { plan: Despachoplan; dbMode: string; trialEndsAt: string | null; isTrialActive: boolean; subscription: SubscriptionInfo | null; } function daysUntil(isoDate: string): number { const diff = new Date(isoDate).getTime() - Date.now(); return Math.max(0, Math.ceil(diff / (1000 * 60 * 60 * 24))); } type Frequency = 'monthly' | 'annual'; export default function PlanesDespachoPage() { const [planInfo, setPlanInfo] = useState(null); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(null); const [message, setMessage] = useState<{ kind: 'ok' | 'err'; text: string } | null>(null); // Toggle mensual/anual solo aplica a Mi Empresa y Mi Empresa+. Business // Control y Enterprise siempre se cobran anual. Default monthly para // bajar friction inicial; el descuento del 17% al pagar anual se // muestra como CTA secundario. const [meFreq, setMeFreq] = useState('monthly'); const [mePlusFreq, setMePlusFreq] = useState('monthly'); const fetchPlan = () => { apiClient.get('/despachos/me/plan') .then(res => setPlanInfo(res.data)) .catch(() => setPlanInfo(null)); }; useEffect(() => { setLoading(true); apiClient.get('/despachos/me/plan') .then(res => setPlanInfo(res.data)) .catch(() => setPlanInfo(null)) .finally(() => setLoading(false)); }, []); const currentPlan = planInfo?.plan ?? null; const trialDaysLeft = planInfo?.trialEndsAt ? daysUntil(planInfo.trialEndsAt) : 0; const hasPaidPlan = currentPlan === 'business_control' || currentPlan === 'business_cloud' || currentPlan === 'mi_empresa' || currentPlan === 'mi_empresa_plus'; /** Resuelve la frecuencia para un plan. Mi Empresa y Mi Empresa+ leen su * propio toggle; el resto (business_*) siempre annual. */ function frequencyFor(plan: PaidPlan): Frequency { if (plan === 'mi_empresa') return meFreq; if (plan === 'mi_empresa_plus') return mePlusFreq; return 'annual'; } async function handleContratar(plan: PaidPlan) { const frequency = frequencyFor(plan); setBusy(plan); setMessage(null); try { const result = await subscribeMe({ plan, frequency }); window.open(result.paymentUrl, '_blank'); setMessage({ kind: 'ok', text: 'Abrimos el pago de MercadoPago en otra pestana. Al completar regresa aqui.' }); } catch (err: any) { const msg: string = err?.response?.data?.message || err?.message || ''; if (/Ya existe una suscripci/i.test(msg)) { // Ya hay sub activa/pendiente en otro plan — tratar como cambio try { await changeMyPlan({ plan, frequency }); setMessage({ kind: 'ok', text: 'Cambio de plan programado para el final del periodo actual.' }); fetchPlan(); } catch (changeErr: any) { setMessage({ kind: 'err', text: changeErr?.response?.data?.message || changeErr?.message || 'Error al cambiar el plan' }); } } else { setMessage({ kind: 'err', text: msg || 'Error al contratar el plan' }); } } finally { setBusy(null); } } async function handleCancelar() { if (!confirm('Seguro que quieres cancelar la suscripcion? Conservaras acceso hasta el final del periodo pagado.')) return; setBusy('cancel'); setMessage(null); try { await cancelMySubscription(); setMessage({ kind: 'ok', text: 'Suscripcion cancelada. Acceso activo hasta el final del periodo actual.' }); fetchPlan(); } catch (err: any) { setMessage({ kind: 'err', text: err?.response?.data?.message || err?.message || 'Error al cancelar' }); } finally { setBusy(null); } } function ActiveBadge() { return (
Plan actual
); } /** * Toggle binario Mensual/Anual. La opción anual va resaltada con un * pequeño badge "−17%" para enfocar el descuento. */ function FrequencyToggle({ value, onChange }: { value: Frequency; onChange: (v: Frequency) => void }) { return (
); } function PlanActionButton({ plan }: { plan: PaidPlan }) { const isCurrent = currentPlan === plan; if (isCurrent) { return ; } const label = hasPaidPlan ? 'Cambiar a este plan' : 'Contratar'; return ( ); } return (

Planes Horux Despachos

Tres planes: Mi Empresa para usuarios individuales, Business Control y Enterprise para despachos.

{/* Trial banner */} {!loading && planInfo?.isTrialActive && (
Periodo de prueba activo — {trialDaysLeft} {trialDaysLeft === 1 ? 'dia restante' : 'dias restantes'}
)} {/* Banner de suscripción activa */} {!loading && planInfo?.subscription && hasPaidPlan && (() => { const sub = planInfo.subscription; const periodEndDate = sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd) : null; const fechaFormato = periodEndDate ? periodEndDate.toLocaleDateString('es-MX', { year: 'numeric', month: 'long', day: 'numeric' }) : null; const montoFmt = sub.amount.toLocaleString('es-MX'); return (
Suscripcion activa — { sub.plan === 'business_control' ? 'Business Control' : sub.plan === 'business_cloud' ? 'Enterprise' : sub.plan === 'mi_empresa_plus' ? 'Mi Empresa +' : 'Mi Empresa' }
Proxima renovacion{fechaFormato ? ` el ${fechaFormato}` : ''}: ${montoFmt}/año
); })()} {/* Toast de resultado */} {message && (
{message.text}
)}
{/* Mi Empresa */} {currentPlan === 'mi_empresa' && }
Mi Empresa

Para una sola empresa

${meFreq === 'monthly' ? '580' : '5,800'}

{meFreq === 'monthly' ? 'por mes (IVA incluido)' : 'por año (IVA incluido)'}

{meFreq === 'monthly' ? (

o $5,800/año (ahorras 17%)

) : (

Pagas 10 meses en lugar de 12

)}
1 RFC
3 usuarios
Hasta 1,000,000 CFDIs
Base de datos en la nube
Dashboard, CFDI, IVA/ISR, alertas, calendario
Reportes, conciliación, documentos, facturación
50 timbres/mes incluidos
{/* Mi Empresa + */} {currentPlan === 'mi_empresa_plus' && }
Mi Empresa +

Mi Empresa con API y Lolita IA

${mePlusFreq === 'monthly' ? '900' : '9,000'}

{mePlusFreq === 'monthly' ? 'por mes (IVA incluido)' : 'por año (IVA incluido)'}

{mePlusFreq === 'monthly' ? (

o $9,000/año (ahorras 17%)

) : (

Pagas 10 meses en lugar de 12

)}
1 RFC
3 usuarios
Hasta 1,000,000 CFDIs
Base de datos en la nube
Dashboard, CFDI, IVA/ISR, alertas, calendario
Reportes, conciliación, documentos, facturación
50 timbres/mes incluidos
API REST incluida
Lolita IA agente fiscal
{/* Business Control */} {currentPlan === 'business_control' ? : (
Más popular
) }
Business Control

Tu servidor, tus datos

$25,850

por año (IVA incluido)

+ $45/mes por cada RFC adicional sobre 100

Hasta 100 RFCs
Usuarios ilimitados
Hasta 1,000,000 CFDIs por contribuyente
Servidor local con backup
Control total de tus datos
Dashboard, CFDI, IVA/ISR, alertas, calendario
Reportes, conciliación, documentos, facturación, API
{/* Enterprise (key interna: business_cloud) */} {currentPlan === 'business_cloud' && }
Enterprise

Despachos grandes con alto volumen

$43,000

por año (IVA incluido)

+ $45/mes por cada RFC adicional sobre 100

Hasta 100 RFCs
Usuarios ilimitados
Hasta 3,000,000 CFDIs por contribuyente
Servidor local con backup
Backups automáticos en la nube
Dashboard, CFDI, IVA/ISR, alertas, calendario
Reportes, conciliación, documentos, facturación, API
Soporte prioritario
{/* Cancelar — visible solo si tiene plan pagable activo */} {hasPaidPlan && (
)}
); }