feat(admin/usuarios): agregar usuario globalmente desde admin
El admin global ahora puede crear usuarios directamente desde /admin/usuarios sin depender de que un owner los invite. Backend: - Nuevo endpoint POST /usuarios/global (controller + service) - Valida límite de usuarios del plan del tenant destino - Si el email ya existe, agrega membership al tenant destino - Si no existe, crea user con temp password + membership - Schema Zod: email, nombre, role, tenantId, supervisorUserId? Frontend: - Botón 'Agregar Usuario' en /admin/usuarios - Formulario con: nombre, email, rol, empresa - Hook useCreateUsuarioGlobal con invalidación de queries
This commit is contained in:
@@ -200,6 +200,65 @@ export async function getAllUsuarios(): Promise<UserListItem[]> {
|
||||
return memberships.map(m => mapMembershipRow(m, true));
|
||||
}
|
||||
|
||||
export async function createUsuarioGlobal(
|
||||
tenantId: string,
|
||||
data: UserInvite
|
||||
): Promise<UserListItem> {
|
||||
const tenant = await prisma.tenant.findUnique({
|
||||
where: { id: tenantId },
|
||||
select: { plan: true },
|
||||
});
|
||||
|
||||
// Límite del catálogo despacho desde BD (con cache). -1 = ilimitado.
|
||||
const planLimits = tenant ? await getDespachoPlanLimits(tenant.plan) : null;
|
||||
const maxUsers = planLimits?.maxUsers ?? 1;
|
||||
|
||||
const currentCount = await prisma.tenantMembership.count({
|
||||
where: { tenantId, active: true },
|
||||
});
|
||||
|
||||
if (maxUsers !== -1 && currentCount >= maxUsers) {
|
||||
throw new Error('Límite de usuarios alcanzado para este plan');
|
||||
}
|
||||
|
||||
// Si el email ya existe como user global, agregamos membership en este tenant
|
||||
let user = await prisma.user.findUnique({ where: { email: data.email } });
|
||||
|
||||
if (!user) {
|
||||
const tempPassword = randomBytes(4).toString('hex');
|
||||
const passwordHash = await bcrypt.hash(tempPassword, 12);
|
||||
user = await prisma.user.create({
|
||||
data: {
|
||||
email: data.email,
|
||||
passwordHash,
|
||||
nombre: data.nombre,
|
||||
lastTenantId: tenantId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const rolId = await getRolId(data.role);
|
||||
|
||||
await prisma.tenantMembership.upsert({
|
||||
where: { userId_tenantId: { userId: user.id, tenantId } },
|
||||
update: { rolId, isOwner: false, active: true },
|
||||
create: {
|
||||
userId: user.id,
|
||||
tenantId,
|
||||
rolId,
|
||||
isOwner: false,
|
||||
active: true,
|
||||
},
|
||||
});
|
||||
|
||||
const membership = await prisma.tenantMembership.findUnique({
|
||||
where: { userId_tenantId: { userId: user.id, tenantId } },
|
||||
include: MEMBERSHIP_INCLUDE,
|
||||
});
|
||||
|
||||
return mapMembershipRow(membership!);
|
||||
}
|
||||
|
||||
export async function updateUsuarioGlobal(
|
||||
userId: string,
|
||||
data: UserUpdate & { tenantId?: string }
|
||||
|
||||
Reference in New Issue
Block a user