60 Commits

Author SHA1 Message Date
Patriots01 87f11ae968 Se termino de actualizar las dependencias faltantes que no suponen riesgo para el proyecto, a versiones estables, asi como dependencias de desarrollo, se hicieron pruebas de esta version y es estable y apta para migrar al sistema de pruebas, falta actualizar las dependencias criticas como lo son: nestjs, TypeScript, etc, estas versiones se recomienda hacer un plan de trabajo para que no causen conflicto al actualizar el sistema 2026-08-03 15:33:47 -06:00
Patriots01 becf48529d Se implemento la carga de banner con orden en automatico, asi como la funcion de poder actualizar el orden de los banner, ademas de algunas actualizaciones de dependencias, todo esto se probo y funciona correctamente, se mandan estos cambios por que es una version estable a la cual se puede actualizar antes de seguir actualizando las dependencias faltantes 2026-08-03 15:28:04 -06:00
Patriots01 028a8b277d Modificaciones del carrusel 2026-08-03 11:14:41 -06:00
Patriots01 535e030927 Modificaciones en los archivos de carrusel, entity, controller, servicio, se agrego un nuevo endpoint para actualizar los banner y se agrego un campo de orden en la entity 2026-02-18 21:16:38 +00:00
Patriots01 a53d9ddabe Se actualizo la infromacion de comentarios y participantes en el orden de enviar la infromacion y se documento el campo de orden en carrusel, para no causar conflicto falta arreglar esta parte 2026-02-17 22:32:20 +00:00
santiago f02dd40d51 Se quito el orden para no causar conflicto 2026-02-09 12:22:33 -06:00
santiago b607cc2ccb Se quito el orden en los banners 2026-02-09 12:15:50 -06:00
santiago b5a832e179 Modificaciones 2026-02-09 12:13:50 -06:00
Patriots01 fb31589c78 Se implemento el cierre de la conexion en la base de datos 2026-02-07 00:40:01 +00:00
jalvarado 6238c82296 Merge pull request 'feat: ampliacion de campos' (#17) from develop into master
Reviewed-on: #17
2026-02-04 18:35:27 -06:00
miguel fa472fcbf1 feat: ampliacion de campos 2026-02-04 18:33:09 -06:00
Patriots01 da05d88923 Se modifico la confirmacion del alumno 2026-02-04 20:39:58 +00:00
santiago 20f7688f8b Modificaciones en produccion 2025-09-12 20:12:08 -06:00
santiago 05e4443c1b Mandar modificaciones a produccion 2025-09-12 20:09:28 -06:00
santiago a41b30b94a Modificacion y refactorizacion del codigo 2025-08-27 12:44:22 -06:00
santiago aa8efa74d9 Modificacion en la peticion para verificar si existe el correo 2025-08-27 12:30:46 -06:00
santiago 98ca9f7792 Modificacion para agegar la descarga de participantes de eventos 2025-08-23 09:57:16 -06:00
santiago 2c953b58da Subir cambios a develop 2025-08-22 12:48:25 -06:00
evenegas 0f986a2f7f Descarga de participantes 2025-08-22 12:14:54 -06:00
evenegas eaa5921c69 se agregó fecha de consulta a la api 2025-08-20 11:43:26 -06:00
evenegas b6f4084185 se agregó fecha de consulta a la api 2025-08-20 11:42:55 -06:00
evenegas 0aa0d514cb se agregó implementacion de Api para ampliación 2025-08-19 12:16:17 -06:00
jalvarado a88db620f3 Merge pull request 'develop' (#16) from develop into master
Reviewed-on: #16
2025-08-12 14:58:18 -06:00
miguel d6f28003b3 Implement deleteAllCarrusel method to remove all images and database records from the carrusel 2025-08-12 14:56:34 -06:00
santiago 5c68472a85 Agregar funcionalidad para agregar almacenamiento en la base de cedetec de T&L 2025-07-30 14:16:14 -06:00
miguel a5da262469 Add Almacenamiento module, controller, service, and entity for data management 2025-07-28 21:52:03 -06:00
miguel 562642648d Merge branch 'develop' 2025-07-28 10:56:37 -06:00
miguel 0b591d51bd Refactor eventos controller to remove duplicate getEventosDisponibles method 2025-07-28 10:49:25 -06:00
stern f628d2a3b4 Modificación a script start en package.json para uso de Node 22 2025-05-14 11:41:55 -06:00
stern 71866ffe86 Merge branch 'develop' of https://repositorio.acatlan.unam.mx/CIDWA/cedetec_api_nest into develop 2025-05-14 11:39:47 -06:00
stern 2a33f20681 Modificación a script start en package.json para uso de Node 22 2025-05-14 11:39:04 -06:00
jalvarado 0b48fde321 Increase maximum link length validation to 150 characters in CarruselService 2025-05-14 11:39:02 -06:00
jalvarado ef643019bb Merge pull request 'ampliacion link' (#14) from master into develop
Reviewed-on: #14
2025-05-14 11:35:03 -06:00
jalvarado 47747c3bed ampliacion link 2025-05-14 11:33:45 -06:00
jalvarado 0eebc186b2 Merge pull request 'master' (#12) from master into develop
Reviewed-on: #12
2025-05-13 20:54:12 -06:00
jorgemike 6fe658f668 Refactor authentication and carousel services; enhance error handling and validation 2025-05-13 19:42:36 -06:00
jalvarado d6a63dae13 Merge pull request 'Merge pull request 'master' (#10) from master into develop' (#11) from develop into master
Reviewed-on: #11
2024-07-24 19:34:50 -06:00
mike f5ae636a7f Seguridaad 2024-07-24 14:34:01 -06:00
jalvarado d6d54d17f0 Merge pull request 'master' (#10) from master into develop
Reviewed-on: #10
2024-07-23 19:56:34 -06:00
mike ff158b0d32 CRUD de comentarios 2024-07-23 19:00:51 -06:00
Jorge Miguel Alvarado Reyes f924401e7b Observaciones 2024-01-17 16:37:40 -06:00
Jorge Miguel Alvarado Reyes 73ef0d982b Requisitos -> Obervaciones, longitud 60 -> 300 2024-01-17 16:35:20 -06:00
jalvarado 95b287292f Merge pull request 'develop' (#9) from develop into master
Reviewed-on: #9
2023-08-04 09:42:53 -06:00
jalvarado e4a56184d5 Carrusel envia mensaje de error enlugar de detener el servicio 2023-06-26 12:51:29 -06:00
JorgeMike e2afc63e28 Muchos cambios 2023-06-26 08:33:18 -06:00
JorgeMike 0a70bf2af2 Se puede agregar links al carrusel 2023-06-06 14:07:49 -06:00
jalvarado c80590b838 Merge pull request 'email' (#8) from email into develop
Reviewed-on: #8
2023-05-03 20:19:14 +00:00
JorgeMike 537ef3484f .env removed 2023-03-27 09:24:45 -06:00
JorgeMike d07c4b4a41 the banner edit is ready and name of participant its getting update in eache event 2023-03-27 09:24:11 -06:00
jalvarado 291a2ff75e deleted .env 2023-03-21 14:38:54 -06:00
jalvarado 34ecc34fbe se agrego el crear excels con los participantes 2023-03-21 14:36:38 -06:00
jalvarado 89066bc769 removed .env original and added new variables 2023-03-14 15:56:05 -06:00
jalvarado 9d910e247c Merge branch 'develop' of https://repositorio.acatlan.unam.mx/CIDWA/cedetec_api_nest into develop 2023-03-14 15:52:20 -06:00
jalvarado d4b6655880 Merge branch 'email' into develop 2023-03-14 15:51:53 -06:00
jalvarado 4a69baa07e Actualizar '.env.example' 2023-03-14 21:46:05 +00:00
jalvarado e29aacfc8f Actualizar '.env.example' 2023-03-14 21:38:33 +00:00
jalvarado b8daadd0e3 eliminar el env 2023-03-10 10:01:24 -06:00
jalvarado 01e7bfd580 Merge branch 'develop' of https://repositorio.acatlan.unam.mx/CIDWA/cedetec_api_nest into develop 2023-03-10 10:00:15 -06:00
jalvarado a46ba72fe4 env example 2023-03-10 09:58:48 -06:00
jalvarado d277503ff9 Merge pull request 'cupo_evento' (#3) from cupo_evento into develop
Reviewed-on: #3
2023-03-10 15:56:55 +00:00
47 changed files with 4127 additions and 2822 deletions
-22
View File
@@ -1,22 +0,0 @@
PORT=5089
# DB_HOST=132.248.180.82
# DB_USER=user_cedetec
# DB_PASSWORD=c3t3d3c
# DB_NAME=Betelgeuse_Cedetec_Prueba
# DB_PORT=
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=Dosmiluno2001
DB_NAME=cedetec_proyectos
DB_PORT=3306
DB_HOST_DSC = 132.248.180.82
DB_USER_DSC = sites_user
DB_PASS_DSC = F0rTest
DB_NAME_DSC = sat
NODEMAILER_SERVICE = gmail
NODEMAILER_USER = cidwa1@pcpuma.acatlan.unam.mx
NODEMAILER_PASWORD = cidwaxd1,
+16 -1
View File
@@ -12,4 +12,19 @@ DB_NAME_DSC=
NODEMAILER_SERVICE= NODEMAILER_SERVICE=
NODEMAILER_USER= NODEMAILER_USER=
NODEMAILER_PASWORD= NODEMAILER_PASWORD=
#132.248.180.82
#3306
#usr_tl
#A1m_p@$
#Betelgeuse_TL_test
HOST_TYL=132.248.180.82
PORT_TYL=3306
USUARIO_TYL=usr_tl
CONTRASENA_TYL=A1m_p@$
DATABASE_TYL=Betelgeuse_TL_test
#Funciones para la api
API_URL=
TOKEN=
+3056 -2542
View File
File diff suppressed because it is too large Load Diff
+14 -13
View File
@@ -8,7 +8,7 @@
"scripts": { "scripts": {
"build": "nest build", "build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start", "start": "source ~/.nvm/nvm.sh && nvm use 22.10.0 && npm install && nest start",
"start:dev": "nest start --watch", "start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch", "start:debug": "nest start --debug --watch",
"start:prod": "node dist/main", "start:prod": "node dist/main",
@@ -29,28 +29,28 @@
"@nestjs/serve-static": "^3.0.1", "@nestjs/serve-static": "^3.0.1",
"@nestjs/swagger": "^6.2.1", "@nestjs/swagger": "^6.2.1",
"@nestjs/typeorm": "^9.0.1", "@nestjs/typeorm": "^9.0.1",
"@types/multer": "^1.4.7",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"blob-stream": "^0.1.3", "blob-stream": "^0.1.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.15.1",
"exceljs": "^4.3.0", "date-fns": "^2.30.0",
"moment": "^2.29.4", "exceljs": "^4.4.0",
"moment": "^2.30.1",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"mysql2": "^3.1.2", "mysql2": "^3.23.2",
"nodemailer": "^6.9.1", "nodemailer": "^6.9.1",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"pdfkit": "^0.13.0", "pdfkit": "^0.13.0",
"pg": "^8.9.0", "pg": "^8.22.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0", "rxjs": "^7.8.2",
"swagger-ui-express": "^4.6.1", "swagger-ui-express": "^4.6.1",
"sybase": "^1.2.3", "sybase": "^1.2.3",
"typeorm": "^0.3.12", "typeorm": "^0.3.12",
"uuid": "^9.0.0", "uuid": "^9.0.1",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
@@ -58,10 +58,11 @@
"@nestjs/cli": "^9.0.0", "@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0", "@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0", "@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13", "@types/express": "^5.0.6",
"@types/jest": "29.2.4", "@types/jest": "29.2.4",
"@types/multer": "^2.2.0",
"@types/node": "18.11.18", "@types/node": "18.11.18",
"@types/passport-local": "^1.0.35", "@types/passport-local": "^1.0.38",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.0.0",
@@ -74,8 +75,8 @@
"source-map-support": "^0.5.20", "source-map-support": "^0.5.20",
"supertest": "^6.1.3", "supertest": "^6.1.3",
"ts-jest": "29.0.3", "ts-jest": "29.0.3",
"ts-loader": "^9.2.3", "ts-loader": "^9.6.2",
"ts-node": "^10.0.0", "ts-node": "^10.9.2",
"tsconfig-paths": "4.1.1", "tsconfig-paths": "4.1.1",
"typescript": "^4.7.4" "typescript": "^4.7.4"
}, },
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

@@ -0,0 +1,18 @@
import { Controller, Post, Get, Body } from '@nestjs/common';
import { AlmacenamientoService } from './almacenamiento.service';
@Controller('almacenamiento')
export class AlmacenamientoController {
constructor(private readonly almacenamientoService: AlmacenamientoService) {}
@Post()
async create(@Body() body: { correo: string; agree: boolean }) {
return this.almacenamientoService.create(body.correo);
}
@Get()
async findAll() {
const data = await this.almacenamientoService.findAll();
return { message: 'GET endpoint reached', data };
}
}
@@ -0,0 +1,32 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Almacenamiento } from './entity/almacenamiento.entity';
import { AlmacenamientoService } from './almacenamiento.service';
import { AlmacenamientoController } from './almacenamiento.controller';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
TypeOrmModule.forRootAsync({
name: 'almacenamiento',
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
name: 'almacenamiento',
type: 'mariadb',
host: configService.get('HOST_TYL'),
port: Number(configService.get('PORT_TYL')),
username: configService.get('USUARIO_TYL'),
password: configService.get('CONTRASENA_TYL'),
database: configService.get('DATABASE_TYL'),
entities: [Almacenamiento],
synchronize: true,
ssl: false, // <- importante
}),
}),
TypeOrmModule.forFeature([Almacenamiento], 'almacenamiento'),
],
providers: [AlmacenamientoService],
controllers: [AlmacenamientoController],
})
export class AlmacenamientoModule {}
@@ -0,0 +1,33 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Almacenamiento } from './entity/almacenamiento.entity';
@Injectable()
export class AlmacenamientoService {
constructor(
@InjectRepository(Almacenamiento, 'almacenamiento')
private readonly almacenamientoRepository: Repository<Almacenamiento>,
) {}
// Example method to create a new record
async create(correo: string): Promise<any> {
console.log(correo)
const existente = await this.almacenamientoRepository.findOne({ where:{correo} });
console.log(existente)
if (!existente) {
// Maneja el error aquí mismo
throw new HttpException(
{ error: 'Este usuario no tiene acceso' },
HttpStatus.NOT_FOUND
);
}
return { mensaje: 'Usuario verificado correctamente' };
}
// Example method to get all records
async findAll(): Promise<Almacenamiento[]> {
return this.almacenamientoRepository.find();
}
}
@@ -0,0 +1,16 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('almacenamiento')
export class Almacenamiento {
@PrimaryGeneratedColumn({ type: 'int', name: 'id_almacenamiento' })
idAlmacenamiento: number;
@Column('varchar', { name: 'correo', length: 100 })
correo: string;
@Column('timestamp', {
name: 'fecha_ampliacion',
})
fechaConsulta: Date;
}
+19 -6
View File
@@ -46,10 +46,15 @@ import { EmailService } from './email/email.service';
import { EmailController } from './email/email.controller'; import { EmailController } from './email/email.controller';
import { EmailModule } from './email/email.module'; import { EmailModule } from './email/email.module';
import { MulterModule } from '@nestjs/platform-express'; import { MulterModule } from '@nestjs/platform-express';
import { CarouselController } from './carousel/carousel.controller';
import { CarouselService } from './carousel/carousel.service';
import { ServeStaticModule } from '@nestjs/serve-static'; import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path'; import { join } from 'path';
import { CarruselModule } from './carrusel/carrusel.module';
import { Carrusel } from './carrusel/entity/carrusel.entity';
import { ComentariosModule } from './comentarios/comentarios.module';
import { Comentarios } from './comentarios/entity/comentarios.entity';
import { AlmacenamientoModule } from './almacenamiento/almacenamiento.module';
import { Almacenamiento } from './almacenamiento/entity/almacenamiento.entity';
import { TylController } from './tyl/tyl.controller';
@Module({ @Module({
imports: [ imports: [
@@ -71,6 +76,7 @@ import { join } from 'path';
username: configService.get('DB_USER'), username: configService.get('DB_USER'),
password: configService.get('DB_PASSWORD'), password: configService.get('DB_PASSWORD'),
database: configService.get('DB_NAME'), database: configService.get('DB_NAME'),
synchronize: true,
entities: [ entities: [
Evento, Evento,
Participante, Participante,
@@ -93,8 +99,11 @@ import { join } from 'path';
Equipo, Equipo,
EquipoEvento, EquipoEvento,
EquipoMiembro, EquipoMiembro,
/* Agregado el 2 de junio */
Carrusel,
/* No recuerdo porque empece a ponerle fechas */
Comentarios,
], ],
synchronize: true,
}), }),
}), }),
EventosModule, EventosModule,
@@ -115,21 +124,25 @@ import { join } from 'path';
ProyectoActividadModule, ProyectoActividadModule,
ActividadModule, ActividadModule,
EmailModule, EmailModule,
ComentariosModule,
MulterModule.register({ MulterModule.register({
dest: './uploads', // directorio de destino para guardar los archivos cargados dest: './uploads', // directorio de destino para guardar los archivos cargados
}), }),
CarruselModule,
ComentariosModule,
AlmacenamientoModule,
], ],
controllers: [ controllers: [
AppController, AppController,
ParticipanteController,
InscripcionStatusController, InscripcionStatusController,
CarouselController, TylController,
], ],
providers: [AppService, InscripcionStatusService, CarouselService], providers: [AppService, InscripcionStatusService],
}) })
export class AppModule { export class AppModule {
static port: number; static port: number;
constructor(private readonly configService: ConfigService) { constructor(private readonly configService: ConfigService) {
console.log('PORT', this.configService.get('PORT'));
AppModule.port = this.configService.get('PORT'); AppModule.port = this.configService.get('PORT');
} }
} }
+12 -4
View File
@@ -1,18 +1,21 @@
import { Body, Controller, Post, Request, UseGuards } from '@nestjs/common'; import { Body, Controller, Param, Post } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { LoginMiembroDto } from './dto/loginMiembro.dto'; import { LoginMiembroDto } from './dto/loginMiembro.dto';
import { RegistrarMiembroDto } from './dto/registrarMiembro.dto'; import { RegistrarMiembroDto } from './dto/registrarMiembro.dto';
import { ApiTags } from '@nestjs/swagger';
import { ApiPostRegistro } from './auth.docs';
import { jwtStrategy } from './jwt.strategy';
@Controller('auth') @Controller('auth')
@ApiTags('auth')
export class AuthController { export class AuthController {
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private authValidar: jwtStrategy,
) /* @InjectRepository(Usuario) private usuarioRepository: Repository<Usuario> */ {} ) /* @InjectRepository(Usuario) private usuarioRepository: Repository<Usuario> */ {}
@Post('registro') @Post('registro')
@ApiPostRegistro()
registrarUsuario(@Body() registrarMiembro: RegistrarMiembroDto) { registrarUsuario(@Body() registrarMiembro: RegistrarMiembroDto) {
return this.authService.registrar(registrarMiembro); return this.authService.registrar(registrarMiembro);
} }
@@ -21,4 +24,9 @@ export class AuthController {
login(@Body() LoginUsuario: LoginMiembroDto) { login(@Body() LoginUsuario: LoginMiembroDto) {
return this.authService.login(LoginUsuario); return this.authService.login(LoginUsuario);
} }
@Post('validate')
validar(@Param() token: any) {
return this.authValidar.validate
}
} }
+78
View File
@@ -0,0 +1,78 @@
import { applyDecorators } from '@nestjs/common';
import {
ApiOperation,
ApiResponse,
ApiTags,
ApiBody,
} from '@nestjs/swagger';
import { RegistrarMiembroDto } from './dto/registrarMiembro.dto';
export function ApiPostRegistro() {
return applyDecorators(
ApiTags('Auth'),
ApiOperation({
summary: 'Registrar nuevo miembro',
description:
'Registra un nuevo usuario en la plataforma. No se permite duplicidad de correo electrónico.',
}),
ApiBody({
type: RegistrarMiembroDto,
examples: {
requeridos: {
summary: 'Solo datos obligatorios',
description: 'Ejemplo básico con los campos mínimos requeridos para registrar un usuario.',
value: {
nombre: 'Luis',
apellido_paterno: 'Martínez',
apellido_materno: 'Lopez',
id_tipo_miembro: 1,
password: 'contraseñaSegura123',
cuenta: 421010101,
email: 'luis@example.com',
},
},
completo: {
summary: 'Todos los campos llenos',
description: 'Ejemplo de un usuario con carrera, semestre y descripción.',
value: {
nombre: 'Ana',
apellido_paterno: 'Torres',
apellido_materno: 'Hernández',
id_tipo_miembro: 2,
password: 'AnaSegura456',
descripcion: 'Estudiante interesada en programación web',
id_carrera: 3,
semestre: '4°',
cuenta: 421020202,
email: 'ana.torres@example.com',
},
},
invalido: {
summary: 'Ejemplo con datos inválidos',
description: 'Muestra un correo mal formado y una contraseña demasiado corta.',
value: {
nombre: 'Mario',
apellido_paterno: 'Gómez',
apellido_materno: 'Ramírez',
id_tipo_miembro: 1,
password: '123',
cuenta: 421000000,
email: 'mario-sin-arroba.com',
},
},
},
}),
ApiResponse({
status: 201,
description: 'Usuario registrado exitosamente.',
}),
ApiResponse({
status: 403,
description: 'El usuario ya existe.',
}),
ApiResponse({
status: 400,
description: 'Datos inválidos o faltantes.',
}),
);
}
+5 -3
View File
@@ -54,9 +54,11 @@ export class AuthService {
/* console.log(token); */ /* console.log(token); */
const data = { const data = {
usuario: usuario.nombre, user: {
id: usuario.id_miembro, usuario: usuario.nombre,
email: usuario.email, id: usuario.id_miembro,
email: usuario.email,
},
token, token,
}; };
-92
View File
@@ -1,92 +0,0 @@
import {
BadRequestException,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Param,
Post,
Res,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { Response } from 'express';
import * as fs from 'fs';
import { diskStorage } from 'multer';
import * as path from 'path';
@Controller('carousel')
export class CarouselController {
private images: string[] = [];
@Post('/imagenes')
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: './public',
filename: function (req, file, cb) {
const directory = './public';
const filePath = `${directory}/${file.originalname}`;
if (fs.existsSync(filePath)) {
cb(
new BadRequestException(
`Ya existe un archivo con el nombre: '${file.originalname}'. Si está seguro de que no es el mismo, cambie el nombre del archivo.`,
),
null,
);
} else {
cb(null, file.originalname);
}
},
}),
fileFilter: function (req, file, cb) {
const filetypes = /jpeg|jpg|webp|png/;
const extname = filetypes.test(
path.extname(file.originalname).toLowerCase(),
);
const mimetype = filetypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
}
cb(
new BadRequestException(
'Lo sentimos, para mejorar la calidad de las imagenes en el carousel solo se aceptan imagenes con terminación: .jpeg .jpg .png .webp',
),
false,
);
},
}),
)
async addImage(@UploadedFile() file) {
return { msg: 'Imagen guardada con exito :)' };
}
@Delete('/imagenes/:nombreArchivo')
async deleteImage(@Param('nombreArchivo') nombreArchivo: string) {
try {
await fs.promises.unlink(`./public/${nombreArchivo}`);
return { message: 'Imagen eliminada exitosamente' };
} catch (err) {
throw new Error('Error al eliminar la imagen');
}
}
@Get('/imagenes')
async getImages(@Res() res: Response) {
// Leer el directorio 'public' para obtener el nombre de todos los archivos
const fileNames = await fs.promises.readdir('./public');
// Generar la URL para cada archivo y agregarla a un arreglo
// Filtrar solo las imágenes con las terminaciones "jpg", "webp" y "png"
const imageNames = fileNames.filter((fileName) =>
['.jpg', '.webp', '.png'].includes(path.extname(fileName)),
);
const imageUrls = imageNames.map((imageName) => `public/${imageName}`);
// Devolver el arreglo de URLs como respuesta
return res.json(imageUrls);
}
}
-18
View File
@@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CarouselService } from './carousel.service';
describe('CarouselService', () => {
let service: CarouselService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CarouselService],
}).compile();
service = module.get<CarouselService>(CarouselService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
-9
View File
@@ -1,9 +0,0 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class CarouselService {
uploadImage(file){
/* console.log(file) */
}
}
+80
View File
@@ -0,0 +1,80 @@
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
Post,
Put,
Res,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { CarruselService } from './carrusel.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { actualizarContador, contador } from './utils';
import * as path from 'path';
import { carruselDto } from './dto/carruselDto.dto';
@Controller('carrusel')
export class CarruselController {
constructor(private carruselService: CarruselService) {}
@Post('addImage')
@UseInterceptors(
FileInterceptor('imagen', {
storage: diskStorage({
destination: './public/carrusel',
filename: function (req, file, cb) {
actualizarContador();
const { ext } = path.parse(file.originalname);
const filename = `carrusel${contador}${ext}`;
return cb(null, filename);
},
}),
fileFilter: function (req, file, cb) {
if (!file.originalname.match(/\.(jpg|jpeg|png|webp)$/)) {
return {
message:
'Lo sentimos los archivos permitidos para usar el carrusel son jpg jpeg png webp',
};
}
cb(null, true);
},
}),
)
addImagenToCarrusel(@Body() carruselDto: carruselDto, @UploadedFile() file) {
return this.carruselService.addImagenToCarrusel(carruselDto, file.filename);
}
@Patch('update/:id')
updateImagenCarrusel(
@Param('id', ParseIntPipe) id: number,
@Body() carruselDto: carruselDto,
) {
return this.carruselService.updateImagenCarrusel(id, carruselDto);
}
@Put('updateOrden')
async updateOrden(@Body() banners: { id: number; orden: number}[]) {
return this.carruselService.updateOrden(banners);
}
@Delete('delete/:id')
deleteImagenByRuta(@Param('id', ParseIntPipe) id: number) {
return this.carruselService.deleteImagenByRuta(id);
}
@Get('imagenes')
getAllImagenes() {
return this.carruselService.getAllImages();
}
@Delete('deleteAll')
deleteAllCarrusel() {
return this.carruselService.deleteAllCarrusel();
}
}
+12
View File
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { CarruselController } from './carrusel.controller';
import { CarruselService } from './carrusel.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Carrusel } from 'src/carrusel/entity/carrusel.entity';
@Module({
imports: [TypeOrmModule.forFeature([Carrusel])],
controllers: [CarruselController],
providers: [CarruselService],
})
export class CarruselModule {}
+208
View File
@@ -0,0 +1,208 @@
import {
BadRequestException,
Injectable,
InternalServerErrorException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Carrusel } from 'src/carrusel/entity/carrusel.entity';
import { Repository } from 'typeorm';
import { carruselDto } from './dto/carruselDto.dto';
import * as fs from 'fs';
import * as path from 'path';
@Injectable()
export class CarruselService {
constructor(
@InjectRepository(Carrusel)
private carruselRepository: Repository<Carrusel>,
) {}
async addImagenToCarrusel(
carruselDto: carruselDto,
filename: string,
): Promise<{ message: string; carrusel: Carrusel }> {
if (!filename) {
throw new BadRequestException({
message: 'No se ha subido la imagen',
});
}
if (!carruselDto.link) {
throw new BadRequestException({
message: 'El campo "link" es obligatorio',
});
}
if (carruselDto.link.length > 150) {
throw new BadRequestException({
message: 'El campo "link" no puede tener más de 100 caracteres',
});
}
carruselDto.ruta = `/public/carrusel/${filename}`;
// Obtener el banner con mayor orden
const ultimoCarrusel = await this.carruselRepository.find({
order: {
orden: 'DESC',
},
take: 1
});
const ultimoOrden = ultimoCarrusel.length > 0 ? ultimoCarrusel[0].orden : 0;
// const siguienteOrden = ultimoCarrusel
// ? ultimoCarrusel.orden + 1
// : 1;
// const carruselCreate = this.carruselRepository.create(carruselDto);
const carruselCreate = this.carruselRepository.create({
...carruselDto,
orden: ultimoOrden + 1,
});
try {
const carrusel = await this.carruselRepository.save(carruselCreate);
return {
message: 'Imagen agregada correctamente',
carrusel,
};
} catch (error) {
console.error('❌ Error al guardar carrusel en base de datos');
console.table({
code: error.code,
message: error.message,
});
throw new InternalServerErrorException({
message: 'Ocurrió un error inesperado al guardar la imagen',
error: error.message,
});
}
}
async updateImagenCarrusel(
id: number,
carruselDto: carruselDto,
): Promise<{ message: string; carrusel: Carrusel }> {
const carrusel = await this.carruselRepository.findOne({
where: { id_imagen_carrusel: id },
});
if (!carrusel) {
throw new BadRequestException({
message: `No se encontró una imagen de carrusel con el id ${id}`,
});
}
Object.assign(carrusel, carruselDto);
try {
const carruselUpdated = await this.carruselRepository.save(carrusel);
return {
message: 'Imagen de carrusel actualizada correctamente',
carrusel: carruselUpdated,
};
} catch (error) {
console.error('❌ Error al actualizar carrusel en base de datos');
console.table({
code: error.code,
message: error.message,
});
throw new InternalServerErrorException({
message: 'Ocurrió un error inesperado al actualizar la imagen',
error: error.message,
});
}
}
// Actualizar todas las imagenes por orden
async updateOrden(banners: { id: number; orden: number}[]) {
// Paso 1.- Mandar los banner a un orden extremo para que no causen conflicto al actualizar
for( const banner of banners ) {
await this.carruselRepository.update(
{ id_imagen_carrusel: banner.id },
{ orden: banner.orden + 1000 },
);
}
// Paso 2.- Actualizamos al nuevo orden
for (const banner of banners ) {
await this.carruselRepository.update(
{ id_imagen_carrusel: banner.id },
{ orden: banner.orden },
)
}
return {
message: 'Orden actualizado correctamente',
}
}
async deleteImagenByRuta(id: number) {
try {
const res = await this.carruselRepository.findOne({
where: { id_imagen_carrusel: id },
});
await fs.promises.unlink(`./${res.ruta}`);
return this.carruselRepository.delete({ id_imagen_carrusel: id });
} catch (error) {
console.error('Error:', error);
throw error;
}
}
getAllImages(): Promise<Carrusel[]> {
// return this.carruselRepository.find();
return this.carruselRepository.find({
order: {
orden: 'ASC',
},
});
// return this.carruselRepository.find();
}
async deleteAllCarrusel(): Promise<{ message: string }> {
try {
// Obtener todas las imágenes del carrusel
const images = await this.carruselRepository.find();
// Eliminar archivos físicos de la carpeta public/carrusel
const carruselDir = './public/carrusel';
if (fs.existsSync(carruselDir)) {
// Leer todos los archivos en el directorio
const files = await fs.promises.readdir(carruselDir);
// Eliminar cada archivo
for (const file of files) {
const filePath = path.join(carruselDir, file);
try {
await fs.promises.unlink(filePath);
console.log(`✅ Archivo eliminado: ${filePath}`);
} catch (fileError) {
console.warn(
`⚠️ No se pudo eliminar el archivo: ${filePath}`,
fileError.message,
);
}
}
}
// Eliminar todos los registros de la base de datos
await this.carruselRepository.delete({});
return {
message: `Carrusel limpiado exitosamente. Se eliminaron ${images.length} imágenes y registros.`,
};
} catch (error) {
console.error('❌ Error al limpiar el carrusel:', error);
throw new InternalServerErrorException({
message: 'Ocurrió un error inesperado al limpiar el carrusel',
error: error.message,
});
}
}
}
+13
View File
@@ -0,0 +1,13 @@
import { IsNotEmpty, IsOptional, IsNumber } from "class-validator";
export class carruselDto {
@IsNotEmpty()
ruta: string
@IsNotEmpty()
link: string
@IsOptional()
@IsNumber()
orden?: number
}
+18
View File
@@ -0,0 +1,18 @@
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Carrusel {
@PrimaryGeneratedColumn()
id_imagen_carrusel: number
@Column({length: 40})
ruta: string
/* "/public/carrusel/carrusel1.jpg" */
@Column({length: 150})
link: string
/* "https://www.acatlan.unam.mx/" */
@Column({ type: 'int', unique: true })
orden: number
}
+5
View File
@@ -0,0 +1,5 @@
export let contador = 0
export const actualizarContador = () => {
contador++
}
+4
View File
@@ -0,0 +1,4 @@
export class ComentarioDto {
from: string;
comentario: string;
}
+43
View File
@@ -0,0 +1,43 @@
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Post,
Query,
} from '@nestjs/common';
import { ComentariosService } from './comentarios.service';
import { ComentarioDto } from './comentario.dto';
@Controller('comentarios')
export class ComentariosController {
constructor(private comentariosService: ComentariosService) {}
@Post()
postComentario(@Body() comentario: ComentarioDto) {
return this.comentariosService.postComentario(comentario);
}
@Get()
getComentarios() {
return this.comentariosService.getComentarios();
}
@Get('by-date')
getComentariosByDate(
@Query('before') before?: string,
@Query('after') after?: string,
@Query('from') from?: string,
@Query('to') to?: string,
) {
return this.comentariosService.getComentariosByDate(before, after, from, to);
}
@Delete(':id')
deleteComentario(@Param('id', ParseIntPipe) id: number) {
return this.comentariosService.deleteComentario(id);
}
}
+12
View File
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { ComentariosController } from './comentarios.controller';
import { ComentariosService } from './comentarios.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Comentarios } from './entity/comentarios.entity';
@Module({
imports: [TypeOrmModule.forFeature([Comentarios])],
controllers: [ComentariosController],
providers: [ComentariosService],
})
export class ComentariosModule {}
+66
View File
@@ -0,0 +1,66 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Comentarios } from './entity/comentarios.entity';
import { Repository } from 'typeorm';
import { ComentarioDto } from './comentario.dto';
@Injectable()
export class ComentariosService {
constructor(
@InjectRepository(Comentarios)
private comentariosRepository: Repository<Comentarios>,
) {}
postComentario(comentario: ComentarioDto) {
if (comentario.from !== 'CEDETEC' && comentario.from !== 'PCPUMA') {
throw new BadRequestException('Mensaje no autorizado');
}
const nuevoComentario = this.comentariosRepository.create(comentario);
return this.comentariosRepository.save(nuevoComentario);
}
getComentarios() {
return this.comentariosRepository.find();
}
async getComentariosByDate(
before?: string,
after?: string,
from?: string,
to?: string,
) {
const query = this.comentariosRepository.createQueryBuilder('comentarios');
if (before) {
query.andWhere('comentarios.fecha_registro < :before', {
before: new Date(before),
});
}
if (after) {
query.andWhere('comentarios.fecha_registro > :after', {
after: new Date(after),
});
}
if (from) {
query.andWhere('comentarios.fecha_registro >= :from', {
from: new Date(from),
});
}
if (to) {
query.andWhere('comentarios.fecha_registro <= :to', { to: new Date(to) });
}
query.orderBy('comentarios.fecha_registro', 'DESC');
return await query.getMany();
}
deleteComentario(id: number) {
return this.comentariosRepository.delete({ id_comentario: id });
}
}
@@ -0,0 +1,16 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity()
export class Comentarios {
@PrimaryGeneratedColumn()
id_comentario: number;
@Column()
from: string;
@Column()
comentario: string;
@CreateDateColumn({ type: 'timestamp' })
fecha_registro: Date;
}
Binary file not shown.
+33 -9
View File
@@ -1,6 +1,7 @@
import { import {
Body, Body,
Controller, Controller,
Get,
HttpException, HttpException,
HttpStatus, HttpStatus,
Param, Param,
@@ -8,6 +9,7 @@ import {
Post, Post,
Res, Res,
UploadedFile, UploadedFile,
UseGuards,
UseInterceptors, UseInterceptors,
} from '@nestjs/common'; } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express'; import { FileInterceptor } from '@nestjs/platform-express';
@@ -15,27 +17,49 @@ import { EmailDto } from './dto/emailDto.dto';
import { EmailService } from './email.service'; import { EmailService } from './email.service';
import { Response } from 'express'; import { Response } from 'express';
import * as xlsx from 'xlsx'; import * as xlsx from 'xlsx';
import { AuthGuard } from '@nestjs/passport';
@Controller('email') @Controller('email')
export class EmailController { export class EmailController {
constructor(private readonly emailService: EmailService) {} constructor(private readonly emailService: EmailService) { }
@UseGuards(AuthGuard('jwt'))
@Post('/evento/:id') @Post('/evento/:id')
async sendEmail( async sendEmail(
@Param('id', ParseIntPipe) id: number, @Param('id', ParseIntPipe) id: number,
@Body() emailDto: EmailDto, @Body() emailDto: EmailDto,
@Res() res,
): Promise<any> { ): Promise<any> {
return this.emailService.sendEmail(id, emailDto); return this.emailService.sendEmail(id, emailDto, res);
} }
@Post('constancias') @UseGuards(AuthGuard('jwt'))
@Post('constancias/:id')
@UseInterceptors(FileInterceptor('file')) @UseInterceptors(FileInterceptor('file'))
async sendConstancias(@UploadedFile() file, @Body('nombreEvento') nombreEvento: string,) { async sendConstancias(
if(!file) { @UploadedFile() file,
throw new HttpException("No se ha cargado ningun archivo", HttpStatus.BAD_REQUEST) @Param('id', ParseIntPipe) id: number,
@Res() res,
) {
if (!file) {
throw new HttpException(
'No se ha cargado ningun archivo',
HttpStatus.BAD_REQUEST,
);
} }
/* console.log(file) */ return this.emailService.sendConstancias(file, id, res);
return this.emailService.sendConstancias(file, nombreEvento);
} }
//@UseGuards(AuthGuard('jwt'))
@Get(':id/participantes/xlsx')
async descargarExcel(
@Param('id') idEvento: number,
@Res() res: Response,
) {
console.log('entro en el controller')
return this.emailService.getParticipantesExcel(idEvento, res);
}
} }
+5 -2
View File
@@ -4,10 +4,13 @@ import { EventosModule } from 'src/eventos/eventos.module';
import { Participante } from 'src/participante/participante.entity'; import { Participante } from 'src/participante/participante.entity';
import { EmailController } from './email.controller'; import { EmailController } from './email.controller';
import { EmailService } from './email.service'; import { EmailService } from './email.service';
import { EventosService } from 'src/eventos/eventos.service';
import { EventoParticipante } from 'src/evento-participante/evento-participante/eventoParticipante.entity';
import { Evento } from 'src/eventos/evento.entity';
@Module({ @Module({
imports:[TypeOrmModule.forFeature([Participante])], imports:[TypeOrmModule.forFeature([Participante, EventoParticipante, Evento])],
controllers:[EmailController], controllers:[EmailController],
providers:[EmailService] providers:[EmailService, EventosService]
}) })
export class EmailModule {} export class EmailModule {}
+163 -43
View File
@@ -1,4 +1,11 @@
import { HttpException, HttpStatus, Inject, Injectable, Res, UploadedFile } from '@nestjs/common'; import {
HttpException,
HttpStatus,
Inject,
Injectable,
Res,
UploadedFile,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import * as nodemailer from 'nodemailer'; import * as nodemailer from 'nodemailer';
@@ -13,6 +20,9 @@ import * as pdfFonts from 'pdfmake/build/vfs_fonts';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'; import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
import { Response } from 'express';
import { format } from 'date-fns';
import * as ExcelJS from 'exceljs';
@Injectable() @Injectable()
export class EmailService { export class EmailService {
@@ -22,6 +32,7 @@ export class EmailService {
@InjectRepository(Participante) @InjectRepository(Participante)
private participanteRepository: Repository<Participante>, private participanteRepository: Repository<Participante>,
private configService: ConfigService, private configService: ConfigService,
private readonly eventoService: EventosService,
) { ) {
this.transporter = nodemailer.createTransport({ this.transporter = nodemailer.createTransport({
service: this.configService.get('NODEMAILER_SERVICE'), service: this.configService.get('NODEMAILER_SERVICE'),
@@ -32,7 +43,7 @@ export class EmailService {
}); });
} }
async sendEmail(id: number, message: EmailDto) { async sendEmail(id: number, message: EmailDto, res: Response) {
const participantes = await this.getEmailDeParticipantes(id); const participantes = await this.getEmailDeParticipantes(id);
participantes.map((res) => { participantes.map((res) => {
const emailDestinatario = res.email; const emailDestinatario = res.email;
@@ -41,8 +52,8 @@ export class EmailService {
subject: message.subject, subject: message.subject,
text: message.text, text: message.text,
}); });
/*console.log("Email enviado a: ", res)*/
}); });
return res.status(201).json({ message: 'Correos enviados con exito' });
} }
async getEmailDeParticipantes(idEvento: number) { async getEmailDeParticipantes(idEvento: number) {
@@ -57,63 +68,172 @@ export class EmailService {
return participantes; return participantes;
} }
async sendConstancias(file, nombreEvento) { async getParticipantesExcel(idEvento: number, res: Response) {
/* console.log(file) */ console.log('entro a la funcion')
const participantes = await this.participanteRepository
.createQueryBuilder('participante')
.innerJoin('participante.eventosParticipante', 'eventoParticipante')
.innerJoin('eventoParticipante.evento', 'evento')
.where('evento.id_evento = :idEvento', { idEvento })
.select(['participante'])
.getMany();
// 1. Crear workbook y worksheet
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Participantes');
// 2. Definir encabezados
worksheet.columns = [
{ header: 'ID', key: 'id', width: 10 },
{ header: 'Nombre', key: 'nombre', width: 30 },
{ header: 'Correo', key: 'correo', width: 30 },
{ header: 'Teléfono', key: 'telefono', width: 20 },
{ header: 'Carrera', key: 'carrera', width: 20 },
{ header: 'Institución', key: 'institucion_procedencia', width: 30 },
{ header: 'Apellido Paterno', key: 'apellido_paterno', width: 20 },
{ header: 'Apellido Materno', key: 'apellido_materno', width: 20 },
];
console.log(participantes);
// 3. Insertar datos
participantes.forEach((p) => {
worksheet.addRow({
nombre: p.nombre,
apellido_paterno: p.apellido_paterno,
apellido_materno: p.apellido_materno,
carrera: p.carrera,
institucion_procedencia: p.institucion_procedencia,
correo: p.email,
telefono: p.telefono,
});
});
// 4. Preparar la respuesta HTTP para enviar el Excel
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
);
res.setHeader(
'Content-Disposition',
'attachment; filename=participantes.xlsx',
);
await workbook.xlsx.write(res);
res.end();
}
async sendConstancias(file, id_evento, res: Response) {
//traemos la info del evento
const evento = await this.eventoService.getEventosPorId(id_evento);
const workbook = xlsx.read(file.buffer); const workbook = xlsx.read(file.buffer);
const worksheet = workbook.Sheets[workbook.SheetNames[0]]; const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const data = xlsx.utils.sheet_to_json(worksheet); const data = xlsx.utils.sheet_to_json(worksheet);
for (const row of data as Row[]) { // Carga el PDF pre-diseñado
const pdfDoc = await PDFDocument.create(); const existingPdfBytes = fs.readFileSync('src/email/DisenoConstancia.pdf');
const page = pdfDoc.addPage();
page.setSize(792, 612);
const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold); for (let i = 0; i < data.length; i++) {
const fontSize = 30; const row = data[i] as Row;
const text = 'Constancia de asistencia';
const textWidth = font.widthOfTextAtSize(text, fontSize);
const textHeight = font.heightAtSize(fontSize);
page.drawText(text, { if(row.Email == undefined){
x: (page.getWidth() - textWidth) / 2, return res.status(400).json({ message: `El correo del renglon ${ i + 2} puede estar vacio o ser undefined`, solucion: `Seleccione el renglon donde ocurre el problema y borre todo lo de este renglon, Aunque parezca que el renglon esta vacio puede que haya un espacio en algun campo y esto genera que el sistema haga la lectura de este renglon`});
y: (page.getHeight() - textHeight) / 2, }
// Carga el PDF pre-diseñado en un documento PDF editable
const pdfDoc = await PDFDocument.load(existingPdfBytes);
const pagina = pdfDoc.getPages();
const primeraPagina = pagina[0];
// Obtén el ancho y alto de la página
const { width, height } = primeraPagina.getSize();
// Obtén la fuente del texto
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
// Configura el tamaño del texto
const fontSize = 26;
const nombreParticipante = `${row['Apellido Paterno']} ${row['Apellido Materno']} ${row.Nombre}`;
// Calcula la anchura del texto del nombre del participante
const textWidth = font.widthOfTextAtSize(nombreParticipante, fontSize);
const centerX = (width - textWidth) / 2;
const centerY = (height - fontSize) / 2;
// Dibuja el texto centrado en la página
primeraPagina.drawText(nombreParticipante, {
x: centerX,
y: centerY + 70,
size: fontSize, size: fontSize,
font: font, color: rgb(0, 0, 0),
color: rgb(0/255, 0/255, 0/255),
}); });
const { width, height } = page.getSize(); const nombreCurso = `${evento.nombre}`;
page.drawText(
`Se le otorga la constancia por su asistencia al evento: ${nombreEvento}`, // Calcula la anchura del texto del nombre del evento
{ const nombreCursoWidth = font.widthOfTextAtSize(nombreCurso, fontSize);
x: 50,
y: height - 50, const centerXNombreCurso = (width - nombreCursoWidth) / 2;
size: 12, const centerYNombreCurso = (height - fontSize) / 2;
font: await pdfDoc.embedFont('Helvetica'),
}, primeraPagina.drawText(nombreCurso, {
); x: centerXNombreCurso,
page.drawText( y: centerYNombreCurso - 17,
`A ${row['Apellido Paterno']} ${row['Apellido Materno']} ${row.Nombre}`, size: fontSize,
{ color: rgb(0, 0, 0),
x: 50, });
y: height - 80,
size: 12, const fechaCurso = `del ${this.formatoFecha(
font: await pdfDoc.embedFont('Helvetica'), evento.fecha_inicio,
}, )} al ${this.formatoFecha(evento.fecha_fin)}`;
);
// Calcula la anchura del texto del nombre del participante
const fechaCursoWidth = font.widthOfTextAtSize(fechaCurso, 20);
const centerXFechaCurso = (width - fechaCursoWidth) / 2;
const centerYFechaCurso = (height - 20) / 2;
primeraPagina.drawText(fechaCurso, {
x: centerXFechaCurso,
y: centerYFechaCurso - 55,
size: 20,
color: rgb(0, 0, 0),
});
// Guarda el PDF modificado en forma de bytes
const pdfBytes = await pdfDoc.save(); const pdfBytes = await pdfDoc.save();
// Crea el objeto de adjunto con el nombre y contenido del PDF
const attachment = { const attachment = {
filename: `Constancia ${nombreEvento} - ${row.Nombre}_${row['Apellido Paterno']}.pdf`, filename: `Constancia ${evento.nombre} - ${row.Nombre}_${row['Apellido Paterno']}.pdf`,
content: pdfBytes, content: pdfBytes,
}; };
/* const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob); */ // Crea el objeto Blob y la URL para el PDF
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
// Envía el correo electrónico con la constancia adjunta
this.transporter.sendMail({ this.transporter.sendMail({
to: row.Email, to: row.Email,
subject: `Constancia de asistencia a "${nombreEvento}"`, subject: `Constancia de asistencia a "${evento.nombre}"`,
text: `¡Un saludo ${row.Nombre}! Aquí está tu constancia de asistencia por el evento "${nombreEvento}". Agradecemos tu asistencia y constancia, esperamos seguir viéndote en eventos futuros.`, text: `¡Un saludo ${row.Nombre}! Aquí está tu constancia de asistencia por el evento "${evento.nombre}". Agradecemos tu asistencia y constancia, esperamos seguir viéndote en eventos futuros.`,
attachments: [attachment], attachments: [attachment],
}); });
} }
return res.status(200).json({ message: 'Constancias enviadas con éxito' });
}
formatoFecha(fecha: Date) {
const fechaLimpia = format(fecha, 'dd/MM/yyyy');
return fechaLimpia;
} }
} }
@@ -1,4 +1,4 @@
import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common'; import { Body, Controller, Get, Param, ParseIntPipe, Post } from '@nestjs/common';
import { RegistrarParticipanteDto } from './dto/registrarParticipanteDto.dto'; import { RegistrarParticipanteDto } from './dto/registrarParticipanteDto.dto';
import { EventoParticipanteService } from './evento-participante.service'; import { EventoParticipanteService } from './evento-participante.service';
import { EventoParticipante } from './eventoParticipante.entity'; import { EventoParticipante } from './eventoParticipante.entity';
@@ -12,5 +12,8 @@ export class EventoParticipanteController {
return this.eventoParticipanteService.registrarParticipante(id, datos) return this.eventoParticipanteService.registrarParticipante(id, datos)
} }
@Get()
getEventoParticipante(): Promise<EventoParticipante[]> {
return this.eventoParticipanteService.getEventoParticipantes();
}
} }
@@ -75,4 +75,8 @@ export class EventoParticipanteService {
}, },
); );
} }
getEventoParticipantes() {
return this.eventoParticipanteRepository.find()
}
} }
+1 -1
View File
@@ -5,7 +5,7 @@ export class actualizarEventoDto {
proyecto?: string; proyecto?: string;
modalidad?: string; modalidad?: string;
lugar?: string; lugar?: string;
requisitos?: string; observaciones?: string;
cuota_inscripcion?: number; cuota_inscripcion?: number;
patrocinador?: string; patrocinador?: string;
tipo_acreditacion?: string; tipo_acreditacion?: string;
+1 -1
View File
@@ -6,7 +6,7 @@ export class crearEventoDto {
modalidad: string; modalidad: string;
lugar: string; lugar: string;
cupo: number; cupo: number;
requisitos: string; observaciones: string;
cuota_inscripcion: number; cuota_inscripcion: number;
patrocinador: string; patrocinador: string;
tipo_acreditacion: string; tipo_acreditacion: string;
+25 -28
View File
@@ -14,52 +14,52 @@ export class Evento {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id_evento: number; id_evento: number;
@Column({length: 70, nullable: false }) @Column({ length: 70, nullable: false })
nombre: string; nombre: string;
@Column({length: 15, nullable: false }) @Column({ length: 15, nullable: false })
tipo_evento: string; tipo_evento: string;
@Column({length: 100,nullable: false}) @Column({ length: 100, nullable: false })
organizadores: string organizadores: string;
@Column({length:30 ,nullable: true}) @Column({ length: 30, nullable: true })
proyecto: string proyecto: string;
@Column({length: 40,nullable: false}) @Column({ length: 40, nullable: false })
modalidad: string modalidad: string;
@Column({length: 60,nullable: false }) @Column({ length: 60, nullable: false })
lugar: string; lugar: string;
@Column({nullable: false}) @Column({ nullable: false })
cupo: number; cupo: number;
@Column({length: 60, nullable: false }) @Column({ length: 500, nullable: false })
requisitos: string; observaciones: string;
@Column({type:'decimal', precision: 7, scale: 2, nullable: true }) @Column({ type: 'decimal', precision: 7, scale: 2, nullable: true })
cuota_inscripcion: number; cuota_inscripcion: number;
@Column({length: 50, nullable: true }) @Column({ length: 50, nullable: true })
patrocinador: string; patrocinador: string;
@Column({length: 40, nullable: true }) @Column({ length: 40, nullable: true })
tipo_acreditacion: string; tipo_acreditacion: string;
@Column({length: 200 ,nullable: false}) @Column({ length: 500, nullable: false })
objetivo: string objetivo: string;
@Column({ nullable: false, type: 'datetime' }) @Column({ nullable: false, type: 'datetime' })
fecha_inicio: Date; fecha_inicio: Date;
@Column({ nullable: false, type: 'datetime' }) @Column({ nullable: false, type: 'datetime' })
fecha_fin: Date; fecha_fin: Date;
@Column({ nullable: false, type: 'datetime' }) @Column({ nullable: false, type: 'datetime' })
fecha_limite_inscripcion: Date; fecha_limite_inscripcion: Date;
@Column({length:40 ,nullable: true }) @Column({ length: 40, nullable: true })
horario: string; horario: string;
@Column({ nullable: true }) @Column({ nullable: true })
@@ -71,9 +71,6 @@ export class Evento {
) )
eventoParticipantes: EventoParticipante[]; eventoParticipantes: EventoParticipante[];
@OneToMany( @OneToMany(() => EquipoEvento, (equipoEvento) => equipoEvento.evento)
() => EquipoEvento, (equipoEvento) => equipoEvento.evento equipoEvento: EquipoEvento;
)
equipoEvento: EquipoEvento
} }
+15 -9
View File
@@ -1,4 +1,4 @@
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Res } from '@nestjs/common'; import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Res, UseGuards } from '@nestjs/common';
import { Response } from 'express'; import { Response } from 'express';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { EventoParticipante } from 'src/evento-participante/evento-participante/eventoParticipante.entity'; import { EventoParticipante } from 'src/evento-participante/evento-participante/eventoParticipante.entity';
@@ -7,6 +7,7 @@ import { actualizarEventoDto } from './dto/actualizarEvento.dto';
import { crearEventoDto } from './dto/crearEvento.dto'; import { crearEventoDto } from './dto/crearEvento.dto';
import { Evento } from './evento.entity'; import { Evento } from './evento.entity';
import { EventosService } from './eventos.service'; import { EventosService } from './eventos.service';
import { AuthGuard } from '@nestjs/passport';
@Controller('eventos') @Controller('eventos')
export class EventosController { export class EventosController {
@@ -18,24 +19,32 @@ export class EventosController {
return this.eventoService.getEventos() return this.eventoService.getEventos()
} }
@Get('disponibles')
getEventosDisponibles (){
return this.eventoService.getEventosDisponibles()
}
@Get(':id') @Get(':id')
getEventosPorId(@Param('id', ParseIntPipe)id: number):Promise<Evento>{ getEventosPorId(@Param('id', ParseIntPipe)id: number):Promise<Evento>{
return this.eventoService.getEventosPorId(id) return this.eventoService.getEventosPorId(id)
} }
@UseGuards(AuthGuard('jwt'))
@Post() @Post()
postEvento(@Body() nuevoEvento: crearEventoDto){ postEvento(@Body() nuevoEvento: crearEventoDto){
return this.eventoService.postEvento(nuevoEvento) return this.eventoService.postEvento(nuevoEvento)
} }
@UseGuards(AuthGuard('jwt'))
@Patch(':id') @Patch(':id')
patchEvento(@Param('id', ParseIntPipe)id:number, @Body() actualizacion: actualizarEventoDto){ patchEvento(@Param('id', ParseIntPipe)id:number, @Body() actualizacion: actualizarEventoDto){
return this.eventoService.updateEvento(id,actualizacion) return this.eventoService.updateEvento(id,actualizacion)
} }
@UseGuards(AuthGuard('jwt'))
@Delete(':id') @Delete(':id')
deleteEvento(@Param('id', ParseIntPipe)id: number){ deleteEvento(@Param('id', ParseIntPipe)id: number){
return this.eventoService.deleteEventoParticipante(id) return this.eventoService.deleteEventoParticipante(id)
} }
@Get(':id/cupos') @Get(':id/cupos')
@@ -43,21 +52,18 @@ export class EventosController {
return this.eventoService.getCupos(id) return this.eventoService.getCupos(id)
} }
@UseGuards(AuthGuard('jwt'))
@Get(':id/participante') @Get(':id/participante')
async getParticipnates(@Param('id', ParseIntPipe)id:number){ async getParticipnates(@Param('id', ParseIntPipe)id:number){
return this.eventoService.getParticipantes(id) return this.eventoService.getParticipantes(id)
} }
@UseGuards(AuthGuard('jwt'))
@Get(':id/exportExcel') @Get(':id/exportExcel')
async exportarExcel (@Param('id', ParseIntPipe)id:number ,@Res() res: Response){ async exportarExcel (@Param('id', ParseIntPipe)id:number ,@Res() res: Response){
this.eventoService.exportarParticipantes(id, res) this.eventoService.exportarParticipantes(id, res)
} }
@Post('disponibles')
getEventosDisponibles (){
return this.eventoService.getEventosDisponibles()
}
@Post('disponibles/pendientesDconstancia') @Post('disponibles/pendientesDconstancia')
getEventosDisponiblesPendientes(){ getEventosDisponiblesPendientes(){
return this.eventoService.geteventosDisponiblesMasDosDias() return this.eventoService.geteventosDisponiblesMasDosDias()
+6 -2
View File
@@ -20,10 +20,14 @@ export class EventosService {
) {} ) {}
getEventos() { getEventos() {
return this.eventoRepository.find(); return this.eventoRepository.find({
order: {
fecha_inicio: 'DESC',
},
});
} }
getEventosPorId(id: number) { public getEventosPorId(id: number) {
return this.eventoRepository.findOne({ return this.eventoRepository.findOne({
where: { where: {
id_evento: id, id_evento: id,
@@ -1,9 +1,11 @@
import { import {
Body, Body,
Controller, Controller,
NotFoundException,
Post, Post,
} from '@nestjs/common'; } from '@nestjs/common';
import { InscripcionDscService } from './inscripcion-dsc.service'; import { InscripcionDscService } from './inscripcion-dsc.service';
import { And } from 'typeorm';
@Controller('inscripcion-dsc') @Controller('inscripcion-dsc')
export class InscripcionDscController { export class InscripcionDscController {
@@ -12,15 +14,19 @@ export class InscripcionDscController {
@Post('confirmacion') @Post('confirmacion')
async confirmacionDsc(@Body() body: any) { async confirmacionDsc(@Body() body: any) {
const { accountNumber } = body; const { accountNumber } = body;
console.log('Entro a la confirmacion')
const userVerification = await this.inscripcionService.userVerification(accountNumber); const userVerification = await this.inscripcionService.userVerification(accountNumber);
/* console.log(userVerification) */ /* console.log(userVerification) */
console.log('Usuario verificacion', userVerification);
if(!userVerification){ if(!userVerification){
return { throw new NotFoundException('El usuario no se encuentra inscrito');
error: true,
msj: 'El usuario no se encuentra inscrito', // return {
desc: '', // error: true,
}; // msj: 'El usuario no se encuentra inscrito',
// desc: '',
// };
} }
const userInformation = userVerification; const userInformation = userVerification;
@@ -29,7 +35,7 @@ export class InscripcionDscController {
try{ try{
/* console.log("verificando confirmacion:", userInformation); /* console.log("verificando confirmacion:", userInformation);
*/ if (userInformation[0].confirmacion) { */ if (userInformation[0].confirmacion && userInformation[1].confirmacion) {
return { return {
error: true, error: true,
msj: 'El usuario ya hizo la confirmación anteriormente', msj: 'El usuario ya hizo la confirmación anteriormente',
+17 -2
View File
@@ -4,31 +4,46 @@ import { createConnection } from 'mysql2/promise';
@Injectable() @Injectable()
export class InscripcionDscService { export class InscripcionDscService {
async userVerification(accountNumber: string) { async userVerification(accountNumber: string) {
let connection;
try { try {
const connection = await createConnection({ connection = await createConnection({
host: process.env.DB_HOST_DSC, host: process.env.DB_HOST_DSC,
user: process.env.DB_USER_DSC, user: process.env.DB_USER_DSC,
password: process.env.DB_PASS_DSC, password: process.env.DB_PASS_DSC,
database: process.env.DB_NAME_DSC, database: process.env.DB_NAME_DSC,
}); });
// const userInformationQuery = `SELECT A.id_cuenta, nombre, if(AI.platica=true,1,0) as confirmacion FROM alumno A
// JOIN alumno_inscrito AI ON (A.id_cuenta = AI.id_cuenta)
// JOIN periodo P ON (P.id_periodo = AI.id_periodo)
// WHERE A.id_cuenta = "${accountNumber}" and P.activo=true`;
const userInformationQuery = `SELECT A.id_cuenta, nombre, if(AI.platica=true,1,0) as confirmacion FROM alumno A const userInformationQuery = `SELECT A.id_cuenta, nombre, if(AI.platica=true,1,0) as confirmacion FROM alumno A
JOIN alumno_inscrito AI ON (A.id_cuenta = AI.id_cuenta) JOIN alumno_inscrito AI ON (A.id_cuenta = AI.id_cuenta) and (AI.id_plataforma = 1 || AI.id_plataforma = 2)
JOIN periodo P ON (P.id_periodo = AI.id_periodo) JOIN periodo P ON (P.id_periodo = AI.id_periodo)
WHERE A.id_cuenta = "${accountNumber}" and P.activo=true`; WHERE A.id_cuenta = "${accountNumber}" and P.activo=true`;
console.log('despues de el select', userInformationQuery)
const qryEjemplo = `SELECT * FROM alumno WHERE id_cuenta="${accountNumber}";`; const qryEjemplo = `SELECT * FROM alumno WHERE id_cuenta="${accountNumber}";`;
const [results] = await connection.query(userInformationQuery); const [results] = await connection.query(userInformationQuery);
const resultsArray = JSON.parse(JSON.stringify(results)); const resultsArray = JSON.parse(JSON.stringify(results));
if (resultsArray.length === 0) return null; if (resultsArray.length === 0) return null;
/* console.log('The solution is: ', resultsArray); */ /* console.log('The solution is: ', resultsArray); */
console.log('The solution is: ', resultsArray);
console.log('Resultados', results)
return results; return results;
} catch (error) { } catch (error) {
console.log('Entro al error', error)
throw error; throw error;
} finally {
if (connection) {
await connection.end();
}
} }
} }
async userConfirmation(accountNumber: string): Promise<any> { async userConfirmation(accountNumber: string): Promise<any> {
try { try {
+5
View File
@@ -20,7 +20,12 @@ async function bootstrap() {
prefix: '/public/', prefix: '/public/',
}); });
app.enableCors(); app.enableCors();
await app.listen(AppModule.port); await app.listen(AppModule.port);
const port = AppModule.port;
console.log(`Documentacion disponible en: http://localhost:${port}/api`);
console.log(`Servidor desplegado en: http://localhost:${port}`);
} }
bootstrap(); bootstrap();
+10 -2
View File
@@ -1,5 +1,13 @@
import { Controller, Post } from '@nestjs/common'; import { Controller, Get, Post } from '@nestjs/common';
import { ParticipanteService } from './participante.service'; import { ParticipanteService } from './participante.service';
import { Participante } from './participante.entity';
@Controller('participante') @Controller('participante')
export class ParticipanteController {} export class ParticipanteController {
constructor(private participanteService: ParticipanteService) {}
@Get()
getParticipantes(): Promise<Participante[]> {
return this.participanteService.getParticipantes();
}
}
+4
View File
@@ -31,4 +31,8 @@ export class ParticipanteService {
}, },
); );
} }
getParticipantes() {
return this.participanteRepository.find();
}
} }
@@ -1,15 +1,15 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { CarouselController } from './carousel.controller'; import { TylController } from './tyl.controller';
describe('CarouselController', () => { describe('TylController', () => {
let controller: CarouselController; let controller: TylController;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
controllers: [CarouselController], controllers: [TylController],
}).compile(); }).compile();
controller = module.get<CarouselController>(CarouselController); controller = module.get<TylController>(TylController);
}); });
it('should be defined', () => { it('should be defined', () => {
+40
View File
@@ -0,0 +1,40 @@
import { Body, Controller, InternalServerErrorException, Post } from '@nestjs/common';
@Controller('tyl')
export class TylController {
@Post('')
async enviarTyl(@Body() body: { email: string }) {
try {
const user = 'acatlan'
const api = 'LW2rtatYP8o2PhCkIqeJV94d6a60cSvTmJXPbRpP';
const params = new URLSearchParams();
params.append('user_name', user!);
params.append('email', body.email);
params.append('api_key', process.env.API_KEY!);
const response = await fetch(
'https://accede.estudio.app/ventas-acatlan',
{
method: 'POST',
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// },
body: params,
redirect: 'manual',
}
);
console.log('respuesta', response);
const redirectUrl = response.headers.get('location');
console.log('redirect', redirectUrl)
return { redirectUrl };
} catch (error) {
throw new InternalServerErrorException('Error al conectar con API externa');
}
}
}