feat(registro): agrega Mi Empresa y Mi Empresa+ a pagina de registro

This commit is contained in:
Horux Dev
2026-04-28 06:49:17 +00:00
parent 2ac8e4d055
commit 066c9cdb74
4 changed files with 131 additions and 26 deletions

View File

@@ -10,7 +10,8 @@ const signupSchema = z.object({
regimenFiscal: z.string().optional(), regimenFiscal: z.string().optional(),
codigoPostal: z.string().regex(/^\d{5}$/, 'Código postal inválido').optional(), codigoPostal: z.string().regex(/^\d{5}$/, 'Código postal inválido').optional(),
verticalProfile: z.enum(['CONTABLE', 'JURIDICO', 'ARQUITECTURA']), verticalProfile: z.enum(['CONTABLE', 'JURIDICO', 'ARQUITECTURA']),
plan: z.enum(['trial', 'business_control', 'business_cloud']).optional().default('trial'), plan: z.enum(['trial', 'business_control', 'business_cloud', 'mi_empresa', 'mi_empresa_plus']).optional().default('trial'),
frequency: z.enum(['monthly', 'annual']).optional().default('annual'),
}), }),
owner: z.object({ owner: z.object({
nombre: z.string().min(2, 'Nombre del owner requerido'), nombre: z.string().min(2, 'Nombre del owner requerido'),

View File

@@ -23,7 +23,7 @@ export async function signupDespacho(data: DespachoSignupRequest) {
data: { data: {
nombre: despacho.nombre, nombre: despacho.nombre,
rfc: tenantSlug.toUpperCase(), rfc: tenantSlug.toUpperCase(),
plan: 'enterprise', plan: (despacho.plan === 'trial' ? 'enterprise' : despacho.plan) as any,
databaseName: databaseName, databaseName: databaseName,
cfdiLimit: -1, cfdiLimit: -1,
usersLimit: -1, usersLimit: -1,
@@ -103,7 +103,7 @@ export async function signupDespacho(data: DespachoSignupRequest) {
const result2 = await subscriptionService.subscribe({ const result2 = await subscriptionService.subscribe({
tenantId: result.tenant.id, tenantId: result.tenant.id,
plan: data.despacho.plan as any, plan: data.despacho.plan as any,
frequency: 'annual', frequency: data.despacho.frequency ?? 'annual',
payerEmail: owner.email, payerEmail: owner.email,
}); });
paymentUrl = result2.paymentUrl; paymentUrl = result2.paymentUrl;

View File

@@ -6,10 +6,11 @@ import Link from 'next/link';
import { Button, Input, Label, Card, CardContent, CardHeader, CardTitle, cn } from '@horux/shared-ui'; import { Button, Input, Label, Card, CardContent, CardHeader, CardTitle, cn } from '@horux/shared-ui';
import { useAuthStore } from '@/stores/auth-store'; import { useAuthStore } from '@/stores/auth-store';
import { apiClient } from '@/lib/api/client'; import { apiClient } from '@/lib/api/client';
import { CheckCircle2, Server, Cloud, ArrowLeft, Clock } from 'lucide-react'; import { CheckCircle2, Server, Cloud, ArrowLeft, Clock, Zap } from 'lucide-react';
type VerticalProfile = 'CONTABLE' | 'JURIDICO' | 'ARQUITECTURA'; type VerticalProfile = 'CONTABLE' | 'JURIDICO' | 'ARQUITECTURA';
type PlanType = 'trial' | 'business_control' | 'business_cloud'; type PlanType = 'trial' | 'business_control' | 'business_cloud' | 'mi_empresa' | 'mi_empresa_plus';
type Frequency = 'monthly' | 'annual';
export default function RegisterDespachoPage() { export default function RegisterDespachoPage() {
const router = useRouter(); const router = useRouter();
@@ -17,6 +18,8 @@ export default function RegisterDespachoPage() {
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const [verticalProfile, setVerticalProfile] = useState<VerticalProfile | null>(null); const [verticalProfile, setVerticalProfile] = useState<VerticalProfile | null>(null);
const [selectedPlan, setSelectedPlan] = useState<PlanType | null>(null); const [selectedPlan, setSelectedPlan] = useState<PlanType | null>(null);
const [meFreq, setMeFreq] = useState<Frequency>('monthly');
const [mePlusFreq, setMePlusFreq] = useState<Frequency>('monthly');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [form, setForm] = useState({ const [form, setForm] = useState({
@@ -38,11 +41,16 @@ export default function RegisterDespachoPage() {
setLoading(true); setLoading(true);
setError(''); setError('');
try { try {
const frequency: Frequency | undefined =
selectedPlan === 'mi_empresa' ? meFreq :
selectedPlan === 'mi_empresa_plus' ? mePlusFreq :
undefined;
const { data } = await apiClient.post('/despachos/signup', { const { data } = await apiClient.post('/despachos/signup', {
despacho: { despacho: {
nombre: form.despachoNombre, nombre: form.despachoNombre,
verticalProfile, verticalProfile,
plan: selectedPlan, plan: selectedPlan,
frequency,
}, },
owner: { owner: {
nombre: form.ownerNombre, nombre: form.ownerNombre,
@@ -168,10 +176,10 @@ export default function RegisterDespachoPage() {
<span className="bg-primary text-primary-foreground rounded-full w-6 h-6 flex items-center justify-center font-bold">3</span> <span className="bg-primary text-primary-foreground rounded-full w-6 h-6 flex items-center justify-center font-bold">3</span>
</div> </div>
<h1 className="text-3xl font-bold">Elige tu plan</h1> <h1 className="text-3xl font-bold">Elige tu plan</h1>
<p className="text-muted-foreground mt-2">Todos los planes incluyen las mismas funcionalidades.</p> <p className="text-muted-foreground mt-2">Mi Empresa para individuales, Business para despachos.</p>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-6">
{/* Trial Gratuito */} {/* Trial Gratuito */}
<Card <Card
className={cn( className={cn(
@@ -202,6 +210,99 @@ export default function RegisterDespachoPage() {
</CardContent> </CardContent>
</Card> </Card>
{/* Mi Empresa */}
<Card
className={cn(
'cursor-pointer transition-all hover:shadow-lg',
selectedPlan === 'mi_empresa' && 'border-primary ring-2 ring-primary/20'
)}
onClick={() => setSelectedPlan('mi_empresa')}
>
<CardHeader className="text-center pb-2">
<div className="mx-auto bg-emerald-100 dark:bg-emerald-900 rounded-full p-3 w-fit mb-2">
<Cloud className="h-6 w-6 text-emerald-600 dark:text-emerald-400" />
</div>
<CardTitle className="text-xl">Mi Empresa</CardTitle>
<p className="text-sm text-muted-foreground">Para una sola empresa</p>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex bg-muted rounded-lg p-1 text-xs font-medium">
<button
type="button"
onClick={(e) => { e.stopPropagation(); setMeFreq('monthly'); }}
className={`flex-1 py-1.5 rounded-md transition-colors ${meFreq === 'monthly' ? 'bg-background shadow-sm text-foreground' : 'text-muted-foreground hover:text-foreground'}`}
>Mensual</button>
<button
type="button"
onClick={(e) => { e.stopPropagation(); setMeFreq('annual'); }}
className={`flex-1 py-1.5 rounded-md transition-colors flex items-center justify-center gap-1 ${meFreq === 'annual' ? 'bg-background shadow-sm text-foreground' : 'text-muted-foreground hover:text-foreground'}`}
>Anual <span className="text-emerald-600 dark:text-emerald-400 text-[10px] font-bold">17%</span></button>
</div>
<div className="text-center">
<div className="text-3xl font-bold">${meFreq === 'monthly' ? '580' : '5,800'}</div>
<p className="text-sm text-muted-foreground">{meFreq === 'monthly' ? 'por mes (IVA incluido)' : 'por año (IVA incluido)'}</p>
{meFreq === 'monthly' && <p className="text-xs text-muted-foreground mt-1">o $5,800/año (ahorras 17%)</p>}
</div>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>1 RFC</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>3 usuarios</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Hasta 1,000,000 CFDIs</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Base de datos en la nube</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Dashboard, CFDI, IVA/ISR, alertas</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>50 timbres/mes incluidos</span></div>
</div>
</CardContent>
</Card>
{/* Mi Empresa + */}
<Card
className={cn(
'cursor-pointer transition-all hover:shadow-lg relative',
selectedPlan === 'mi_empresa_plus' && 'border-primary ring-2 ring-primary/20'
)}
onClick={() => setSelectedPlan('mi_empresa_plus')}
>
<div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-primary text-primary-foreground text-xs px-3 py-1 rounded-full">
Más popular
</div>
<CardHeader className="text-center pb-2">
<div className="mx-auto bg-teal-100 dark:bg-teal-900 rounded-full p-3 w-fit mb-2">
<Zap className="h-6 w-6 text-teal-600 dark:text-teal-400" />
</div>
<CardTitle className="text-xl">Mi Empresa +</CardTitle>
<p className="text-sm text-muted-foreground">Con API y Lolita IA</p>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex bg-muted rounded-lg p-1 text-xs font-medium">
<button
type="button"
onClick={(e) => { e.stopPropagation(); setMePlusFreq('monthly'); }}
className={`flex-1 py-1.5 rounded-md transition-colors ${mePlusFreq === 'monthly' ? 'bg-background shadow-sm text-foreground' : 'text-muted-foreground hover:text-foreground'}`}
>Mensual</button>
<button
type="button"
onClick={(e) => { e.stopPropagation(); setMePlusFreq('annual'); }}
className={`flex-1 py-1.5 rounded-md transition-colors flex items-center justify-center gap-1 ${mePlusFreq === 'annual' ? 'bg-background shadow-sm text-foreground' : 'text-muted-foreground hover:text-foreground'}`}
>Anual <span className="text-emerald-600 dark:text-emerald-400 text-[10px] font-bold">17%</span></button>
</div>
<div className="text-center">
<div className="text-3xl font-bold">${mePlusFreq === 'monthly' ? '900' : '9,000'}</div>
<p className="text-sm text-muted-foreground">{mePlusFreq === 'monthly' ? 'por mes (IVA incluido)' : 'por año (IVA incluido)'}</p>
{mePlusFreq === 'monthly' && <p className="text-xs text-muted-foreground mt-1">o $9,000/año (ahorras 17%)</p>}
</div>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>1 RFC</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>3 usuarios</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Hasta 1,000,000 CFDIs</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Base de datos en la nube</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Dashboard, CFDI, IVA/ISR, alertas</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>50 timbres/mes incluidos</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span><strong>API REST</strong> incluida</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span><strong>Lolita IA</strong> agente fiscal</span></div>
</div>
</CardContent>
</Card>
{/* Business Control */} {/* Business Control */}
<Card <Card
className={cn( className={cn(
@@ -219,21 +320,23 @@ export default function RegisterDespachoPage() {
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="text-center"> <div className="text-center">
<div className="text-3xl font-bold">$21,000</div> <div className="text-3xl font-bold">$25,850</div>
<p className="text-sm text-muted-foreground">primer año (IVA incluido)</p> <p className="text-sm text-muted-foreground">por año (IVA incluido)</p>
<p className="text-xs text-muted-foreground mt-1">$15,000/año a partir del 2do año</p> <p className="text-xs text-muted-foreground mt-1">+ $45/mes por cada RFC adicional sobre 100</p>
</div> </div>
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Base de datos en tu servidor</span></div> <div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Hasta 100 RFCs</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>RFCs ilimitados</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Usuarios ilimitados</span></div> <div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Usuarios ilimitados</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Hasta 1,000,000 CFDIs por contribuyente</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Servidor local con backup</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Control total de tus datos</span></div> <div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Control total de tus datos</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Requiere Docker en tu servidor</span></div> <div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Dashboard, CFDI, IVA/ISR, alertas, calendario</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Reportes, conciliación, documentos, facturación, API</span></div>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
{/* Business Cloud */} {/* Enterprise (business_cloud) */}
<Card <Card
className={cn( className={cn(
'cursor-pointer transition-all hover:shadow-lg relative', 'cursor-pointer transition-all hover:shadow-lg relative',
@@ -241,27 +344,27 @@ export default function RegisterDespachoPage() {
)} )}
onClick={() => setSelectedPlan('business_cloud')} onClick={() => setSelectedPlan('business_cloud')}
> >
<div className="absolute -top-3 left-1/2 -translate-x-1/2 bg-primary text-primary-foreground text-xs px-3 py-1 rounded-full">
Más popular
</div>
<CardHeader className="text-center pb-2"> <CardHeader className="text-center pb-2">
<div className="mx-auto bg-purple-100 dark:bg-purple-900 rounded-full p-3 w-fit mb-2"> <div className="mx-auto bg-purple-100 dark:bg-purple-900 rounded-full p-3 w-fit mb-2">
<Cloud className="h-6 w-6 text-purple-600 dark:text-purple-400" /> <Cloud className="h-6 w-6 text-purple-600 dark:text-purple-400" />
</div> </div>
<CardTitle className="text-xl">Business Cloud</CardTitle> <CardTitle className="text-xl">Enterprise</CardTitle>
<p className="text-sm text-muted-foreground">Nosotros lo operamos por ti</p> <p className="text-sm text-muted-foreground">Despachos grandes con alto volumen</p>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="text-center"> <div className="text-center">
<div className="text-3xl font-bold">$15,000</div> <div className="text-3xl font-bold">$43,000</div>
<p className="text-sm text-muted-foreground">por año (fijo)</p> <p className="text-sm text-muted-foreground">por año (IVA incluido)</p>
<p className="text-xs text-muted-foreground mt-1">+ $45/mes por cada RFC gestionado</p> <p className="text-xs text-muted-foreground mt-1">+ $45/mes por cada RFC adicional sobre 100</p>
</div> </div>
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Base de datos en la nube (Horux)</span></div> <div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Hasta 100 RFCs</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Sin infraestructura propia</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Usuarios ilimitados</span></div> <div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Usuarios ilimitados</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Backups automáticos</span></div> <div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Hasta 3,000,000 CFDIs por contribuyente</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Servidor local con backup</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Backups automáticos en la nube</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Dashboard, CFDI, IVA/ISR, alertas, calendario</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Reportes, conciliación, documentos, facturación, API</span></div>
<div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Soporte prioritario</span></div> <div className="flex items-center gap-2"><CheckCircle2 className="h-4 w-4 text-green-500 flex-shrink-0" /><span>Soporte prioritario</span></div>
</div> </div>
</CardContent> </CardContent>

View File

@@ -13,7 +13,7 @@ export interface DespachoInfo {
plan: string; plan: string;
} }
export type DespachoSignupPlan = 'trial' | 'business_control' | 'business_cloud'; export type DespachoSignupPlan = 'trial' | 'business_control' | 'business_cloud' | 'mi_empresa' | 'mi_empresa_plus';
export interface DespachoSignupRequest { export interface DespachoSignupRequest {
despacho: { despacho: {
@@ -23,6 +23,7 @@ export interface DespachoSignupRequest {
codigoPostal?: string; codigoPostal?: string;
verticalProfile: VerticalProfile; verticalProfile: VerticalProfile;
plan?: DespachoSignupPlan; plan?: DespachoSignupPlan;
frequency?: 'monthly' | 'annual';
}; };
owner: { owner: {
nombre: string; nombre: string;