/** * Bootstrap del tenant admin global (Horux 360 — HTS240708LJA) + usuarios staff. * * Crea: * 1. Tenant Horux 360 (RFC HTS240708LJA, plan enterprise) * 2. Carlos como owner del tenant + rol platform_admin * 3. Ivan como contador del tenant + rol platform_ti (TI superset) * 4. Suscripción authorized por 1 año * * Uso: `pnpm bootstrap:admin-global` * * Idempotente-ish: falla limpio si el tenant ya existe (RFC unique). * Para re-ejecutar, borra el tenant y su BD manualmente antes. * * Requisitos previos: * 1. `pnpm prisma migrate deploy` (schema central) * 2. `pnpm db:seed` (catálogos SAT, regímenes, ISR, eventos fiscales, roles) * * Env vars opcionales (con defaults): * HORUX_ADMIN_EMAIL (default: carlos@horuxfin.com) * HORUX_ADMIN_NOMBRE (default: Carlos) * HORUX_TI_EMAIL (default: ivan@horuxfin.com) * HORUX_TI_NOMBRE (default: Ivan) */ import { prisma } from '../src/config/database.js'; import * as tenantsService from '../src/services/tenants.service.js'; import * as usuariosService from '../src/services/usuarios.service.js'; const RFC = 'HTS240708LJA'; const TENANT_NAME = 'Horux 360'; const PLAN = 'enterprise' as const; const CFDI_LIMIT = -1; // ilimitado const USERS_LIMIT = 10; const SUBSCRIPTION_YEARS = 1; async function main() { const adminEmail = process.env.HORUX_ADMIN_EMAIL || 'carlos@horuxfin.com'; const adminNombre = process.env.HORUX_ADMIN_NOMBRE || 'Carlos'; const tiEmail = process.env.HORUX_TI_EMAIL || 'ivan@horuxfin.com'; const tiNombre = process.env.HORUX_TI_NOMBRE || 'Ivan'; console.log(`Bootstrap del tenant admin global`); console.log(` RFC: ${RFC}`); console.log(` Nombre: ${TENANT_NAME}`); console.log(` Admin: ${adminNombre} <${adminEmail}> (platform_admin)`); console.log(` TI: ${tiNombre} <${tiEmail}> (platform_ti)`); console.log(` Plan: ${PLAN} (cfdi: ${CFDI_LIMIT}, users: ${USERS_LIMIT})`); console.log(''); // 1. Crea tenant + BD provisionada + Carlos como owner + subscription pending const { tenant, user: carlosUser, tempPassword: carlosPassword } = await tenantsService.createTenant({ nombre: TENANT_NAME, rfc: RFC, plan: PLAN, cfdiLimit: CFDI_LIMIT, usersLimit: USERS_LIMIT, adminEmail, adminNombre, amount: 0, }); console.log(`✓ Tenant creado: ${tenant.id}`); console.log(`✓ BD provisionada: ${tenant.databaseName}`); console.log(`✓ Carlos creado (owner): ${carlosUser.email}`); // 2. Asigna platform_admin a Carlos (no se hace automáticamente desde tenants.service) const carlosFull = await prisma.user.findUnique({ where: { email: adminEmail } }); if (carlosFull) { await prisma.userPlatformRole.upsert({ where: { userId_role: { userId: carlosFull.id, role: 'platform_admin' } }, update: {}, create: { userId: carlosFull.id, role: 'platform_admin' }, }); console.log(`✓ Carlos: rol platform_admin asignado`); } // 3. Crea Ivan como contador del tenant (membership) y le asigna platform_ti const ivan = await usuariosService.inviteUsuario(tenant.id, { email: tiEmail, nombre: tiNombre, role: 'contador', }); console.log(`✓ Ivan creado: ${ivan.email} (membership contador)`); await prisma.userPlatformRole.upsert({ where: { userId_role: { userId: ivan.id, role: 'platform_ti' } }, update: {}, create: { userId: ivan.id, role: 'platform_ti' }, }); console.log(`✓ Ivan: rol platform_ti asignado (superset, mismos permisos que admin)`); // 4. Sube la subscription a 'authorized' con vigencia de 1 año const existing = await prisma.subscription.findFirst({ where: { tenantId: tenant.id }, orderBy: { createdAt: 'desc' }, }); if (existing) { const now = new Date(); const end = new Date(now); end.setFullYear(end.getFullYear() + SUBSCRIPTION_YEARS); await prisma.subscription.update({ where: { id: existing.id }, data: { status: 'authorized', currentPeriodStart: now, currentPeriodEnd: end, }, }); console.log(`✓ Suscripción marcada 'authorized' hasta ${end.toISOString().slice(0, 10)}`); } console.log(''); console.log('=== DONE ==='); console.log(`Credenciales temporales para primer login:`); console.log(` Carlos (admin): ${adminEmail}`); console.log(` Password: ${carlosPassword}`); console.log(''); console.log(` Ivan (TI): ${tiEmail}`); console.log(` Password: revisa el correo de bienvenida (inviteUsuario lo envía por email)`); console.log(''); console.log('Próximos pasos manuales:'); console.log(` 1. Carlos login en /login con las credenciales de arriba`); console.log(` 2. Cambiar el password desde /configuracion/seguridad`); console.log(` 3. Verificar que Ivan recibió su correo de invitación`); console.log(` 4. Subir FIEL en /configuracion/sat para habilitar sincronización`); console.log(` 5. (Opcional) Configurar organización Facturapi en /configuracion`); } main() .catch((err) => { console.error('✗ Bootstrap falló:', err.message || err); process.exit(1); }) .finally(() => prisma.$disconnect());