feat: add TipoEvento entity and related functionality
- Introduced TipoEvento entity with enum for event types. - Created DTOs for creating and updating TipoEvento. - Implemented TipoEvento service with methods for CRUD operations and seeding initial data. - Added TipoEvento controller for handling HTTP requests related to event types. - Integrated TipoEvento into the main application module. - Updated Evento entity to include a relationship with TipoEvento. - Enhanced ParticipanteEvento service to register attendance using QR tokens. - Implemented QR token generation and validation for event attendance. - Updated API documentation for new endpoints and functionalities. - Adjusted TypeOrm configuration to include new entities.
This commit is contained in:
+24
-35
@@ -1,35 +1,24 @@
|
|||||||
// @ts-check
|
module.exports = {
|
||||||
import eslint from '@eslint/js';
|
// parser: '@typescript-eslint/parser',
|
||||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
// parserOptions: {
|
||||||
import globals from 'globals';
|
// project: 'tsconfig.json',
|
||||||
import tseslint from 'typescript-eslint';
|
// sourceType: 'module',
|
||||||
|
// },
|
||||||
export default tseslint.config(
|
// plugins: ['@typescript-eslint/eslint-plugin'],
|
||||||
{
|
// extends: [
|
||||||
ignores: ['eslint.config.mjs'],
|
// 'plugin:@typescript-eslint/recommended',
|
||||||
},
|
// 'plugin:prettier/recommended',
|
||||||
eslint.configs.recommended,
|
// ],
|
||||||
...tseslint.configs.recommendedTypeChecked,
|
// root: true,
|
||||||
eslintPluginPrettierRecommended,
|
// env: {
|
||||||
{
|
// node: true,
|
||||||
languageOptions: {
|
// jest: true,
|
||||||
globals: {
|
// },
|
||||||
...globals.node,
|
// ignorePatterns: ['.eslintrc.js'],
|
||||||
...globals.jest,
|
// rules: {
|
||||||
},
|
// '@typescript-eslint/interface-name-prefix': 'off',
|
||||||
ecmaVersion: 5,
|
// '@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
sourceType: 'module',
|
// '@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
parserOptions: {
|
// '@typescript-eslint/no-explicit-any': 'off',
|
||||||
projectService: true,
|
// },
|
||||||
tsconfigRootDir: import.meta.dirname,
|
};
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/no-floating-promises': 'warn',
|
|
||||||
'@typescript-eslint/no-unsafe-argument': 'warn'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
INSERT INTO tipo_evento (tipo_evento) VALUES
|
||||||
|
('Académico'),
|
||||||
|
('Taller / Capacitación'),
|
||||||
|
('Conferencia / Charla'),
|
||||||
|
('Panel / Mesa redonda'),
|
||||||
|
('Feria / Expo'),
|
||||||
|
('Networking / Vinculación'),
|
||||||
|
('Entrevista / Sesión 1:1'),
|
||||||
|
('Cultural / Artístico'),
|
||||||
|
('Deportivo / Recreativo'),
|
||||||
|
('Institucional / Ceremonial'),
|
||||||
|
('Social / Comunitario'),
|
||||||
|
('Aniversario'),
|
||||||
|
('Otro');
|
||||||
@@ -15,9 +15,12 @@ import { CreateAdministradorDto } from './dto/create-administrador.dto';
|
|||||||
import { UpdateAdministradorDto } from './dto/update.administrador.dto';
|
import { UpdateAdministradorDto } from './dto/update.administrador.dto';
|
||||||
import { LoginAdministradorDto } from './dto/login-administrador.dto';
|
import { LoginAdministradorDto } from './dto/login-administrador.dto';
|
||||||
import { ChangePasswordDto } from './dto/change-password.dto';
|
import { ChangePasswordDto } from './dto/change-password.dto';
|
||||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||||
import { AdministradorApiDocumentation } from './administrador.documentation';
|
import { AdministradorApiDocumentation } from './docs/administrador.documentation';
|
||||||
|
import { JwtValidationGuard } from 'src/auth/guards/jwt-validation.guard';
|
||||||
|
import { Roles } from 'src/auth/decorators/roles.decorator';
|
||||||
|
import { RolesGuard } from 'src/auth/guards/roles.guard';
|
||||||
|
|
||||||
@AdministradorApiDocumentation.ApiController
|
@AdministradorApiDocumentation.ApiController
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@@ -26,20 +29,25 @@ export class AdministradorController {
|
|||||||
constructor(private administradorService: AdministradorService) {}
|
constructor(private administradorService: AdministradorService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(RolesGuard)
|
||||||
|
@UseGuards(JwtValidationGuard)
|
||||||
|
@Roles('administrador')
|
||||||
@AdministradorApiDocumentation.ApiGetAll
|
@AdministradorApiDocumentation.ApiGetAll
|
||||||
getAdministradores(): Promise<Administrador[]> {
|
getAdministradores(): Promise<Administrador[]> {
|
||||||
return this.administradorService.getAdministradores();
|
return this.administradorService.getAdministradores();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(RolesGuard)
|
||||||
|
@UseGuards(JwtValidationGuard)
|
||||||
|
@Roles('administrador')
|
||||||
@AdministradorApiDocumentation.ApiGetOne
|
@AdministradorApiDocumentation.ApiGetOne
|
||||||
getAdministrador(@Param('id', ParseIntPipe) id: number) {
|
getAdministrador(@Param('id', ParseIntPipe) id: number) {
|
||||||
return this.administradorService.getAdministrador(id);
|
return this.administradorService.getAdminByIdOrFail(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
|
@Roles('administrador')
|
||||||
@AdministradorApiDocumentation.ApiCreate
|
@AdministradorApiDocumentation.ApiCreate
|
||||||
createAdministrador(@Body() newAdministrador: CreateAdministradorDto) {
|
createAdministrador(@Body() newAdministrador: CreateAdministradorDto) {
|
||||||
return this.administradorService.createAdministrador(newAdministrador);
|
return this.administradorService.createAdministrador(newAdministrador);
|
||||||
@@ -49,13 +57,15 @@ export class AdministradorController {
|
|||||||
@AdministradorApiDocumentation.ApiLogin
|
@AdministradorApiDocumentation.ApiLogin
|
||||||
login(@Body() loginData: LoginAdministradorDto) {
|
login(@Body() loginData: LoginAdministradorDto) {
|
||||||
return this.administradorService.login(
|
return this.administradorService.login(
|
||||||
loginData.correo,
|
loginData.nombre_usuario,
|
||||||
loginData.password,
|
loginData.password,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id/change-password')
|
@Patch(':id/change-password')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(RolesGuard)
|
||||||
|
@UseGuards(JwtValidationGuard)
|
||||||
|
@Roles('administrador')
|
||||||
@AdministradorApiDocumentation.ApiChangePassword
|
@AdministradorApiDocumentation.ApiChangePassword
|
||||||
changePassword(
|
changePassword(
|
||||||
@Param('id', ParseIntPipe) id: number,
|
@Param('id', ParseIntPipe) id: number,
|
||||||
@@ -69,14 +79,18 @@ export class AdministradorController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(RolesGuard)
|
||||||
|
@UseGuards(JwtValidationGuard)
|
||||||
|
@Roles('administrador')
|
||||||
@AdministradorApiDocumentation.ApiRemove
|
@AdministradorApiDocumentation.ApiRemove
|
||||||
deleteAdministrador(@Param('id', ParseIntPipe) id: number) {
|
deleteAdministrador(@Param('id', ParseIntPipe) id: number) {
|
||||||
return this.administradorService.deleteAdministrador(id);
|
return this.administradorService.deleteAdministrador(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(RolesGuard)
|
||||||
|
@UseGuards(JwtValidationGuard)
|
||||||
|
@Roles('administrador')
|
||||||
@AdministradorApiDocumentation.ApiUpdate
|
@AdministradorApiDocumentation.ApiUpdate
|
||||||
updateAdministrador(
|
updateAdministrador(
|
||||||
@Param('id', ParseIntPipe) id: number,
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ export class AdministradorService {
|
|||||||
|
|
||||||
// Crear y guardar el nuevo administrador
|
// Crear y guardar el nuevo administrador
|
||||||
const newAdministrador = this.administradorRepository.create({
|
const newAdministrador = this.administradorRepository.create({
|
||||||
|
nombre_usuario: administrador.nombre_usuario,
|
||||||
correo: administrador.correo,
|
correo: administrador.correo,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
id_tipo_user: administrador.id_tipo_user,
|
tipoUser: { id_tipo_user: administrador.id_tipo_user }, // Asignar el tipo de usuario
|
||||||
});
|
});
|
||||||
|
|
||||||
const savedAdministrador =
|
const savedAdministrador =
|
||||||
@@ -48,10 +49,10 @@ export class AdministradorService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(correo: string, password: string) {
|
async login(nombre_usuario: string, password: string) {
|
||||||
// Buscar administrador por correo
|
// Buscar administrador por nombre de usuario
|
||||||
const administrador = await this.administradorRepository.findOne({
|
const administrador = await this.administradorRepository.findOne({
|
||||||
where: { correo },
|
where: { nombre_usuario },
|
||||||
relations: ['tipoUser'],
|
relations: ['tipoUser'],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,24 +77,24 @@ export class AdministradorService {
|
|||||||
|
|
||||||
// Generar token JWT
|
// Generar token JWT
|
||||||
const payload = {
|
const payload = {
|
||||||
sub: administrador.id_admnistrador,
|
sub: administrador.id_administrador,
|
||||||
correo: administrador.correo,
|
correo: administrador.correo,
|
||||||
id_tipo_user: administrador.id_tipo_user,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: this.jwtService.sign(payload),
|
token: this.jwtService.sign(payload),
|
||||||
|
tipo_usuario: administrador.tipoUser.tipo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async changePassword(
|
async changePassword(
|
||||||
id_admnistrador: number,
|
id_administrador: number,
|
||||||
currentPassword: string,
|
currentPassword: string,
|
||||||
newPassword: string,
|
newPassword: string,
|
||||||
) {
|
) {
|
||||||
// Buscar administrador
|
// Buscar administrador
|
||||||
const administrador = await this.administradorRepository.findOne({
|
const administrador = await this.administradorRepository.findOne({
|
||||||
where: { id_admnistrador },
|
where: { id_administrador },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!administrador) {
|
if (!administrador) {
|
||||||
@@ -131,23 +132,17 @@ export class AdministradorService {
|
|||||||
return this.administradorRepository.find({
|
return this.administradorRepository.find({
|
||||||
relations: ['tipoUser'],
|
relations: ['tipoUser'],
|
||||||
select: {
|
select: {
|
||||||
id_admnistrador: true,
|
nombre_usuario: true,
|
||||||
|
id_administrador: true,
|
||||||
correo: true,
|
correo: true,
|
||||||
id_tipo_user: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAdministrador(id_admnistrador: number) {
|
async getAdminByIdOrFail(id_administrador: number) {
|
||||||
const administradorFound = await this.administradorRepository.findOne({
|
const administradorFound = await this.administradorRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id_admnistrador,
|
id_administrador,
|
||||||
},
|
|
||||||
relations: ['tipoUser'],
|
|
||||||
select: {
|
|
||||||
id_admnistrador: true,
|
|
||||||
correo: true,
|
|
||||||
id_tipo_user: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -161,9 +156,20 @@ export class AdministradorService {
|
|||||||
return administradorFound;
|
return administradorFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAdministrador(id_admnistrador: number) {
|
async getAdminById(id_administrador: number) {
|
||||||
|
const administradorFound = await this.administradorRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id_administrador,
|
||||||
|
},
|
||||||
|
relations: ['tipoUser'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return administradorFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteAdministrador(id_administrador: number) {
|
||||||
const result = await this.administradorRepository.delete({
|
const result = await this.administradorRepository.delete({
|
||||||
id_admnistrador,
|
id_administrador,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.affected === 0) {
|
if (result.affected === 0) {
|
||||||
@@ -177,12 +183,12 @@ export class AdministradorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateAdministrador(
|
async updateAdministrador(
|
||||||
id_admnistrador: number,
|
id_administrador: number,
|
||||||
administrador: UpdateAdministradorDto,
|
administrador: UpdateAdministradorDto,
|
||||||
) {
|
) {
|
||||||
const administradorFound = await this.administradorRepository.findOne({
|
const administradorFound = await this.administradorRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id_admnistrador,
|
id_administrador,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -10,9 +10,7 @@ import { applyDecorators } from '@nestjs/common';
|
|||||||
|
|
||||||
export class AdministradorApiDocumentation {
|
export class AdministradorApiDocumentation {
|
||||||
// Decorador para toda la clase del controlador
|
// Decorador para toda la clase del controlador
|
||||||
static ApiController = applyDecorators(
|
static ApiController = applyDecorators(ApiTags('Administrador'));
|
||||||
ApiTags('Administrador'),
|
|
||||||
);
|
|
||||||
// Documentación para crear un administrador
|
// Documentación para crear un administrador
|
||||||
static ApiCreate = applyDecorators(
|
static ApiCreate = applyDecorators(
|
||||||
ApiOperation({
|
ApiOperation({
|
||||||
@@ -26,6 +24,11 @@ export class AdministradorApiDocumentation {
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['correo', 'password', 'id_tipo_user'],
|
required: ['correo', 'password', 'id_tipo_user'],
|
||||||
properties: {
|
properties: {
|
||||||
|
nombre_usuario: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'mike',
|
||||||
|
description: 'Nombre de usuario del administrador',
|
||||||
|
},
|
||||||
correo: {
|
correo: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
format: 'email',
|
format: 'email',
|
||||||
@@ -2,18 +2,31 @@ import { IsEmail, IsNotEmpty, IsNumber, MinLength } from 'class-validator';
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class CreateAdministradorDto {
|
export class CreateAdministradorDto {
|
||||||
@ApiProperty({ example: 'admin@example.com', description: 'Correo del administrador' })
|
@ApiProperty({
|
||||||
@IsEmail({}, { message: 'El correo no es válido' })
|
example: 'admin@example.com',
|
||||||
@IsNotEmpty({ message: 'El correo es requerido' })
|
description: 'Correo del administrador',
|
||||||
correo: string;
|
})
|
||||||
|
@IsEmail({}, { message: 'El correo no es válido' })
|
||||||
|
@IsNotEmpty({ message: 'El correo es requerido' })
|
||||||
|
correo: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'Password123', description: 'Contraseña del administrador' })
|
@ApiProperty({
|
||||||
@MinLength(6, { message: 'La contraseña debe tener al menos 6 caracteres' })
|
example: 'mike',
|
||||||
@IsNotEmpty({ message: 'La contraseña es requerida' })
|
description: 'Nombre de usuario del administrador',
|
||||||
password: string;
|
})
|
||||||
|
@IsNotEmpty({ message: 'El nombre de usuario es requerido' })
|
||||||
|
nombre_usuario: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 1, description: 'ID del tipo de usuario' })
|
@ApiProperty({
|
||||||
@IsNumber({}, { message: 'El tipo de usuario debe ser un número' })
|
example: 'Password123',
|
||||||
@IsNotEmpty({ message: 'El tipo de usuario es requerido' })
|
description: 'Contraseña del administrador',
|
||||||
id_tipo_user: number;
|
})
|
||||||
}
|
@MinLength(6, { message: 'La contraseña debe tener al menos 6 caracteres' })
|
||||||
|
@IsNotEmpty({ message: 'La contraseña es requerida' })
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 1, description: 'ID del tipo de usuario' })
|
||||||
|
@IsNumber({}, { message: 'El tipo de usuario debe ser un número' })
|
||||||
|
@IsNotEmpty({ message: 'El tipo de usuario es requerido' })
|
||||||
|
id_tipo_user: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
|
import { IsEmail, IsNotEmpty } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class LoginAdministradorDto {
|
export class LoginAdministradorDto {
|
||||||
@ApiProperty({ example: 'admin@example.com', description: 'Correo del administrador' })
|
@ApiProperty({
|
||||||
@IsEmail({}, { message: 'El correo no es válido' })
|
example: 'mike',
|
||||||
@IsNotEmpty({ message: 'El correo es requerido' })
|
description: 'Nombre de usuario del administrador',
|
||||||
correo: string;
|
})
|
||||||
|
@IsNotEmpty({ message: 'El nombre de usuario es requerido' })
|
||||||
|
nombre_usuario: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'Password123', description: 'Contraseña del administrador' })
|
@ApiProperty({
|
||||||
@IsNotEmpty({ message: 'La contraseña es requerida' })
|
example: 'Password123',
|
||||||
password: string;
|
description: 'Contraseña del administrador',
|
||||||
}
|
})
|
||||||
|
@IsNotEmpty({ message: 'La contraseña es requerida' })
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
import { TipoUser } from 'src/tipo_user/entities/tipo_user.entity';
|
import { TipoUser } from 'src/tipo_user/entities/tipo_user.entity';
|
||||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
@Entity('administrador')
|
@Entity('administrador')
|
||||||
export class Administrador {
|
export class Administrador {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id_admnistrador: number;
|
id_administrador: number;
|
||||||
|
|
||||||
|
@Column({ unique: true })
|
||||||
|
nombre_usuario: string;
|
||||||
|
|
||||||
@Column({ unique: true })
|
@Column({ unique: true })
|
||||||
correo: string;
|
correo: string;
|
||||||
@@ -12,9 +21,10 @@ export class Administrador {
|
|||||||
@Column()
|
@Column()
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
@Column()
|
@Column({ type: 'int' })
|
||||||
id_tipo_user: number;
|
id_tipo_user: number;
|
||||||
|
|
||||||
@ManyToOne(() => TipoUser, (tipoUser) => tipoUser.administrador)
|
@ManyToOne(() => TipoUser, (tipoUser) => tipoUser.administrador)
|
||||||
tipoUser: TipoUser[];
|
@JoinColumn({ name: 'id_tipo_user' })
|
||||||
|
tipoUser: TipoUser;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { ParticipanteEventoModule } from './participante_evento/participante_eve
|
|||||||
import { ValidacionesModule } from './validaciones/validaciones.module';
|
import { ValidacionesModule } from './validaciones/validaciones.module';
|
||||||
import { AlumnosModule } from './alumnos/alumnos.module';
|
import { AlumnosModule } from './alumnos/alumnos.module';
|
||||||
import { TrabajadoresModule } from './trabajadores/trabajadores.module';
|
import { TrabajadoresModule } from './trabajadores/trabajadores.module';
|
||||||
|
import { TipoEventoModule } from './tipo_evento/tipo_evento.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -62,6 +63,7 @@ import { TrabajadoresModule } from './trabajadores/trabajadores.module';
|
|||||||
SeccionModule,
|
SeccionModule,
|
||||||
SeccionPreguntaModule,
|
SeccionPreguntaModule,
|
||||||
TipoCuestionarioModule,
|
TipoCuestionarioModule,
|
||||||
|
TipoEventoModule,
|
||||||
TipoPreguntaModule,
|
TipoPreguntaModule,
|
||||||
TipoUserModule,
|
TipoUserModule,
|
||||||
ValidacionesModule,
|
ValidacionesModule,
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
export const ROLES_KEY = 'roles';
|
||||||
|
export const Roles = (...roles: ('administrador' | 'staff')[]) =>
|
||||||
|
SetMetadata(ROLES_KEY, roles);
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
Injectable,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { AdministradorService } from 'src/administrador/administrador.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtValidationGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private jwtService: JwtService,
|
||||||
|
private administradorService: AdministradorService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const authHeader = request.headers.authorization;
|
||||||
|
|
||||||
|
if (!authHeader) {
|
||||||
|
throw new UnauthorizedException('Token no proporcionado');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = authHeader.split(' ')[1];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decodedToken = this.jwtService.verify(token, {
|
||||||
|
secret: process.env.JWT_SECRET,
|
||||||
|
});
|
||||||
|
|
||||||
|
const administrador = await this.administradorService.getAdminById(
|
||||||
|
decodedToken.sub,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!administrador) {
|
||||||
|
throw new UnauthorizedException('Usuario no encontrado');
|
||||||
|
}
|
||||||
|
|
||||||
|
request.user = administrador;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name === 'TokenExpiredError') {
|
||||||
|
throw new UnauthorizedException(
|
||||||
|
`La sesión ha expirado, inicia sesión nuevamente.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new UnauthorizedException('Token inválido');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
ForbiddenException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { ROLES_KEY } from '../decorators/roles.decorator';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RolesGuard implements CanActivate {
|
||||||
|
constructor(private reflector: Reflector) {}
|
||||||
|
|
||||||
|
canActivate(context: ExecutionContext): boolean {
|
||||||
|
const requiredRoles = this.reflector.get<string[]>(
|
||||||
|
ROLES_KEY,
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
if (!requiredRoles) return true; // Si no hay roles definidos, permitir acceso.
|
||||||
|
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const user = request.user; // Usuario ya validado en JwtValidationGuard
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ForbiddenException('Usuario no autenticado');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener el tipo de usuario
|
||||||
|
const tipo_usuario = user.tipoUser.tipo;
|
||||||
|
|
||||||
|
if (!tipo_usuario) {
|
||||||
|
throw new ForbiddenException('No se pudo determinar el tipo de usuario');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar si el usuario tiene uno de los roles requeridos
|
||||||
|
const hasRole = requiredRoles.includes(tipo_usuario);
|
||||||
|
|
||||||
|
if (!hasRole) {
|
||||||
|
throw new ForbiddenException(
|
||||||
|
'No tienes permisos para acceder a este recurso',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,15 +19,14 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async validate(payload: any) {
|
async validate(payload: any) {
|
||||||
const { sub: id_admnistrador } = payload;
|
const { sub: id_administrador } = payload;
|
||||||
|
|
||||||
const administrador = await this.administradorRepository.findOne({
|
const administrador = await this.administradorRepository.findOne({
|
||||||
where: { id_admnistrador },
|
where: { id_administrador },
|
||||||
select: {
|
select: {
|
||||||
id_admnistrador: true,
|
id_administrador: true,
|
||||||
correo: true,
|
correo: true,
|
||||||
id_tipo_user: true
|
},
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!administrador) {
|
if (!administrador) {
|
||||||
@@ -36,4 +35,4 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
|||||||
|
|
||||||
return administrador;
|
return administrador;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export class CuestionarioService {
|
|||||||
cuestionario.fecha_fin = new Date(createCuestionarioDto.fecha_fin);
|
cuestionario.fecha_fin = new Date(createCuestionarioDto.fecha_fin);
|
||||||
cuestionario.id_tipo_cuestionario =
|
cuestionario.id_tipo_cuestionario =
|
||||||
createCuestionarioDto.id_tipo_cuestionario;
|
createCuestionarioDto.id_tipo_cuestionario;
|
||||||
|
cuestionario.id_tipo_evento = createCuestionarioDto.id_tipo_evento;
|
||||||
cuestionario.contador_secciones =
|
cuestionario.contador_secciones =
|
||||||
createCuestionarioDto.secciones?.length || 0;
|
createCuestionarioDto.secciones?.length || 0;
|
||||||
cuestionario.editable = true;
|
cuestionario.editable = true;
|
||||||
@@ -227,6 +228,7 @@ export class CuestionarioService {
|
|||||||
fecha_inicio: new Date(cuestionario.fecha_inicio),
|
fecha_inicio: new Date(cuestionario.fecha_inicio),
|
||||||
fecha_fin: new Date(cuestionario.fecha_fin),
|
fecha_fin: new Date(cuestionario.fecha_fin),
|
||||||
id_tipo_cuestionario: cuestionario.id_tipo_cuestionario,
|
id_tipo_cuestionario: cuestionario.id_tipo_cuestionario,
|
||||||
|
id_tipo_evento: cuestionario.id_tipo_evento,
|
||||||
contador_secciones: cuestionario.secciones?.length || 0,
|
contador_secciones: cuestionario.secciones?.length || 0,
|
||||||
editable: true,
|
editable: true,
|
||||||
id_evento: eventoExistente.id_evento, // Asociar el evento creado
|
id_evento: eventoExistente.id_evento, // Asociar el evento creado
|
||||||
|
|||||||
@@ -63,6 +63,14 @@ export class CreateCuestionarioDto {
|
|||||||
@IsNumber()
|
@IsNumber()
|
||||||
id_tipo_cuestionario: number;
|
id_tipo_cuestionario: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'ID del tipo de evento',
|
||||||
|
example: 1,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
id_tipo_evento: number;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Cupo máximo de participantes',
|
description: 'Cupo máximo de participantes',
|
||||||
example: 100,
|
example: 100,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Evento } from 'src/evento/entities/evento.entity';
|
|||||||
import { ParticipanteEvento } from 'src/participante_evento/entities/participante_evento.entity';
|
import { ParticipanteEvento } from 'src/participante_evento/entities/participante_evento.entity';
|
||||||
import { TipoCuestionario } from 'src/tipo_cuestionario/entities/tipo_cuestionario.entity';
|
import { TipoCuestionario } from 'src/tipo_cuestionario/entities/tipo_cuestionario.entity';
|
||||||
import { CuestionarioRespondido } from 'src/cuestionario_respondido/entities/cuestionario_respondido.entity';
|
import { CuestionarioRespondido } from 'src/cuestionario_respondido/entities/cuestionario_respondido.entity';
|
||||||
|
import { TipoEvento } from 'src/tipo_evento/entities/tipo_evento.entity';
|
||||||
|
|
||||||
@Entity('cuestionario')
|
@Entity('cuestionario')
|
||||||
export class Cuestionario {
|
export class Cuestionario {
|
||||||
@@ -44,14 +45,13 @@ export class Cuestionario {
|
|||||||
@Column({ type: 'int' })
|
@Column({ type: 'int' })
|
||||||
id_tipo_cuestionario: number;
|
id_tipo_cuestionario: number;
|
||||||
|
|
||||||
|
@Column({ type: 'int', nullable: true })
|
||||||
|
id_tipo_evento: number;
|
||||||
|
|
||||||
@Column({ type: 'int' })
|
@Column({ type: 'int' })
|
||||||
id_evento: number;
|
id_evento: number;
|
||||||
|
|
||||||
// Relaciones
|
// Relaciones
|
||||||
@ManyToOne(() => Cuestionario, { nullable: true })
|
|
||||||
@JoinColumn({ name: 'id_cuestionario_original' })
|
|
||||||
cuestionarioOriginal?: Cuestionario;
|
|
||||||
|
|
||||||
@ManyToOne(() => Evento, (evento) => evento.cuestionarios, { nullable: true })
|
@ManyToOne(() => Evento, (evento) => evento.cuestionarios, { nullable: true })
|
||||||
@JoinColumn({ name: 'id_evento' })
|
@JoinColumn({ name: 'id_evento' })
|
||||||
evento?: Evento;
|
evento?: Evento;
|
||||||
@@ -62,6 +62,12 @@ export class Cuestionario {
|
|||||||
@JoinColumn({ name: 'id_tipo_cuestionario' })
|
@JoinColumn({ name: 'id_tipo_cuestionario' })
|
||||||
tipoCuestionario: TipoCuestionario;
|
tipoCuestionario: TipoCuestionario;
|
||||||
|
|
||||||
|
@ManyToOne(() => TipoEvento, (tipo) => tipo.cuestionarios, {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
|
@JoinColumn({ name: 'id_tipo_evento' })
|
||||||
|
tipoEvento: TipoEvento;
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => CuestionarioSeccion,
|
() => CuestionarioSeccion,
|
||||||
(cuestionarioSeccion) => cuestionarioSeccion.cuestionario,
|
(cuestionarioSeccion) => cuestionarioSeccion.cuestionario,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||||||
import { CuestionarioRespondido } from './entities/cuestionario_respondido.entity';
|
import { CuestionarioRespondido } from './entities/cuestionario_respondido.entity';
|
||||||
import { CuestionarioModule } from '../cuestionario/cuestionario.module';
|
import { CuestionarioModule } from '../cuestionario/cuestionario.module';
|
||||||
import { ParticipanteModule } from '../participante/participante.module';
|
import { ParticipanteModule } from '../participante/participante.module';
|
||||||
|
import { QrModule } from '../qr/qr.module';
|
||||||
import { Participante } from '../participante/entities/participante.entity';
|
import { Participante } from '../participante/entities/participante.entity';
|
||||||
import { Cuestionario } from '../cuestionario/entities/cuestionario.entity';
|
import { Cuestionario } from '../cuestionario/entities/cuestionario.entity';
|
||||||
import { Pregunta } from '../pregunta/entities/pregunta.entity';
|
import { Pregunta } from '../pregunta/entities/pregunta.entity';
|
||||||
@@ -29,6 +30,7 @@ import { ValidacionesModule } from '../validaciones/validaciones.module';
|
|||||||
CuestionarioModule,
|
CuestionarioModule,
|
||||||
ParticipanteModule,
|
ParticipanteModule,
|
||||||
ValidacionesModule,
|
ValidacionesModule,
|
||||||
|
QrModule,
|
||||||
],
|
],
|
||||||
controllers: [CuestionarioRespondidoController],
|
controllers: [CuestionarioRespondidoController],
|
||||||
providers: [CuestionarioRespondidoService],
|
providers: [CuestionarioRespondidoService],
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { RespuestaParticipanteCerrada } from '../respuesta_participante_cerrada/
|
|||||||
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
|
import { PreguntaOpcion } from '../pregunta_opcion/entities/pregunta_opcion.entity';
|
||||||
import { Opcion } from '../opcion/entities/opcion.entity';
|
import { Opcion } from '../opcion/entities/opcion.entity';
|
||||||
import { ValidadorRespuestasService } from '../validaciones/validador-respuestas.service';
|
import { ValidadorRespuestasService } from '../validaciones/validador-respuestas.service';
|
||||||
|
import { QrTokenService } from '../qr/qr-token.service';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import * as QRCode from 'qrcode';
|
import * as QRCode from 'qrcode';
|
||||||
import { CuestionarioService } from 'src/cuestionario/cuestionario.service';
|
import { CuestionarioService } from 'src/cuestionario/cuestionario.service';
|
||||||
@@ -39,6 +40,7 @@ export class CuestionarioRespondidoService {
|
|||||||
private dataSource: DataSource,
|
private dataSource: DataSource,
|
||||||
private validadorRespuestasService: ValidadorRespuestasService,
|
private validadorRespuestasService: ValidadorRespuestasService,
|
||||||
private cuestionarioService: CuestionarioService,
|
private cuestionarioService: CuestionarioService,
|
||||||
|
private qrTokenService: QrTokenService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
create(createCuestionarioRespondidoDto: CreateCuestionarioRespondidoDto) {
|
create(createCuestionarioRespondidoDto: CreateCuestionarioRespondidoDto) {
|
||||||
@@ -99,22 +101,25 @@ export class CuestionarioRespondidoService {
|
|||||||
relations: ['cuestionario', 'participante'],
|
relations: ['cuestionario', 'participante'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (cuestionarioRespondidoExistente) {
|
if (cuestionarioRespondidoExistente) {
|
||||||
await queryRunner.rollbackTransaction();
|
await queryRunner.rollbackTransaction();
|
||||||
|
|
||||||
|
let qrBuffer: Buffer | undefined;
|
||||||
|
let qrToken: string | undefined;
|
||||||
|
|
||||||
// Aunque ya haya respondido, enviamos el correo nuevamente
|
// Aunque ya haya respondido, enviamos el correo nuevamente
|
||||||
if (cuestionario && cuestionario.evento) {
|
if (cuestionario && cuestionario.evento) {
|
||||||
try {
|
try {
|
||||||
// Generar datos para el QR
|
// Generar token para el QR
|
||||||
const datosQR = {
|
qrToken = this.qrTokenService.generateQrToken(
|
||||||
id_participante: participante.id_participante,
|
participante.id_participante,
|
||||||
id_evento: cuestionario.evento.id_evento,
|
cuestionario.evento.id_evento,
|
||||||
id_cuestionario: cuestionario.id_cuestionario,
|
cuestionario.id_cuestionario,
|
||||||
};
|
);
|
||||||
|
|
||||||
// Generar código QR
|
// Generar código QR con el token
|
||||||
const qrJsonData = JSON.stringify(datosQR);
|
qrBuffer = await QRCode.toBuffer(qrToken);
|
||||||
const qrBuffer = await QRCode.toBuffer(qrJsonData);
|
|
||||||
|
|
||||||
// Enviar correo con el QR
|
// Enviar correo con el QR
|
||||||
const urlApiCorreos = process.env.url_api_correos;
|
const urlApiCorreos = process.env.url_api_correos;
|
||||||
@@ -167,14 +172,10 @@ export class CuestionarioRespondidoService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
registrado: true,
|
||||||
message: `El participante con correo ${submitDto.correo} ya había respondido el cuestionario ${cuestionario.nombre_form}. Se ha reenviado el correo con el código QR.`,
|
message: `El participante con correo ${submitDto.correo} ya había respondido el cuestionario ${cuestionario.nombre_form}. Se ha reenviado el correo con el código QR.`,
|
||||||
datos_qr: cuestionario.evento
|
qr_token: qrToken,
|
||||||
? {
|
qr_buffer: qrBuffer ? qrBuffer.toString('base64') : undefined,
|
||||||
id_participante: participante.id_participante,
|
|
||||||
id_evento: cuestionario.evento.id_evento,
|
|
||||||
id_cuestionario: cuestionario.id_cuestionario,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +287,9 @@ export class CuestionarioRespondidoService {
|
|||||||
id_cuestionario: number;
|
id_cuestionario: number;
|
||||||
} | null = null;
|
} | null = null;
|
||||||
|
|
||||||
|
let qrBuffer: Buffer | undefined;
|
||||||
|
let qrToken: string | undefined;
|
||||||
|
|
||||||
// Capturamos los datos para el QR
|
// Capturamos los datos para el QR
|
||||||
if (cuestionario.evento && cuestionario.evento.id_evento) {
|
if (cuestionario.evento && cuestionario.evento.id_evento) {
|
||||||
datosQR = {
|
datosQR = {
|
||||||
@@ -331,9 +335,14 @@ export class CuestionarioRespondidoService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generar código QR
|
// Generar token para el QR
|
||||||
const qrJsonData = JSON.stringify(datosQR);
|
qrToken = this.qrTokenService.generateQrToken(
|
||||||
const qrBuffer = await QRCode.toBuffer(qrJsonData);
|
participante.id_participante,
|
||||||
|
cuestionario.evento.id_evento,
|
||||||
|
cuestionario.id_cuestionario,
|
||||||
|
);
|
||||||
|
|
||||||
|
qrBuffer = await QRCode.toBuffer(qrToken);
|
||||||
|
|
||||||
// Enviar correo con el QR
|
// Enviar correo con el QR
|
||||||
const urlApiCorreos = process.env.url_api_correos;
|
const urlApiCorreos = process.env.url_api_correos;
|
||||||
@@ -388,9 +397,12 @@ export class CuestionarioRespondidoService {
|
|||||||
// Preparar respuesta
|
// Preparar respuesta
|
||||||
const response: any = {
|
const response: any = {
|
||||||
success: true,
|
success: true,
|
||||||
|
registrado: false,
|
||||||
message: 'Respuestas guardadas correctamente',
|
message: 'Respuestas guardadas correctamente',
|
||||||
cuestionarioRespondidoId:
|
cuestionarioRespondidoId:
|
||||||
savedCuestionarioRespondido.idCuestionarioRespondido,
|
savedCuestionarioRespondido.idCuestionarioRespondido,
|
||||||
|
qr_token: qrToken,
|
||||||
|
qr_buffer: qrBuffer ? qrBuffer.toString('base64') : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Incluir IDs para generar QR si hay evento asociado
|
// Incluir IDs para generar QR si hay evento asociado
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { SeccionPregunta } from 'src/seccion_pregunta/entities/seccion_pregunta.
|
|||||||
import { TipoCuestionario } from 'src/tipo_cuestionario/entities/tipo_cuestionario.entity';
|
import { TipoCuestionario } from 'src/tipo_cuestionario/entities/tipo_cuestionario.entity';
|
||||||
import { TipoPregunta } from 'src/tipo_pregunta/entities/tipo_pregunta.entity';
|
import { TipoPregunta } from 'src/tipo_pregunta/entities/tipo_pregunta.entity';
|
||||||
import { TipoUser } from 'src/tipo_user/entities/tipo_user.entity';
|
import { TipoUser } from 'src/tipo_user/entities/tipo_user.entity';
|
||||||
|
import { TipoEvento } from 'src/tipo_evento/entities/tipo_evento.entity';
|
||||||
|
|
||||||
export const createMainDbConfig = async (
|
export const createMainDbConfig = async (
|
||||||
configService: ConfigService,
|
configService: ConfigService,
|
||||||
@@ -47,6 +48,7 @@ export const createMainDbConfig = async (
|
|||||||
Seccion,
|
Seccion,
|
||||||
SeccionPregunta,
|
SeccionPregunta,
|
||||||
TipoCuestionario,
|
TipoCuestionario,
|
||||||
|
TipoEvento,
|
||||||
TipoPregunta,
|
TipoPregunta,
|
||||||
TipoUser,
|
TipoUser,
|
||||||
],
|
],
|
||||||
@@ -54,7 +56,7 @@ export const createMainDbConfig = async (
|
|||||||
retryDelay: 3000,
|
retryDelay: 3000,
|
||||||
connectTimeout: 30000,
|
connectTimeout: 30000,
|
||||||
|
|
||||||
synchronize: configService.get<string>('SYNCHRONIZE') === 'true',
|
synchronize: configService.get<string>('SYNCHRONIZE') === 'false',
|
||||||
dropSchema: configService.get<string>('DROP_SCHEMA') === 'true',
|
dropSchema: configService.get<string>('DROP_SCHEMA') === 'true',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Expose, Type } from 'class-transformer';
|
import { Expose, Type } from 'class-transformer';
|
||||||
import { TipoCuestionarioOutputDto } from 'src/tipo_cuestionario/dto/outputs.dto';
|
import { TipoCuestionarioOutputDto } from 'src/tipo_cuestionario/dto/outputs.dto';
|
||||||
|
import { TipoEventoOutputDto } from 'src/tipo_evento/dto/outputs.dto';
|
||||||
|
|
||||||
export class CuestionarioConCupoDto {
|
export class CuestionarioConCupoDto {
|
||||||
@Expose()
|
@Expose()
|
||||||
@@ -35,6 +36,10 @@ export class CuestionarioConCupoDto {
|
|||||||
@Expose()
|
@Expose()
|
||||||
@Type(() => TipoCuestionarioOutputDto)
|
@Type(() => TipoCuestionarioOutputDto)
|
||||||
tipoCuestionario: TipoCuestionarioOutputDto;
|
tipoCuestionario: TipoCuestionarioOutputDto;
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
@Type(() => TipoEventoOutputDto)
|
||||||
|
tipoEvento: TipoEventoOutputDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventoConCuestionariosDto {
|
export class EventoConCuestionariosDto {
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity';
|
import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity';
|
||||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
import { TipoEvento } from 'src/tipo_evento/entities/tipo_evento.entity';
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Evento {
|
export class Evento {
|
||||||
@@ -29,4 +37,10 @@ export class Evento {
|
|||||||
|
|
||||||
@OneToMany(() => Cuestionario, (cuestionario) => cuestionario.evento)
|
@OneToMany(() => Cuestionario, (cuestionario) => cuestionario.evento)
|
||||||
cuestionarios: Cuestionario[];
|
cuestionarios: Cuestionario[];
|
||||||
|
|
||||||
|
@ManyToOne(() => TipoEvento, (tipo) => tipo.eventos, {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
|
@JoinColumn({ name: 'id_tipo_evento' })
|
||||||
|
tipoEvento: TipoEvento;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { IsString, IsNotEmpty } from 'class-validator';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class RegistrarAsistenciaQrDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Token JWT del código QR escaneado',
|
||||||
|
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
qr_token: string;
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import { ParticipanteEventoService } from './participante_evento.service';
|
|||||||
import { ParticipanteEvento } from './entities/participante_evento.entity';
|
import { ParticipanteEvento } from './entities/participante_evento.entity';
|
||||||
import { CreateParticipanteEventoDto } from './dto/create-participante_evento.dto';
|
import { CreateParticipanteEventoDto } from './dto/create-participante_evento.dto';
|
||||||
import { UpdateParticipanteEventoDto } from './dto/update.participante_evento.dto';
|
import { UpdateParticipanteEventoDto } from './dto/update.participante_evento.dto';
|
||||||
|
import { RegistrarAsistenciaQrDto } from './dto/registrar-asistencia-qr.dto';
|
||||||
import { ApiTags, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
|
import { ApiTags, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
|
||||||
|
|
||||||
@ApiTags('Participantes-Eventos')
|
@ApiTags('Participantes-Eventos')
|
||||||
@@ -20,7 +21,11 @@ export class ParticipanteEventoController {
|
|||||||
constructor(private participanteEventoService: ParticipanteEventoService) {}
|
constructor(private participanteEventoService: ParticipanteEventoService) {}
|
||||||
|
|
||||||
@ApiOperation({ summary: 'Obtener participantes por evento' })
|
@ApiOperation({ summary: 'Obtener participantes por evento' })
|
||||||
@ApiParam({ name: 'id_cuestionario', description: 'ID del evento', type: 'number' })
|
@ApiParam({
|
||||||
|
name: 'id_cuestionario',
|
||||||
|
description: 'ID del evento',
|
||||||
|
type: 'number',
|
||||||
|
})
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
status: 200,
|
status: 200,
|
||||||
description: 'Lista de participantes que están registrados en el evento',
|
description: 'Lista de participantes que están registrados en el evento',
|
||||||
@@ -220,6 +225,44 @@ export class ParticipanteEventoController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Registrar asistencia usando token QR',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'Asistencia registrada correctamente mediante token QR',
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
success: { type: 'boolean' },
|
||||||
|
message: { type: 'string' },
|
||||||
|
data: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
participante: { type: 'string' },
|
||||||
|
fecha_asistencia: { type: 'string', format: 'date-time' },
|
||||||
|
id_evento: { type: 'number' },
|
||||||
|
id_cuestionario: { type: 'number' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 400,
|
||||||
|
description: 'Token QR inválido o expirado',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 404,
|
||||||
|
description: 'Participante no encontrado o no registrado en el evento',
|
||||||
|
})
|
||||||
|
@Post('asistencia-qr')
|
||||||
|
registrarAsistenciaConToken(@Body() body: { qr_token: string }) {
|
||||||
|
return this.participanteEventoService.registrarAsistenciaConToken(
|
||||||
|
body.qr_token,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: 'Eliminar un registro participante-evento' })
|
@ApiOperation({ summary: 'Eliminar un registro participante-evento' })
|
||||||
@ApiParam({ name: 'id', description: 'ID del participante', type: 'number' })
|
@ApiParam({ name: 'id', description: 'ID del participante', type: 'number' })
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import { ParticipanteEvento } from './entities/participante_evento.entity';
|
|||||||
import { Evento } from 'src/evento/entities/evento.entity';
|
import { Evento } from 'src/evento/entities/evento.entity';
|
||||||
import { Participante } from 'src/participante/entities/participante.entity';
|
import { Participante } from 'src/participante/entities/participante.entity';
|
||||||
import { CuestionarioModule } from 'src/cuestionario/cuestionario.module';
|
import { CuestionarioModule } from 'src/cuestionario/cuestionario.module';
|
||||||
|
import { QrModule } from 'src/qr/qr.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([ParticipanteEvento, Evento, Participante]),
|
TypeOrmModule.forFeature([ParticipanteEvento, Evento, Participante]),
|
||||||
CuestionarioModule,
|
CuestionarioModule,
|
||||||
|
QrModule,
|
||||||
],
|
],
|
||||||
controllers: [ParticipanteEventoController],
|
controllers: [ParticipanteEventoController],
|
||||||
providers: [ParticipanteEventoService],
|
providers: [ParticipanteEventoService],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { UpdateParticipanteEventoDto } from './dto/update.participante_evento.dt
|
|||||||
import { Evento } from 'src/evento/entities/evento.entity';
|
import { Evento } from 'src/evento/entities/evento.entity';
|
||||||
import { Participante } from 'src/participante/entities/participante.entity';
|
import { Participante } from 'src/participante/entities/participante.entity';
|
||||||
import { CuestionarioService } from 'src/cuestionario/cuestionario.service';
|
import { CuestionarioService } from 'src/cuestionario/cuestionario.service';
|
||||||
|
import { QrTokenService } from 'src/qr/qr-token.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ParticipanteEventoService {
|
export class ParticipanteEventoService {
|
||||||
@@ -17,6 +18,7 @@ export class ParticipanteEventoService {
|
|||||||
@InjectRepository(Participante)
|
@InjectRepository(Participante)
|
||||||
private participanteRepository: Repository<Participante>,
|
private participanteRepository: Repository<Participante>,
|
||||||
private readonly cuestionarioService: CuestionarioService,
|
private readonly cuestionarioService: CuestionarioService,
|
||||||
|
private readonly qrTokenService: QrTokenService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createParticipanteEvento(
|
async createParticipanteEvento(
|
||||||
@@ -175,4 +177,83 @@ export class ParticipanteEventoService {
|
|||||||
participante_eventoFound.asistio = true;
|
participante_eventoFound.asistio = true;
|
||||||
return this.participanteEventoRepository.save(participante_eventoFound);
|
return this.participanteEventoRepository.save(participante_eventoFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra la asistencia usando un token QR
|
||||||
|
* @param qrToken Token JWT del código QR
|
||||||
|
* @returns Resultado del registro de asistencia
|
||||||
|
*/
|
||||||
|
async registrarAsistenciaConToken(qrToken: string) {
|
||||||
|
// Validar el token QR
|
||||||
|
const tokenData = this.qrTokenService.validateQrToken(qrToken);
|
||||||
|
|
||||||
|
if (!tokenData) {
|
||||||
|
throw new HttpException(
|
||||||
|
'Token QR inválido o expirado',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id_participante, id_evento, id_cuestionario } = tokenData;
|
||||||
|
|
||||||
|
// Verificar que el participante existe
|
||||||
|
const participante = await this.participanteRepository.findOne({
|
||||||
|
where: { id_participante },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!participante) {
|
||||||
|
throw new HttpException(
|
||||||
|
`Participante con ID ${id_participante} no encontrado`,
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buscar el registro participante-evento
|
||||||
|
const participanteEvento = await this.participanteEventoRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id_participante,
|
||||||
|
id_cuestionario,
|
||||||
|
},
|
||||||
|
relations: ['participante'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!participanteEvento) {
|
||||||
|
throw new HttpException(
|
||||||
|
'El participante no está registrado en este evento',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si ya había registrado asistencia
|
||||||
|
if (participanteEvento.asistio && participanteEvento.fecha_asistencia) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message:
|
||||||
|
'El participante ya había registrado su asistencia previamente',
|
||||||
|
data: {
|
||||||
|
participante: participante.correo,
|
||||||
|
fecha_asistencia_previa: participanteEvento.fecha_asistencia,
|
||||||
|
fecha_actual: new Date(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registrar la asistencia
|
||||||
|
participanteEvento.fecha_asistencia = new Date();
|
||||||
|
participanteEvento.asistio = true;
|
||||||
|
|
||||||
|
const resultado =
|
||||||
|
await this.participanteEventoRepository.save(participanteEvento);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Asistencia registrada exitosamente',
|
||||||
|
data: {
|
||||||
|
participante: participante.correo,
|
||||||
|
fecha_asistencia: resultado.fecha_asistencia,
|
||||||
|
id_evento,
|
||||||
|
id_cuestionario,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { IsString, IsNotEmpty } from 'class-validator';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class ValidateQrTokenDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Token JWT del código QR',
|
||||||
|
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class QrTokenService {
|
||||||
|
constructor(
|
||||||
|
private jwtService: JwtService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Genera un token JWT específico para el QR de asistencia
|
||||||
|
* @param id_participante ID del participante
|
||||||
|
* @param id_evento ID del evento
|
||||||
|
* @param id_cuestionario ID del cuestionario
|
||||||
|
* @returns Token JWT codificado
|
||||||
|
*/
|
||||||
|
generateQrToken(
|
||||||
|
id_participante: number,
|
||||||
|
id_evento: number,
|
||||||
|
id_cuestionario: number,
|
||||||
|
): string {
|
||||||
|
const payload = {
|
||||||
|
id_participante,
|
||||||
|
id_evento,
|
||||||
|
id_cuestionario,
|
||||||
|
type: 'qr_asistencia',
|
||||||
|
iat: Math.floor(Date.now() / 1000),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Usar una clave secreta específica para QR o la misma que JWT general
|
||||||
|
const secret =
|
||||||
|
this.configService.get<string>('QR_JWT_SECRET') ||
|
||||||
|
this.configService.get<string>('JWT_SECRET', 'tu_clave_secreta');
|
||||||
|
|
||||||
|
return this.jwtService.sign(payload, {
|
||||||
|
secret,
|
||||||
|
expiresIn: '30d', // El QR puede ser válido por 30 días
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valida y decodifica un token de QR
|
||||||
|
* @param token Token JWT a validar
|
||||||
|
* @returns Datos decodificados del token o null si es inválido
|
||||||
|
*/
|
||||||
|
validateQrToken(token: string): {
|
||||||
|
id_participante: number;
|
||||||
|
id_evento: number;
|
||||||
|
id_cuestionario: number;
|
||||||
|
type: string;
|
||||||
|
iat: number;
|
||||||
|
} | null {
|
||||||
|
try {
|
||||||
|
const secret =
|
||||||
|
this.configService.get<string>('QR_JWT_SECRET') ||
|
||||||
|
this.configService.get<string>('JWT_SECRET', 'tu_clave_secreta');
|
||||||
|
|
||||||
|
const decoded = this.jwtService.verify(token, { secret });
|
||||||
|
|
||||||
|
// Verificar que sea un token de tipo QR de asistencia
|
||||||
|
if (decoded.type !== 'qr_asistencia') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id_participante: decoded.id_participante,
|
||||||
|
id_evento: decoded.id_evento,
|
||||||
|
id_cuestionario: decoded.id_cuestionario,
|
||||||
|
type: decoded.type,
|
||||||
|
iat: decoded.iat,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error validando token QR:', error.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si un token QR es válido y no ha expirado
|
||||||
|
* @param token Token a verificar
|
||||||
|
* @returns true si es válido, false si no
|
||||||
|
*/
|
||||||
|
isTokenValid(token: string): boolean {
|
||||||
|
const decoded = this.validateQrToken(token);
|
||||||
|
return decoded !== null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
-4
@@ -10,14 +10,20 @@ import {
|
|||||||
Query,
|
Query,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { QrService } from './qr.service';
|
import { QrService } from './qr.service';
|
||||||
|
import { QrTokenService } from './qr-token.service';
|
||||||
import { Qr } from './qr.entity';
|
import { Qr } from './qr.entity';
|
||||||
import { CreateQrDto } from './dto/create-qr.dto';
|
import { CreateQrDto } from './dto/create-qr.dto';
|
||||||
import { UpdateQrDto } from './dto/update.qr.dto';
|
import { UpdateQrDto } from './dto/update.qr.dto';
|
||||||
import { ApiOperation, ApiParam, ApiQuery } from '@nestjs/swagger';
|
import { ValidateQrTokenDto } from './dto/validate-qr-token.dto';
|
||||||
|
import { ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
@ApiTags('QR')
|
||||||
@Controller('qr')
|
@Controller('qr')
|
||||||
export class QrController {
|
export class QrController {
|
||||||
constructor(private qrService: QrService) {}
|
constructor(
|
||||||
|
private qrService: QrService,
|
||||||
|
private qrTokenService: QrTokenService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Get('generate')
|
@Get('generate')
|
||||||
@ApiOperation({ summary: 'Genera un código QR a partir de un texto' })
|
@ApiOperation({ summary: 'Genera un código QR a partir de un texto' })
|
||||||
@@ -31,16 +37,64 @@ export class QrController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('asistencia/:idParticipante/:idEvento')
|
@Get('asistencia/:idParticipante/:idEvento')
|
||||||
@ApiOperation({ summary: 'Genera un código QR para asistencia con IDs de participante y evento' })
|
@ApiOperation({
|
||||||
|
summary:
|
||||||
|
'Genera un código QR para asistencia con IDs de participante y evento',
|
||||||
|
})
|
||||||
@ApiParam({ name: 'idParticipante', description: 'ID del participante' })
|
@ApiParam({ name: 'idParticipante', description: 'ID del participante' })
|
||||||
@ApiParam({ name: 'idEvento', description: 'ID del evento' })
|
@ApiParam({ name: 'idEvento', description: 'ID del evento' })
|
||||||
async generateAsistenciaQR(
|
async generateAsistenciaQR(
|
||||||
@Param('idParticipante', ParseIntPipe) idParticipante: number,
|
@Param('idParticipante', ParseIntPipe) idParticipante: number,
|
||||||
@Param('idEvento', ParseIntPipe) idEvento: number
|
@Param('idEvento', ParseIntPipe) idEvento: number,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return this.qrService.generateAsistenciaQR(idParticipante, idEvento);
|
return this.qrService.generateAsistenciaQR(idParticipante, idEvento);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('validate-token')
|
||||||
|
@ApiOperation({ summary: 'Valida un token de QR para asistencia' })
|
||||||
|
async validateQrToken(@Body() validateDto: ValidateQrTokenDto) {
|
||||||
|
const tokenData = this.qrTokenService.validateQrToken(validateDto.token);
|
||||||
|
|
||||||
|
if (!tokenData) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: 'Token inválido o expirado',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
data: tokenData,
|
||||||
|
message: 'Token válido',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('generate-token/:idParticipante/:idEvento/:idCuestionario')
|
||||||
|
@ApiOperation({ summary: 'Genera un token JWT para QR de asistencia' })
|
||||||
|
@ApiParam({ name: 'idParticipante', description: 'ID del participante' })
|
||||||
|
@ApiParam({ name: 'idEvento', description: 'ID del evento' })
|
||||||
|
@ApiParam({ name: 'idCuestionario', description: 'ID del cuestionario' })
|
||||||
|
async generateQrToken(
|
||||||
|
@Param('idParticipante', ParseIntPipe) idParticipante: number,
|
||||||
|
@Param('idEvento', ParseIntPipe) idEvento: number,
|
||||||
|
@Param('idCuestionario', ParseIntPipe) idCuestionario: number,
|
||||||
|
) {
|
||||||
|
const token = this.qrTokenService.generateQrToken(
|
||||||
|
idParticipante,
|
||||||
|
idEvento,
|
||||||
|
idCuestionario,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
qr_data: {
|
||||||
|
id_participante: idParticipante,
|
||||||
|
id_evento: idEvento,
|
||||||
|
id_cuestionario: idCuestionario,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
getQrs(): Promise<Qr[]> {
|
getQrs(): Promise<Qr[]> {
|
||||||
return this.qrService.getQrs();
|
return this.qrService.getQrs();
|
||||||
|
|||||||
+6
-2
@@ -1,12 +1,16 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { QrService } from './qr.service';
|
import { QrService } from './qr.service';
|
||||||
import { QrController } from './qr.controller';
|
import { QrController } from './qr.controller';
|
||||||
|
import { QrTokenService } from './qr-token.service';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { Qr } from './qr.entity';
|
import { Qr } from './qr.entity';
|
||||||
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Qr])],
|
imports: [TypeOrmModule.forFeature([Qr]), AuthModule, ConfigModule],
|
||||||
controllers: [QrController],
|
controllers: [QrController],
|
||||||
providers: [QrService]
|
providers: [QrService, QrTokenService],
|
||||||
|
exports: [QrTokenService],
|
||||||
})
|
})
|
||||||
export class QrModule {}
|
export class QrModule {}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Expose } from 'class-transformer';
|
import { Expose } from 'class-transformer';
|
||||||
|
|
||||||
export class TipoCuestionarioOutputDto {
|
export class TipoCuestionarioOutputDto {
|
||||||
@Expose()
|
@Expose()
|
||||||
id_tipo_cuestionario: number;
|
id_tipo_cuestionario: number;
|
||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
tipo_cuestionario: string;
|
tipo_cuestionario: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { IsEnum, IsNotEmpty } from 'class-validator';
|
||||||
|
import { TipoEventoEnum } from '../entities/tipo_evento.entity';
|
||||||
|
|
||||||
|
export class CreateTipoEventoDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsEnum(TipoEventoEnum)
|
||||||
|
tipo_evento: TipoEventoEnum;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Expose } from 'class-transformer';
|
||||||
|
|
||||||
|
export class TipoEventoOutputDto {
|
||||||
|
@Expose()
|
||||||
|
id_tipo_evento: number;
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
tipo_evento: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/mapped-types';
|
||||||
|
import { CreateTipoEventoDto } from './create-tipo_evento.dto';
|
||||||
|
|
||||||
|
export class UpdateTipoEventoDto extends PartialType(CreateTipoEventoDto) {}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Cuestionario } from 'src/cuestionario/entities/cuestionario.entity';
|
||||||
|
import { Evento } from 'src/evento/entities/evento.entity';
|
||||||
|
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
|
||||||
|
|
||||||
|
export enum TipoEventoEnum {
|
||||||
|
ACADEMICO = 'Académico',
|
||||||
|
TALLER_CAPACITACION = 'Taller / Capacitación',
|
||||||
|
CONFERENCIA_CHARLA = 'Conferencia / Charla',
|
||||||
|
PANEL_MESA_REDONDA = 'Panel / Mesa redonda',
|
||||||
|
FERIA_EXPO = 'Feria / Expo',
|
||||||
|
NETWORKING = 'Networking / Vinculación',
|
||||||
|
ENTREVISTA = 'Entrevista / Sesión 1:1',
|
||||||
|
CULTURAL_ARTISTICO = 'Cultural / Artístico',
|
||||||
|
DEPORTIVO = 'Deportivo / Recreativo',
|
||||||
|
INSTITUCIONAL = 'Institucional / Ceremonial',
|
||||||
|
SOCIAL_COMUNITARIO = 'Social / Comunitario',
|
||||||
|
ANIVERSARIO = 'Aniversario',
|
||||||
|
OTRO = 'Otro',
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity('tipo_evento')
|
||||||
|
export class TipoEvento {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id_tipo_evento: number;
|
||||||
|
|
||||||
|
@Column({ type: 'enum', enum: TipoEventoEnum })
|
||||||
|
tipo_evento: TipoEventoEnum;
|
||||||
|
|
||||||
|
@OneToMany(() => Cuestionario, (cuestionario) => cuestionario.tipoEvento)
|
||||||
|
cuestionarios: Cuestionario[];
|
||||||
|
|
||||||
|
@OneToMany(() => Evento, (evento) => evento.tipoEvento)
|
||||||
|
eventos: Evento[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Patch,
|
||||||
|
Param,
|
||||||
|
Delete,
|
||||||
|
ParseIntPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { TipoEventoService } from './tipo_evento.service';
|
||||||
|
import { CreateTipoEventoDto } from './dto/create-tipo_evento.dto';
|
||||||
|
import { UpdateTipoEventoDto } from './dto/update-tipo_evento.dto';
|
||||||
|
|
||||||
|
@Controller('tipo-evento')
|
||||||
|
export class TipoEventoController {
|
||||||
|
constructor(private readonly tipoEventoService: TipoEventoService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
create(@Body() createTipoEventoDto: CreateTipoEventoDto) {
|
||||||
|
return this.tipoEventoService.create(createTipoEventoDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll() {
|
||||||
|
return this.tipoEventoService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(@Param('id', ParseIntPipe) id: number) {
|
||||||
|
return this.tipoEventoService.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
update(
|
||||||
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
@Body() updateTipoEventoDto: UpdateTipoEventoDto,
|
||||||
|
) {
|
||||||
|
return this.tipoEventoService.update(id, updateTipoEventoDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
remove(@Param('id', ParseIntPipe) id: number) {
|
||||||
|
return this.tipoEventoService.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { TipoEvento } from './entities/tipo_evento.entity';
|
||||||
|
import { TipoEventoController } from './tipo_evento.controller';
|
||||||
|
import { TipoEventoService } from './tipo_evento.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([TipoEvento])],
|
||||||
|
controllers: [TipoEventoController],
|
||||||
|
providers: [TipoEventoService],
|
||||||
|
exports: [TipoEventoService],
|
||||||
|
})
|
||||||
|
export class TipoEventoModule {}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
ConflictException,
|
||||||
|
OnModuleInit,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { TipoEvento, TipoEventoEnum } from './entities/tipo_evento.entity';
|
||||||
|
import { CreateTipoEventoDto } from './dto/create-tipo_evento.dto';
|
||||||
|
import { UpdateTipoEventoDto } from './dto/update-tipo_evento.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TipoEventoService implements OnModuleInit {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(TipoEvento)
|
||||||
|
private tipoEventoRepository: Repository<TipoEvento>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
await this.seedTipoEventos();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async seedTipoEventos() {
|
||||||
|
const enumValues = Object.values(TipoEventoEnum);
|
||||||
|
|
||||||
|
for (const tipoEvento of enumValues) {
|
||||||
|
const existingTipo = await this.tipoEventoRepository.findOne({
|
||||||
|
where: { tipo_evento: tipoEvento },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingTipo) {
|
||||||
|
await this.tipoEventoRepository.save({
|
||||||
|
tipo_evento: tipoEvento,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(createTipoEventoDto: CreateTipoEventoDto): Promise<TipoEvento> {
|
||||||
|
const existingTipo = await this.tipoEventoRepository.findOne({
|
||||||
|
where: { tipo_evento: createTipoEventoDto.tipo_evento },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingTipo) {
|
||||||
|
throw new ConflictException('Este tipo de evento ya existe');
|
||||||
|
}
|
||||||
|
|
||||||
|
const tipoEvento = this.tipoEventoRepository.create(createTipoEventoDto);
|
||||||
|
return this.tipoEventoRepository.save(tipoEvento);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(): Promise<TipoEvento[]> {
|
||||||
|
return this.tipoEventoRepository.find({
|
||||||
|
order: { id_tipo_evento: 'ASC' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: number): Promise<TipoEvento> {
|
||||||
|
const tipoEvento = await this.tipoEventoRepository.findOne({
|
||||||
|
where: { id_tipo_evento: id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tipoEvento) {
|
||||||
|
throw new NotFoundException(`Tipo de evento con ID ${id} no encontrado`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tipoEvento;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
id: number,
|
||||||
|
updateTipoEventoDto: UpdateTipoEventoDto,
|
||||||
|
): Promise<TipoEvento> {
|
||||||
|
const tipoEvento = await this.findOne(id);
|
||||||
|
|
||||||
|
if (updateTipoEventoDto.tipo_evento) {
|
||||||
|
const existingTipo = await this.tipoEventoRepository.findOne({
|
||||||
|
where: { tipo_evento: updateTipoEventoDto.tipo_evento },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingTipo && existingTipo.id_tipo_evento !== id) {
|
||||||
|
throw new ConflictException('Este tipo de evento ya existe');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(tipoEvento, updateTipoEventoDto);
|
||||||
|
return this.tipoEventoRepository.save(tipoEvento);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: number): Promise<{ message: string }> {
|
||||||
|
const tipoEvento = await this.findOne(id);
|
||||||
|
|
||||||
|
await this.tipoEventoRepository.remove(tipoEvento);
|
||||||
|
|
||||||
|
return { message: 'Tipo de evento eliminado exitosamente' };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,11 @@ import { Administrador } from 'src/administrador/entities/administrador.entity';
|
|||||||
import { Participante } from 'src/participante/entities/participante.entity';
|
import { Participante } from 'src/participante/entities/participante.entity';
|
||||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
export enum TipoUsuarioEnum {
|
||||||
|
ADMINISTRADOR = 'administrador',
|
||||||
|
STAFF = 'staff',
|
||||||
|
}
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class TipoUser {
|
export class TipoUser {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
Injectable,
|
Injectable,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { TipoUser } from './entities/tipo_user.entity';
|
import { TipoUser, TipoUsuarioEnum } from './entities/tipo_user.entity';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { CreateTipoUserDto } from './dto/create-tipo-user.dto';
|
import { CreateTipoUserDto } from './dto/create-tipo-user.dto';
|
||||||
import { UpdateTipoUserDto } from './dto/update.tipo_user.dto';
|
import { UpdateTipoUserDto } from './dto/update.tipo_user.dto';
|
||||||
@@ -23,6 +23,25 @@ export class TipoUserService {
|
|||||||
private administradorRepository: Repository<Administrador>,
|
private administradorRepository: Repository<Administrador>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
await this.seedTipoEventos();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async seedTipoEventos() {
|
||||||
|
const enumValues = Object.values(TipoUsuarioEnum);
|
||||||
|
|
||||||
|
for (const tipoUsuario of enumValues) {
|
||||||
|
const existingTipo = await this.tipoUserRepository.findOne({
|
||||||
|
where: { tipo: tipoUsuario },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingTipo) {
|
||||||
|
await this.tipoUserRepository.save({
|
||||||
|
tipo: tipoUsuario,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
//Funcion para registrar usuario
|
//Funcion para registrar usuario
|
||||||
async registrarUsuario(dto: CreateTipoUserDto) {
|
async registrarUsuario(dto: CreateTipoUserDto) {
|
||||||
|
|||||||
Reference in New Issue
Block a user