fix: Correcciones de errores y mejoras del dashboard CFO

- Corregido auth.service.ts para usar estructura multi-tenant correcta (user_tenants)
- Dashboard rediseñado para CFO digital (ingresos/egresos, CFDIs, alertas)
- Sidebar actualizado con rutas correctas (Métricas, Transacciones, CFDIs, Reportes, Asistente IA)
- Agregadas páginas de Perfil (/profile) y Configuración (/settings)
- Corregidos errores de TypeScript (strict mode, tipos duplicados)
- Actualizado docker-compose.yml a PostgreSQL 16
- Corregidas migraciones SQL (índices IMMUTABLE, constraints)
- Configuración ESM modules en packages
- CORS configurado para acceso de red local

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 04:24:23 +00:00
parent 45570baccc
commit 338b9c05df
24 changed files with 7758 additions and 351 deletions

View File

@@ -149,8 +149,8 @@ export class AuthService {
const passwordHash = await hashPassword(input.password);
await client.query(
`INSERT INTO public.users (id, email, password_hash, first_name, last_name, role, tenant_id, is_active, email_verified, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW())`,
`INSERT INTO public.users (id, email, password_hash, first_name, last_name, default_role, is_active, is_email_verified, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW())`,
[
userId,
input.email.toLowerCase(),
@@ -158,12 +158,18 @@ export class AuthService {
input.firstName,
input.lastName,
'owner',
tenantId,
true,
false,
]
);
// Associate user with tenant via user_tenants
await client.query(
`INSERT INTO public.user_tenants (user_id, tenant_id, role, is_active, accepted_at, created_at, updated_at)
VALUES ($1, $2, $3, $4, NOW(), NOW(), NOW())`,
[userId, tenantId, 'owner', true]
);
// Create session
const sessionId = uuidv4();
const tokens = jwtService.generateTokenPair(
@@ -178,11 +184,12 @@ export class AuthService {
);
const refreshTokenHash = hashToken(tokens.refreshToken);
const tokenHash = hashToken(tokens.accessToken);
await client.query(
`INSERT INTO public.user_sessions (id, user_id, tenant_id, refresh_token_hash, expires_at, created_at)
VALUES ($1, $2, $3, $4, $5, NOW())`,
[sessionId, userId, tenantId, refreshTokenHash, jwtService.getRefreshTokenExpiration()]
`INSERT INTO public.user_sessions (id, user_id, tenant_id, token_hash, refresh_token_hash, expires_at, created_at, last_activity_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())`,
[sessionId, userId, tenantId, tokenHash, refreshTokenHash, jwtService.getRefreshTokenExpiration()]
);
await client.query('COMMIT');
@@ -225,12 +232,17 @@ export class AuthService {
const client = await pool.connect();
try {
// Find user by email
// Find user by email - join through user_tenants for multi-tenant support
const userResult = await client.query(
`SELECT u.*, t.schema_name, t.name as tenant_name, t.slug as tenant_slug
`SELECT u.id, u.email, u.password_hash, u.first_name, u.last_name, u.is_active,
ut.role, ut.tenant_id,
t.schema_name, t.name as tenant_name, t.slug as tenant_slug, t.status as tenant_status
FROM public.users u
JOIN public.tenants t ON u.tenant_id = t.id
WHERE u.email = $1 AND u.is_active = true AND t.is_active = true`,
LEFT JOIN public.user_tenants ut ON u.id = ut.user_id AND ut.is_active = true
LEFT JOIN public.tenants t ON ut.tenant_id = t.id
WHERE u.email = $1 AND u.is_active = true
ORDER BY ut.created_at DESC
LIMIT 1`,
[input.email.toLowerCase()]
);
@@ -249,28 +261,35 @@ export class AuthService {
throw new AuthenticationError('Credenciales invalidas');
}
// Determine role - use tenant role if available, otherwise use default_role
const userRole = user.role || 'viewer';
const tenantId = user.tenant_id;
const schemaName = user.schema_name;
// Create session
const sessionId = uuidv4();
const tokens = jwtService.generateTokenPair(
{
id: user.id,
email: user.email,
role: user.role,
tenant_id: user.tenant_id,
schema_name: user.schema_name,
role: userRole,
tenant_id: tenantId,
schema_name: schemaName,
},
sessionId
);
const refreshTokenHash = hashToken(tokens.refreshToken);
const tokenHash = hashToken(tokens.accessToken);
await client.query(
`INSERT INTO public.user_sessions (id, user_id, tenant_id, refresh_token_hash, user_agent, ip_address, expires_at, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())`,
`INSERT INTO public.user_sessions (id, user_id, tenant_id, token_hash, refresh_token_hash, user_agent, ip_address, expires_at, created_at, last_activity_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW())`,
[
sessionId,
user.id,
user.tenant_id,
tenantId,
tokenHash,
refreshTokenHash,
userAgent || null,
ipAddress || null,
@@ -281,9 +300,9 @@ export class AuthService {
// Update last login
await client.query('UPDATE public.users SET last_login_at = NOW() WHERE id = $1', [user.id]);
auditLog('LOGIN_SUCCESS', user.id, user.tenant_id, { userAgent, ipAddress });
auditLog('LOGIN_SUCCESS', user.id, tenantId, { userAgent, ipAddress });
logger.info('User logged in', { userId: user.id, tenantId: user.tenant_id });
logger.info('User logged in', { userId: user.id, tenantId });
return {
user: {
@@ -291,14 +310,14 @@ export class AuthService {
email: user.email,
first_name: user.first_name,
last_name: user.last_name,
role: user.role,
tenant_id: user.tenant_id,
role: userRole,
tenant_id: tenantId,
},
tenant: {
id: user.tenant_id,
id: tenantId,
name: user.tenant_name,
slug: user.tenant_slug,
schema_name: user.schema_name,
schema_name: schemaName,
},
tokens,
};