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:
Horux Dev
2026-05-09 21:56:42 +00:00
parent b00b677c54
commit 9f11a0ba39
70 changed files with 2801 additions and 609 deletions

View File

@@ -26,7 +26,7 @@ export async function signupDespacho(data: DespachoSignupRequest) {
plan: 'trial',
databaseName: databaseName,
verticalProfile: despacho.verticalProfile as any,
dbMode: (despacho.plan === 'business_control' ? 'BYO' : 'MANAGED') as any,
dbMode: 'MANAGED',
dbSchemaVersion: 0,
trialEndsAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
codigoPostal: despacho.codigoPostal,
@@ -91,40 +91,9 @@ export async function signupDespacho(data: DespachoSignupRequest) {
email: result.user.email,
}).catch(err => console.error('[Signup] Welcome email failed:', err));
// If paid plan, create MP checkout via subscriptionService.subscribe()
// que también crea la fila Subscription en BD (clave para que el webhook
// pueda aplicar la dualidad firstYear→renewal tras el primer cobro aprobado).
let paymentUrl: string | undefined;
if (data.despacho.plan && data.despacho.plan !== 'trial') {
try {
const subscriptionService = await import('./payment/subscription.service.js');
const result2 = await subscriptionService.subscribe({
tenantId: result.tenant.id,
plan: data.despacho.plan as any,
// mi_empresa(+) acepta monthly/annual; los demás solo annual
// — el subscribe valida y rechaza monthly cuando no aplica.
frequency: data.despacho.frequency || 'annual',
payerEmail: owner.email,
});
paymentUrl = result2.paymentUrl;
} catch (err: any) {
// Rollback: delete tenant + user since payment couldn't be set up
await prisma.tenantMembership.deleteMany({ where: { tenantId: result.tenant.id } }).catch(() => {});
await prisma.refreshToken.deleteMany({ where: { userId: result.user.id } }).catch(() => {});
await prisma.tenant.delete({ where: { id: result.tenant.id } }).catch(() => {});
await prisma.user.delete({ where: { id: result.user.id } }).catch(() => {});
const msg = err?.message || '';
if (msg.includes('MercadoPago no está configurado') || msg.includes('Unauthorized access')) {
throw new Error('No se pudo procesar el cobro. Verifica que el sistema de pagos esté configurado o selecciona el plan Trial.');
}
throw new Error(msg || 'No se pudo procesar el cobro.');
}
}
return {
accessToken,
refreshToken,
paymentUrl,
user: {
id: result.user.id,
email: result.user.email,