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:
Horux Dev
2026-05-17 14:32:45 +00:00
parent e8aaf9ff15
commit 0b704e0e27
6 changed files with 239 additions and 11 deletions

View File

@@ -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 }