fix: C-7 webhook tenant lookup, C-5 full tax calc §15, M-2/M-3/M-4/M-8/L-7/L-8/L-9 gap fixes

This commit is contained in:
Lizandro Guarnizo
2026-06-01 21:10:43 -05:00
parent a047a8b032
commit a5842278fb
11 changed files with 156 additions and 54 deletions
+4
View File
@@ -6,6 +6,8 @@ import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { JwtStrategy } from "./jwt.strategy";
import { WarehousesModule } from "../warehouses/warehouses.module";
import { NotificationsModule } from "../notifications/notifications.module";
import { IntegrationsModule } from "../integrations/integrations.module";
@Module({
imports: [
@@ -19,6 +21,8 @@ import { WarehousesModule } from "../warehouses/warehouses.module";
}),
}),
WarehousesModule,
NotificationsModule,
IntegrationsModule,
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
+27 -1
View File
@@ -5,11 +5,13 @@ import { JwtService } from "@nestjs/jwt";
import { ConfigService } from "@nestjs/config";
import { PrismaService } from "../prisma/prisma.service";
import { WarehousesService } from "../warehouses/warehouses.service";
import { NotificationsService } from "../notifications/notifications.service";
import { IntegrationsService, INTEGRATION_CATALOG } from "../integrations/integrations.service";
import { generateSuiteCode } from "../common/utils/suite-code.util";
import { RegisterDto, LoginDto } from "./dto/auth.dto";
import * as bcrypt from "bcrypt";
import * as crypto from "crypto";
import { TOTP, generateSecret, generateURI, verify as totpVerify } from "otplib";
import { generateSecret, generateURI, verify as totpVerify } from "otplib";
const TENANT_SLUG = "moraworld";
const BCRYPT_ROUNDS = 10;
@@ -21,6 +23,8 @@ export class AuthService {
private jwt: JwtService,
private config: ConfigService,
private warehouses: WarehousesService,
private notifications: NotificationsService,
private integrations: IntegrationsService,
) {}
/** Builds the suite address from the default warehouse in DB, falls back to env vars */
@@ -70,6 +74,24 @@ export class AuthService {
await this.audit(tenant.id, user.id, "USER_REGISTER", "User", user.id);
// L-7: Auto-seed integration keys vacías (idempotente)
try {
await Promise.all(
INTEGRATION_CATALOG.map(cat =>
this.prisma.client.integration.upsert({
where: { tenantId_key: { tenantId: tenant.id, key: cat.key } },
create: { tenantId: tenant.id, key: cat.key, label: cat.label, group: cat.group ?? null, isActive: false },
update: {},
})
)
);
} catch { /* non-blocking */ }
// L-8: Auto-seed notification templates (idempotente)
try {
await this.notifications.seedDefaultTemplates(tenant.id);
} catch { /* non-blocking */ }
const tokens = await this.generateTokens(user.id, user.email, user.role, tenant.id);
return {
user: this.sanitizeUser(user),
@@ -100,6 +122,7 @@ export class AuthService {
}
// MFA
const PRIVILEGED_ROLES = ["ADMIN_EMPRESA", "SUPER_ADMIN", "OPERADOR_BODEGA", "AGENTE_ADUANERO"];
if (user.mfaEnabled) {
if (!dto.totpCode) return { requiresMfa: true, userId: user.id };
const ok = totpVerify({ token: dto.totpCode, secret: user.mfaSecret! });
@@ -107,6 +130,9 @@ export class AuthService {
await this.audit(tenant.id, user.id, "MFA_FAILED", "User", user.id);
throw new UnauthorizedException("Código MFA inválido.");
}
} else if (PRIVILEGED_ROLES.includes(user.role)) {
// M-8: MFA obligatorio para roles privilegiados — forzar setup antes del primer acceso
return { requiresMfaSetup: true, userId: user.id };
}
await this.prisma.client.user.update({