feat(core,crm): gestión de compañías en el CRM + soporte hub_admin sin tenant

Las compañías (a76.company) se gestionan en el CRM, ligadas a un tenant del
Workspace. Cambios:

- my-companies: por MEMBRESÍA (user_tenants ∪ user_company_roles), así el hub_admin
  sin tenant en el token ve las compañías que creó/se le asignaron; para usuarios
  con tenant, autocrea una por defecto en el primer acceso.
- POST /auth/companies: da de alta una compañía bajo un tenant + asigna al usuario.
  GET /auth/assignable-tenants: tenants elegibles (hub_admin: todos; usuario: el suyo).
- security.validate_access_to_resource: si el token no trae tenant (hub_admin),
  resuelve el tenant desde la compañía activa (a76.company.tenant_id) → puede operar
  por compañía seleccionada.
- set-active: fija sso_tenant_id/pub con el tenant de la compañía (override).
- Pantalla "Compañías" (nav) para crear/listar/seleccionar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ernesto Herrera
2026-07-17 11:44:36 -06:00
parent 0b6848bbdd
commit 63ad2e2ecd
6 changed files with 335 additions and 59 deletions

View File

@@ -6,22 +6,28 @@ import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ cookies, request }) => {
try {
const { companyId } = await request.json();
const body = await request.json();
const companyId = body?.companyId;
const tenantId = body?.tenantId;
if (!companyId || typeof companyId !== 'number') {
return json({ error: 'Invalid company ID' }, { status: 400 });
}
// Establecer la cookie desde el servidor
cookies.set('active_company_id', companyId.toString(), {
path: '/',
maxAge: 60 * 60 * 24 * 30, // 30 días
sameSite: 'lax',
httpOnly: false, // Permitir acceso desde JavaScript
secure: process.env.NODE_ENV === 'production'
});
const isProd = process.env.NODE_ENV === 'production';
const base = { path: '/', maxAge: 60 * 60 * 24 * 30, sameSite: 'lax' as const, secure: isProd };
return json({ success: true, companyId });
// Compañía activa (legible desde JS)
cookies.set('active_company_id', companyId.toString(), { ...base, httpOnly: false });
// Fijar el tenant de la compañía como override → el backend escala por ese
// tenant aunque el token no lo traiga (caso hub_admin operando por compañía).
if (typeof tenantId === 'number' && Number.isFinite(tenantId)) {
cookies.set('sso_tenant_id', tenantId.toString(), { ...base, httpOnly: true });
cookies.set('sso_tenant_pub', tenantId.toString(), { ...base, httpOnly: false });
}
return json({ success: true, companyId, tenantId: tenantId ?? null });
} catch (error) {
console.error('Error setting active company:', error);
return json({ error: 'Internal server error' }, { status: 500 });