136 lines
5.1 KiB
TypeScript
136 lines
5.1 KiB
TypeScript
/**
|
|
* 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());
|