feat: facturación primer pago, fixes SAT/MP, autocompletado RFCs/conceptos
Backend: - Notificación email al admin cuando llega primer pago aprobado (sin factura auto) - Endpoints GET /pagos-sin-factura y POST /emitir-factura-pago para admin global - Fix vinculación org Facturapi Horux 360 (69f23a5a242e0af47a41fa0d) - Fix webhook MP: validación defensiva de x-signature header - Fix autocompleto RFCs: eliminado filtro por contribuyenteId - Fix autocompleto conceptos: eliminado filtro por contribuyenteId - SAT fixes: anti-bot CSF scraper, request reuse, date range fix, stale job thresholds - SAT sync request reuse across jobs para evitar agotar cuota diaria - Typo fix MP_ACCESS_TOKEN en .env - Trial invitations system backend Frontend: - Nueva página /admin/facturas-pendientes con tabla y emisión manual - Métrica 'Facturas pendientes' en /clientes (clickable) - Navegación onboarding FIEL/CSD corregida - Sidebar themes sincronizados - Fix SAT portal migration scraper (NetIQ) - Trial invitation acceptance pages
This commit is contained in:
@@ -10,10 +10,10 @@ const signupSchema = z.object({
|
||||
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'),
|
||||
// plan y frequency ya no se escogen en el registro — todos empiezan con trial genérico.
|
||||
// Se mantienen opcionales para compatibilidad backward con clientes antiguos.
|
||||
plan: z.enum(['trial', 'mi_empresa', 'mi_empresa_plus', 'business_control', 'business_cloud']).optional(),
|
||||
frequency: z.enum(['monthly', 'annual']).optional(),
|
||||
}),
|
||||
owner: z.object({
|
||||
nombre: z.string().min(2, 'Nombre del owner requerido'),
|
||||
@@ -42,16 +42,10 @@ export async function getMyPlan(req: Request, res: Response, next: NextFunction)
|
||||
// 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.
|
||||
//
|
||||
// FIX: Si hay una subscription en trial con un plan específico (ej.
|
||||
// business_control desde una TrialInvitation), respetamos ese plan
|
||||
// para que el feature-gate y los límites funcionen correctamente.
|
||||
const subscription = await prisma.subscription.findFirst({
|
||||
where: { tenantId, status: { in: ['authorized', 'pending', 'paused', 'trial'] } },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
@@ -61,6 +55,18 @@ export async function getMyPlan(req: Request, res: Response, next: NextFunction)
|
||||
},
|
||||
});
|
||||
|
||||
let currentPlan: string;
|
||||
if (subscription?.status === 'trial' && subscription.plan && subscription.plan !== 'trial') {
|
||||
currentPlan = subscription.plan;
|
||||
} else 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.
|
||||
return res.json({
|
||||
plan: currentPlan,
|
||||
dbMode: tenant.dbMode,
|
||||
|
||||
Reference in New Issue
Block a user