Initial commit - Horux Despachos NL

This commit is contained in:
2026-05-03 16:47:53 -06:00
commit b00b677c54
647 changed files with 133843 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
import type { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { signupDespacho } from '../services/despacho.service.js';
import { AppError } from '../middlewares/error.middleware.js';
import { prisma } from '../config/database.js';
const signupSchema = z.object({
despacho: z.object({
nombre: z.string().min(2, 'Nombre del despacho requerido'),
regimenFiscal: z.string().optional(),
codigoPostal: z.string().regex(/^\d{5}$/, 'Código postal inválido').optional(),
verticalProfile: z.enum(['CONTABLE', 'JURIDICO', 'ARQUITECTURA']),
plan: z.enum(['trial', 'mi_empresa', 'mi_empresa_plus', 'business_control', 'business_cloud']).optional().default('trial'),
// Solo aplica a mi_empresa y mi_empresa_plus (los otros pagados son
// anuales fijos). Default annual sesga el cash-flow del negocio.
frequency: z.enum(['monthly', 'annual']).optional().default('annual'),
}),
owner: z.object({
nombre: z.string().min(2, 'Nombre del owner requerido'),
email: z.string().email('Email inválido'),
password: z.string().min(10, 'La contraseña debe tener al menos 10 caracteres'),
}),
});
export async function getMyPlan(req: Request, res: Response, next: NextFunction) {
try {
const tenantId = req.user!.tenantId;
const tenant = await prisma.tenant.findUnique({
where: { id: tenantId },
select: { dbMode: true, trialEndsAt: true, verticalProfile: true, plan: true },
});
if (!tenant) {
return next(new AppError(404, 'Tenant no encontrado'));
}
const now = new Date();
const isTrialActive = tenant.trialEndsAt ? tenant.trialEndsAt > now : false;
// Mapea según trialEndsAt + tenant.plan (no dbMode). dbMode era proxy
// antes de la introducción de Mi Empresa / Mi Empresa+ — para esos
// planes, dbMode también es MANAGED y reportar `business_cloud` daba
// mapeo equivocado. tenant.plan es la fuente de verdad post-migración
// 20260426073942 (que añadió mi_empresa y mi_empresa_plus al enum).
let currentPlan: string;
if (isTrialActive) {
currentPlan = 'trial';
} else {
currentPlan = String(tenant.plan);
}
// Estado de suscripción activa (si hay) — alimenta la UI con el monto
// recurrente actual, fecha de próxima renovación y si el primer pago
// (cuando aplica dualidad firstYear) ya fue completado.
const subscription = await prisma.subscription.findFirst({
where: { tenantId, status: { in: ['authorized', 'pending', 'paused', 'trial'] } },
orderBy: { createdAt: 'desc' },
select: {
status: true, amount: true, plan: true,
currentPeriodStart: true, currentPeriodEnd: true,
},
});
return res.json({
plan: currentPlan,
dbMode: tenant.dbMode,
trialEndsAt: tenant.trialEndsAt?.toISOString() ?? null,
isTrialActive,
subscription: subscription
? {
status: subscription.status,
plan: subscription.plan,
amount: Number(subscription.amount),
currentPeriodStart: subscription.currentPeriodStart?.toISOString() ?? null,
currentPeriodEnd: subscription.currentPeriodEnd?.toISOString() ?? null,
}
: null,
});
} catch (error) {
return next(error);
}
}
export async function signup(req: Request, res: Response, next: NextFunction) {
try {
const data = signupSchema.parse(req.body);
const result = await signupDespacho(data);
return res.status(201).json(result);
} catch (error: any) {
if (error instanceof z.ZodError) {
return next(new AppError(400, error.errors[0].message));
}
if (error.message?.includes('Ya existe')) {
return next(new AppError(409, error.message));
}
return next(error);
}
}