diff --git a/apps/api/src/controllers/tenants.controller.ts b/apps/api/src/controllers/tenants.controller.ts index e7477ff..c1cdcc2 100644 --- a/apps/api/src/controllers/tenants.controller.ts +++ b/apps/api/src/controllers/tenants.controller.ts @@ -39,21 +39,24 @@ export async function createTenant(req: Request, res: Response, next: NextFuncti throw new AppError(403, 'Solo administradores pueden crear clientes'); } - const { nombre, rfc, plan, cfdiLimit, usersLimit } = req.body; + const { nombre, rfc, plan, cfdiLimit, usersLimit, adminEmail, adminNombre, amount } = req.body; - if (!nombre || !rfc) { - throw new AppError(400, 'Nombre y RFC son requeridos'); + if (!nombre || !rfc || !adminEmail || !adminNombre) { + throw new AppError(400, 'Nombre, RFC, adminEmail y adminNombre son requeridos'); } - const tenant = await tenantsService.createTenant({ + const result = await tenantsService.createTenant({ nombre, rfc, plan, cfdiLimit, usersLimit, + adminEmail, + adminNombre, + amount: amount || 0, }); - res.status(201).json(tenant); + res.status(201).json(result); } catch (error) { next(error); } diff --git a/apps/api/src/services/fiel.service.ts b/apps/api/src/services/fiel.service.ts index 7fbd0d7..2639836 100644 --- a/apps/api/src/services/fiel.service.ts +++ b/apps/api/src/services/fiel.service.ts @@ -4,6 +4,7 @@ import { join } from 'path'; import { prisma } from '../config/database.js'; import { env } from '../config/env.js'; import { encryptFielCredentials, encrypt, decryptFielCredentials } from './sat/sat-crypto.service.js'; +import { emailService } from './email/email.service.js'; import type { FielStatus } from '@horux/shared'; /** @@ -144,10 +145,22 @@ export async function uploadFiel( console.error('[FIEL] Filesystem storage failed (DB storage OK):', fsError); } + // Notify admin that client uploaded FIEL + const tenant = await prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { nombre: true, rfc: true }, + }); + if (tenant) { + emailService.sendFielNotification({ + clienteNombre: tenant.nombre, + clienteRfc: tenant.rfc, + }).catch(err => console.error('[EMAIL] FIEL notification failed:', err)); + } + const daysUntilExpiration = Math.ceil( (validUntil.getTime() - Date.now()) / (1000 * 60 * 60 * 24) ); - + return { success: true, message: 'FIEL configurada correctamente', diff --git a/apps/api/src/services/tenants.service.ts b/apps/api/src/services/tenants.service.ts index 9178ea9..1bbf725 100644 --- a/apps/api/src/services/tenants.service.ts +++ b/apps/api/src/services/tenants.service.ts @@ -1,5 +1,8 @@ import { prisma, tenantDb } from '../config/database.js'; import { PLANS } from '@horux/shared'; +import { emailService } from './email/email.service.js'; +import { randomBytes } from 'crypto'; +import bcrypt from 'bcryptjs'; export async function getAllTenants() { return prisma.tenant.findMany({ @@ -41,13 +44,17 @@ export async function createTenant(data: { plan?: 'starter' | 'business' | 'professional' | 'enterprise'; cfdiLimit?: number; usersLimit?: number; + adminEmail: string; + adminNombre: string; + amount: number; }) { const plan = data.plan || 'starter'; const planConfig = PLANS[plan]; - // Provision a dedicated database for this tenant + // 1. Provision a dedicated database for this tenant const databaseName = await tenantDb.provisionDatabase(data.rfc); + // 2. Create tenant record const tenant = await prisma.tenant.create({ data: { nombre: data.nombre, @@ -59,7 +66,39 @@ export async function createTenant(data: { } }); - return tenant; + // 3. Create admin user with temp password + const tempPassword = randomBytes(4).toString('hex'); // 8-char random + const hashedPassword = await bcrypt.hash(tempPassword, 10); + + const user = await prisma.user.create({ + data: { + tenantId: tenant.id, + email: data.adminEmail, + passwordHash: hashedPassword, + nombre: data.adminNombre, + role: 'admin', + }, + }); + + // 4. Create initial subscription + await prisma.subscription.create({ + data: { + tenantId: tenant.id, + plan, + status: 'pending', + amount: data.amount, + frequency: 'monthly', + }, + }); + + // 5. Send welcome email (non-blocking) + emailService.sendWelcome(data.adminEmail, { + nombre: data.adminNombre, + email: data.adminEmail, + tempPassword, + }).catch(err => console.error('[EMAIL] Welcome email failed:', err)); + + return { tenant, user, tempPassword }; } export async function updateTenant(id: string, data: {