feat: StorageService S3/MinIO, POST /users, invoice upload, portal dashboard §08 statuses + notifications

This commit is contained in:
Lizandro Guarnizo
2026-06-01 20:55:30 -05:00
parent 98ab5a309c
commit a047a8b032
19 changed files with 1166 additions and 134 deletions
+18 -2
View File
@@ -1,8 +1,8 @@
import { Controller, Get, Patch, Body, Param, Query, UseGuards } from "@nestjs/common";
import { Controller, Get, Post, Patch, Body, Param, Query, UseGuards } from "@nestjs/common";
import { UsersService } from "./users.service";
import { JwtAuthGuard, RolesGuard, Roles } from "../auth/guards/auth.guard";
import { CurrentUser } from "../auth/decorators/current-user.decorator";
import { IsEnum, IsBoolean } from "class-validator";
import { IsEnum, IsBoolean, IsEmail, IsString, MinLength, IsOptional } from "class-validator";
class UpdateRoleDto {
@IsEnum(["CLIENTE","OPERADOR_BODEGA","AGENTE_ADUANERO","ADMIN_EMPRESA","SUPER_ADMIN","SOPORTE"])
@@ -11,6 +11,15 @@ class UpdateRoleDto {
class SetActiveDto {
@IsBoolean() isActive!: boolean;
}
class CreateUserDto {
@IsEmail() email!: string;
@IsString() @MinLength(8) password!: string;
@IsString() firstName!: string;
@IsString() lastName!: string;
@IsOptional() @IsString() phone?: string;
@IsEnum(["CLIENTE","OPERADOR_BODEGA","AGENTE_ADUANERO","ADMIN_EMPRESA","SUPER_ADMIN","SOPORTE"])
role!: string;
}
@Controller("users")
@UseGuards(JwtAuthGuard, RolesGuard)
@@ -29,6 +38,13 @@ export class UsersController {
return this.svc.findOne(id);
}
/** Admin creates a user directly (doc §12) */
@Post()
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN")
create(@Body() dto: CreateUserDto, @CurrentUser() user: any): Promise<any> {
return this.svc.create(dto, user.tenantId);
}
@Patch(":id/role")
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN")
updateRole(@Param("id") id: string, @Body() dto: UpdateRoleDto): Promise<any> {
+7 -1
View File
@@ -1,6 +1,12 @@
import { Module } from "@nestjs/common";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
import { PrismaModule } from "../prisma/prisma.module";
@Module({ controllers: [UsersController], providers: [UsersService] })
@Module({
imports: [PrismaModule],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
+49 -1
View File
@@ -1,5 +1,7 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { Injectable, NotFoundException, ConflictException } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import * as bcrypt from "bcrypt";
import { generateSuiteCode } from "../common/utils/suite-code.util";
@Injectable()
export class UsersService {
@@ -39,6 +41,52 @@ export class UsersService {
return user;
}
/** Admin creates a user directly (no self-registration) */
async create(dto: {
email: string;
password: string;
firstName: string;
lastName: string;
phone?: string;
role: string;
}, tenantId: string): Promise<any> {
const exists = await this.prisma.client.user.findFirst({
where: { email: dto.email, tenantId },
});
if (exists) throw new ConflictException("Ya existe un usuario con ese email.");
const hash = await bcrypt.hash(dto.password, 12);
// Generate suite code for CLIENTE role
let suiteCode: string | undefined;
if (dto.role === "CLIENTE") {
const count = await this.prisma.client.user.count({ where: { tenantId } });
suiteCode = generateSuiteCode(count + 1);
}
const user = await this.prisma.client.user.create({
data: {
tenantId,
email: dto.email,
passwordHash: hash,
firstName: dto.firstName,
lastName: dto.lastName,
phone: dto.phone,
role: dto.role as any,
isActive: true,
},
});
if (suiteCode) {
await this.prisma.client.suite.create({
data: { tenantId, userId: user.id, code: suiteCode },
});
}
const { passwordHash: _, ...safe } = user as any;
return safe;
}
async updateRole(id: string, role: string): Promise<any> {
return this.prisma.client.user.update({ where: { id }, data: { role: role as any } });
}