This commit is contained in:
jorgemike
2025-03-02 21:01:47 -06:00
parent 0658c1f80b
commit 43b9387a9f
37 changed files with 379 additions and 430 deletions
+1 -1
View File
@@ -55,4 +55,4 @@ pids
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
imagenes
fotografias
@@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AdscripcionController } from './adscripcion.controller'; // Asumiendo que el controlador se llama EdificioController
describe('AdscripcionController', () => {
let controller: AdscripcionController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AdscripcionController],
}).compile();
controller = module.get<AdscripcionController>(AdscripcionController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
@@ -7,7 +7,6 @@ export class AdscripcionController {
@Get()
findAll() {
Logger.debug('find all adscripcion');
return this.adscripcionService.findAll();
}
}
@@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AdscripcionService } from './adscripcion.service';
describe('AdscripcionService', () => {
let service: AdscripcionService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AdscripcionService],
}).compile();
service = module.get<AdscripcionService>(AdscripcionService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
-22
View File
@@ -1,22 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});
+6 -6
View File
@@ -20,10 +20,10 @@ import { Profesor } from './profesor/entities/profesor.entity';
import { AdscripcionModule } from './adscripcion/adscripcion.module';
import { EdificioModule } from './edificio/edificio.module';
import { CategoriaModule } from './categoria/categoria.module';
import { ImagenModule } from './images/images.module';
import { DatabaseModule } from './database/database.module';
import { LineasInvestigacionModule } from './lineas-investigacion/lineas-investigacion.module';
import { ProyectosAcademicosModule } from './proyectos-academicos/proyectos-academicos.module';
import { FotografiaModule } from './fotografia/fotografia.module';
@Module({
imports: [
@@ -43,7 +43,6 @@ import { ProyectosAcademicosModule } from './proyectos-academicos/proyectos-acad
username: configService.get('DB_USER'),
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_DATABASE'),
//dropSchema: true,
entities: [
Profesor,
Usuario,
@@ -55,14 +54,15 @@ import { ProyectosAcademicosModule } from './proyectos-academicos/proyectos-acad
ProyectosAcademicos,
TipoUsuario,
],
synchronize: true,
//dropSchema: true,
//synchronize: true, JAMA USAR ESTO EN PRODUCCION BORRA DATOS DE LA DB
}),
}),
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'imagenes'),
serveRoot: '/imagenes',
rootPath: join(__dirname, '..', 'fotografias'),
serveRoot: '/fotografias',
}),
ImagenModule,
FotografiaModule,
ProfesorModule,
AdscripcionModule,
EdificioModule,
+2 -2
View File
@@ -24,10 +24,8 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { SignUpDto } from './dto/sign-up.dto';
import { JwtAuthGuard } from 'src/permissions/jwt.guard';
@ApiBearerAuth()
@ApiTags('Auth')
@Controller('auth')
@UseGuards(RolesGuard)
export class AuthController {
constructor(private authService: AuthService) {}
@@ -45,7 +43,9 @@ export class AuthController {
summary: 'Registro de usuario',
description: 'Registro ded administradores o responsables',
})
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@UseGuards(RolesGuard)
@Roles(Role.Admin)
async register(@Body() data: SignUpDto) {
Logger.debug('register user');
@@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CategoriaController } from './categoria.controller';
describe('CategoriaController', () => {
let controller: CategoriaController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CategoriaController],
}).compile();
controller = module.get<CategoriaController>(CategoriaController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
-1
View File
@@ -7,7 +7,6 @@ export class CategoriaController {
@Get()
findAll() {
Logger.debug('find all categoria');
return this.categoriaService.findAll();
}
}
-18
View File
@@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CategoriaService } from './categoria.service';
describe('CategoriaService', () => {
let service: CategoriaService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CategoriaService],
}).compile();
service = module.get<CategoriaService>(CategoriaService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
-18
View File
@@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DatabaseController } from './database.controller';
describe('DatabaseController', () => {
let controller: DatabaseController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [DatabaseController],
}).compile();
controller = module.get<DatabaseController>(DatabaseController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
+12 -1
View File
@@ -1,6 +1,6 @@
import { Controller, Post } from '@nestjs/common';
import { DatabaseService } from './database.service';
import { ApiTags } from '@nestjs/swagger';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
@ApiTags('Carga de Datos')
@Controller('database')
@@ -12,4 +12,15 @@ export class DatabaseController {
await this.databaseService.cargarDatosDesdeExcel();
return { message: 'Datos cargados exitosamente' };
}
@Post('migrar-datos')
@ApiOperation({
summary: 'Migrar datos a la nueva base de datos',
description:
'Este endpoint ejecuta la migración desde la base de datos antigua a la nueva.',
})
async migrateDatabase() {
await this.databaseService.migrateDB();
return { message: 'Migración completada exitosamente' };
}
}
-18
View File
@@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DatabaseService } from './database.service';
describe('DatabaseService', () => {
let service: DatabaseService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [DatabaseService],
}).compile();
service = module.get<DatabaseService>(DatabaseService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
+4
View File
@@ -60,4 +60,8 @@ export class DatabaseService {
await Promise.all(data.map((row) => repo.save(row)));
}
}
async migrateDB(): Promise<any> {
}
}
@@ -11,15 +11,16 @@ import { DatosAcademicosService } from './datos-academicos.service';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard } from 'src/permissions/jwt.guard';
import { RolesGuard } from 'src/permissions/roles.guard';
import { Roles } from 'src/permissions/roles.decorator';
import { Role } from 'src/permissions/role.enum';
@ApiBearerAuth()
@ApiTags('datos-academicos')
@Controller('datos-academicos')
@UseGuards(JwtAuthGuard, RolesGuard)
export class DatosAcademicosController {
constructor(private datosAcademicosService: DatosAcademicosService) {}
@Patch(':id_datos')
@Patch(':id_profesor')
@ApiOperation({
summary: 'Actualizar datos academicos de un profesor',
description:
@@ -29,13 +30,12 @@ export class DatosAcademicosController {
schema: {
type: 'object',
properties: {
id_profesor: {
id_datos: {
type: 'number',
description:
'ID del profesor al que se debe actualizar sus lineas de investigacion',
description: 'ID de los datos que se van a actualizar',
example: 1,
},
lineas_inv_html: {
grados_obtenidos: {
type: 'string',
description:
'Nuevo contenido de líneas de investigación en formato Markdown o HTML',
@@ -45,14 +45,21 @@ export class DatosAcademicosController {
},
})
async updateDatosAcademicos(
@Param('id_datos') id_datos: number,
@Param('id_profesor') id_profesor: number,
@Body('id_datos') id_datos: number,
@Body('grados_obtenidos') grados_obtenidos: string,
@Body('id_profesor') id_profesor: number,
@Body('grado_maximo') grado_maximo: number,
) {
console.log(grados_obtenidos);
console.log(grado_maximo);
console.log(id_profesor);
console.log(id_datos);
if (!grados_obtenidos || grados_obtenidos.trim() === '') {
throw new HttpException(
'El contenido no puede estar vacío.',
HttpStatus.BAD_REQUEST,
);
}
return this.datosAcademicosService.updateDatosAcademicos(
id_datos,
id_profesor,
grados_obtenidos,
);
}
}
@@ -1,14 +1,18 @@
import { Injectable } from '@nestjs/common';
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DatosAcademicos } from './entities/datos_academicos.entity';
import { Profesor } from 'src/profesor/entities/profesor.entity';
import { ProfesorService } from 'src/profesor/profesor.service';
@Injectable()
export class DatosAcademicosService {
constructor(
@InjectRepository(DatosAcademicos)
private readonly datosAcademicosRepository: Repository<DatosAcademicos>,
@Inject(forwardRef(() => ProfesorService))
private readonly profesorService: ProfesorService,
) {}
async create(profesor: Profesor, grado_maximo: string) {
@@ -21,6 +25,59 @@ export class DatosAcademicosService {
return await this.datosAcademicosRepository.save(datosAcademicos);
}
async updateDatosAcademicos(
id_datos: number,
id_profesor: number,
nuevoContenido: string,
) {
const profesor = await this.profesorService.findOne(id_profesor);
let datosAcademicos = await this.datosAcademicosRepository.findOne({
where: { profesor: profesor, id_datos },
});
if (!datosAcademicos) {
Logger.error(
`No se encontraron datos académicos del profesor con ID ${id_profesor} al intentar actualizar. Se procederá a crear una nueva.`,
);
// Crear nuevos datos académicos con el contenido
datosAcademicos = this.datosAcademicosRepository.create({
profesor: profesor,
grados_obtenidos: nuevoContenido, // Se asigna el nuevo contenido directamente
grado_maximo: 'Sin Grado Maximo', // Valor predeterminado
});
// Guardar los nuevos datos académicos
await this.datosAcademicosRepository.save(datosAcademicos);
} else {
// Si ya existen, actualizar su contenido
datosAcademicos.grados_obtenidos = nuevoContenido;
await this.datosAcademicosRepository.save(datosAcademicos);
}
return datosAcademicos; // Devolver los datos académicos actualizados o recién creados
}
async updateGradoMaximo(id_profesor: number, nuevoGradoMaximo: string) {
const profesor = await this.profesorService.findOne(id_profesor);
const datosAcademicos = await this.datosAcademicosRepository.findOne({
where: { profesor: profesor },
});
if (!datosAcademicos) {
Logger.error(
`No se encontraron datos académicos del profesor con ID ${id_profesor} al intentar actualizar el grado máximo \n Se procede a crear una nueva`,
);
return await this.create(profesor, nuevoGradoMaximo);
}
datosAcademicos.grado_maximo = nuevoGradoMaximo;
return await this.datosAcademicosRepository.save(datosAcademicos);
}
async removeByProfesorId(id_profesor: number): Promise<void> {
await this.datosAcademicosRepository.delete({ profesor: { id_profesor } });
}
@@ -21,9 +21,7 @@ export class DatosAcademicos {
@Column({ type: 'varchar', length: 600, nullable: true })
grados_obtenidos: string;
@ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion, {
onDelete: 'CASCADE',
})
@ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion)
@JoinColumn({ name: 'id_profesor' })
profesor: Profesor;
}
-18
View File
@@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EdificioController } from './edificio.controller';
describe('EdificioController', () => {
let controller: EdificioController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [EdificioController],
}).compile();
controller = module.get<EdificioController>(EdificioController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
-1
View File
@@ -7,7 +7,6 @@ export class EdificioController {
@Get()
findAll() {
Logger.debug('find all edificio');
return this.edificioService.findAll();
}
}
-18
View File
@@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EdificioService } from './edificio.service'; // Asegúrate de importar el servicio correcto
describe('EdificioService', () => {
let service: EdificioService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [EdificioService], // Asegúrate de incluir el servicio correcto aquí
}).compile();
service = module.get<EdificioService>(EdificioService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
+85
View File
@@ -0,0 +1,85 @@
import {
Controller,
Post,
Patch,
UseInterceptors,
UploadedFile,
Body,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { FotografiaService } from './fotografia.service';
import {
ApiBody,
ApiConsumes,
ApiOperation,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { Express } from 'express';
@ApiTags('fotografia')
@Controller('fotografia')
export class FotografiaController {
constructor(private readonly fotografiaService: FotografiaService) {}
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
@ApiOperation({ summary: 'Subir una imagen con un nombre personalizado' })
@ApiConsumes('multipart/form-data')
@ApiBody({
schema: {
type: 'object',
properties: {
customName: {
type: 'string',
description: 'Nombre personalizado de la imagen',
},
file: {
type: 'string',
format: 'binary',
},
},
},
})
@ApiResponse({ status: 201, description: 'Imagen subida exitosamente' })
uploadFile(
@UploadedFile() file: Express.Multer.File,
@Body('customName') customName: string,
) {
return { message: 'Imagen subida exitosamente', fileName: file.filename };
}
@Post('update')
@UseInterceptors(FileInterceptor('file'))
@ApiOperation({
summary: 'Actualizar una imagen existente con un nombre personalizado',
})
@ApiConsumes('multipart/form-data')
@ApiBody({
schema: {
type: 'object',
properties: {
oldFileName: {
type: 'string',
description: 'Nombre de la imagen antigua',
},
customName: {
type: 'string',
description: 'Nuevo nombre personalizado de la imagen',
},
file: {
type: 'string',
format: 'binary',
},
},
},
})
@ApiResponse({ status: 200, description: 'Imagen actualizada correctamente' })
updateFile(
@UploadedFile() file: Express.Multer.File,
@Body('oldFileName') oldFileName: string,
@Body('customName') customName: string,
) {
return this.fotografiaService.updateFile(oldFileName, file, customName);
}
}
+37
View File
@@ -0,0 +1,37 @@
import { Module } from '@nestjs/common';
import { MulterModule } from '@nestjs/platform-express';
import { FotografiaController } from './fotografia.controller';
import { FotografiaService } from './fotografia.service';
import { diskStorage } from 'multer';
import { join } from 'path';
import { existsSync, mkdirSync } from 'fs';
@Module({
imports: [
MulterModule.register({
storage: diskStorage({
destination: (req, file, cb) => {
const uploadPath = join(__dirname, '..', '..', 'fotografias');
if (!existsSync(uploadPath)) {
mkdirSync(uploadPath, { recursive: true });
}
cb(null, uploadPath);
},
filename: (req, file, cb) => {
const uniqueSuffix =
Date.now() + '-' + Math.round(Math.random() * 1e9);
const customName = req.body.customName
? req.body.customName.toLowerCase().replace(/\s+/g, '-')
: 'image';
cb(
null,
`${customName}-${uniqueSuffix}${file.originalname.substring(file.originalname.lastIndexOf('.'))}`,
);
},
}),
}),
],
controllers: [FotografiaController],
providers: [FotografiaService],
})
export class FotografiaModule {}
+58
View File
@@ -0,0 +1,58 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { renameSync, existsSync, unlinkSync, mkdirSync } from 'fs';
import { join } from 'path';
@Injectable()
export class FotografiaService {
updateFile(
oldFileName: string,
newFile: Express.Multer.File,
customName?: string,
) {
try {
const uploadPath = join(__dirname, '..', '..', 'fotografias');
const oldFilePath = join(uploadPath, oldFileName);
const tempFilePath = newFile.path; // Ruta temporal del archivo subido
// 🛠️ 1. Asegurar que la carpeta 'fotografias/' exista
if (!existsSync(uploadPath)) {
mkdirSync(uploadPath, { recursive: true });
}
// 🛠️ 2. Verificar si el archivo temporal realmente existe antes de renombrar
if (!existsSync(tempFilePath)) {
throw new BadRequestException('El archivo temporal no fue encontrado.');
}
// 🛠️ 3. Eliminar la imagen anterior si existe
if (oldFileName && existsSync(oldFilePath)) {
try {
unlinkSync(oldFilePath);
console.log(`✅ Imagen anterior eliminada: ${oldFileName}`);
} catch (error) {
console.error(`❌ Error al eliminar la imagen anterior: ${error.message}`);
}
} else {
console.log('No se encontró imagen anterior para eliminar.', oldFileName);
}
// 🛠️ 4. Generar nuevo nombre de archivo con customName y timestamp
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
const fileExtension = newFile.originalname.substring(newFile.originalname.lastIndexOf('.'));
const newFileName = `${customName ? customName.toLowerCase().replace(/\s+/g, '-') : 'image'}-${uniqueSuffix}${fileExtension}`;
const newFilePath = join(uploadPath, newFileName);
// 🛠️ 5. Mover el archivo de la carpeta temporal a 'fotografias/' con su nuevo nombre
renameSync(tempFilePath, newFilePath);
console.log(`Imagen subida con éxito: ${newFileName}`);
console.log('-------------------')
return {
message: 'Imagen actualizada correctamente',
fileName: newFileName,
};
} catch (error) {
throw new BadRequestException(`Error en la actualización de la imagen: ${error.message}`);
}
}
}
-27
View File
@@ -1,27 +0,0 @@
import { Controller, Post, Body, Put, Logger } from '@nestjs/common';
import { ImagenService } from './images.service';
@Controller('imagen')
export class ImagenController {
constructor(private readonly imagenService: ImagenService) {}
@Post()
async upload(@Body() body) {
Logger.debug('upload image');
const { fotografia, nombre } = body;
const imageUrl = await this.imagenService.saveImage(fotografia, nombre);
return { message: 'Image uploaded successfully', imageUrl };
}
@Put()
async modify(@Body() body) {
Logger.debug('modify image');
const { fotografia, nombre } = body;
const imageUrl = await this.imagenService.modify(fotografia, nombre);
return { message: 'Image uploaded successfully', imageUrl };
}
}
-10
View File
@@ -1,10 +0,0 @@
import { Module } from '@nestjs/common';
import { ImagenController } from './images.controller';
import { ImagenService } from './images.service';
@Module({
controllers: [ImagenController],
providers: [ImagenService],
})
export class ImagenModule {}
-90
View File
@@ -1,90 +0,0 @@
import { Injectable, BadRequestException, Logger } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
@Injectable()
export class ImagenService {
async saveImage(fotografia: string, id: string): Promise<string> {
if (!fotografia) {
throw new BadRequestException('No image provided');
}
// Extract the image extension
const matches = fotografia.match(/^data:image\/([a-zA-Z]+);base64,/);
if (!matches) {
throw new BadRequestException('Invalid image format');
}
const ext = matches[1];
// Decode base64 string and save the image
const buffer = Buffer.from(fotografia.split(',')[1], 'base64');
const imageName = `${id}.${ext}`;
// Save image to the 'imagenes' directory in the root of the project
const imagePath = path.join(process.cwd(), 'imagenes', imageName);
// Ensure the directory exists
const dirPath = path.join(process.cwd(), 'imagenes');
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
fs.writeFileSync(imagePath, buffer);
return imageName;
}
async deleteImage(id: string): Promise<void> {
Logger.debug('delete image');
const imageDir = path.join(process.cwd(), 'imagenes');
const imagePath = path.join(imageDir, id);
const extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'avif'];
let fileFound = false;
for (const ext of extensions) {
const fullImagePath = `${imagePath}.${ext}`;
if (fs.existsSync(fullImagePath)) {
fs.unlinkSync(fullImagePath);
fileFound = true;
break;
}
}
if (!fileFound) {
console.log('Image not found, but continuing to save the new image.');
}
}
async modify(fotografia: string, id: string): Promise<string> {
if (!fotografia) {
throw new BadRequestException('No image provided');
}
await this.deleteImage(id);
// Extract the image extension
const matches = fotografia.match(/^data:image\/([a-zA-Z]+);base64,/);
if (!matches) {
throw new BadRequestException('Invalid image format');
}
const ext = matches[1];
// Decode base64 string and save the image
const buffer = Buffer.from(fotografia.split(',')[1], 'base64');
const imageName = `${id}.${ext}`;
// Save image to the 'imagenes' directory in the root of the project
const imagePath = path.join(process.cwd(), 'imagenes', imageName);
// Ensure the directory exists
const dirPath = path.join(process.cwd(), 'imagenes');
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
fs.writeFileSync(imagePath, buffer);
return imageName;
}
}
@@ -12,15 +12,10 @@ export class LineasInvestigacion {
@PrimaryGeneratedColumn()
id_linea_inv: number;
@Column({ type: 'int', nullable: false, unique: true })
id_profesor: number;
@Column({ type: 'longtext', nullable: true })
@Column({ type: 'varchar', length: 600, nullable: true })
lineas_inv_html: string;
@ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion, {
onDelete: 'CASCADE',
})
@ManyToOne(() => Profesor, (profesor) => profesor.lineasInvestigacion)
@JoinColumn({ name: 'id_profesor' })
profesor: Profesor;
}
@@ -19,7 +19,7 @@ import { RolesGuard } from 'src/permissions/roles.guard';
export class LineasInvestigacionController {
constructor(private lineasInvestigacionService: LineasInvestigacionService) {}
@Patch(':id_linea_inv')
@Patch(':id_profesor')
@ApiOperation({
summary: 'Actualizar líneas de investigación de un profesor',
description:
@@ -29,10 +29,10 @@ export class LineasInvestigacionController {
schema: {
type: 'object',
properties: {
id_profesor: {
id_linea_inv: {
type: 'number',
description:
'ID del profesor al que se debe actualizar sus lineas de investigacion',
'ID de las lineas de investigacion que se deben actualizar',
example: 1,
},
lineas_inv_html: {
@@ -45,9 +45,9 @@ export class LineasInvestigacionController {
},
})
async updateLineasInvestigacion(
@Param('id_linea_inv') id_linea_inv: number,
@Param('id_profesor') id_profesor: number,
@Body('id_linea_inv') id_linea_inv: number,
@Body('lineas_inv_html') lineasInvHtml: string,
@Body('id_profesor') id_profesor: number,
) {
if (!lineasInvHtml || lineasInvHtml.trim() === '') {
throw new HttpException(
@@ -31,24 +31,30 @@ export class LineasInvestigacionService {
) {
const profesor = await this.profesorService.findOne(id_profesor);
console.log(profesor);
console.log(nuevoContenido);
const lineasInvestigacion =
await this.lineasInvestigacionRepository.findOne({
where: { profesor: profesor, id_linea_inv },
});
let lineasInvestigacion = await this.lineasInvestigacionRepository.findOne({
where: { profesor: profesor, id_linea_inv },
});
if (!lineasInvestigacion) {
Logger.error(
`No se encontró la línea de investigación del profesor con ID ${id_profesor} al intentar actualizarla \n Se procede a crear una nueva`,
`No se encontró la línea de investigación del profesor con ID ${id_profesor} al intentar actualizarla. Se procede a crear una nueva.`,
);
return await this.create(profesor);
// Crear una nueva línea de investigación con el contenido
lineasInvestigacion = this.lineasInvestigacionRepository.create({
profesor: profesor,
lineas_inv_html: nuevoContenido, // Se asigna el nuevo contenido directamente
});
// Guardar la nueva línea de investigación
await this.lineasInvestigacionRepository.save(lineasInvestigacion);
} else {
// Si ya existe, actualizar su contenido
lineasInvestigacion.lineas_inv_html = nuevoContenido;
await this.lineasInvestigacionRepository.save(lineasInvestigacion);
}
lineasInvestigacion.lineas_inv_html = nuevoContenido;
return await this.lineasInvestigacionRepository.save(lineasInvestigacion);
return lineasInvestigacion; // Devolver la línea de investigación actualizada o recién creada
}
async removeByProfesorId(id_profesor: number): Promise<void> {
+3 -2
View File
@@ -29,6 +29,7 @@ async function bootstrap() {
.addTag('Usuario')
.addTag('lineas-investigacion')
.addTag('proyectos-academicos')
.addTag('datos-academicos')
.addBearerAuth()
.build();
const documentFactory = () => SwaggerModule.createDocument(app, config);
@@ -40,8 +41,8 @@ async function bootstrap() {
app.use(bodyParser.json({ limit: '1mb' }));
app.use(bodyParser.urlencoded({ limit: '1mb', extended: true }));
app.useStaticAssets(path.join(__dirname, '..', 'imagenes'), {
prefix: '/imagenes/',
app.useStaticAssets(path.join(__dirname, '..', 'fotografias'), {
prefix: '/fotografias/',
});
await app.listen(3000);
+10 -23
View File
@@ -67,14 +67,6 @@ export class Profesor {
})
fecha_actualizacion: Date;
/* Datos Personales Eliminados */
/* @Column({ type: 'varchar', length: 13, nullable: true })
tel_personal: string; */
/* @Column({ type: 'varchar', length: 65, nullable: true })
correo_per: string; */
/* RELACIONES */
@Column({ type: 'int', nullable: true })
@@ -92,49 +84,44 @@ export class Profesor {
@ManyToOne(() => Edificio, (edificio) => edificio.profesores)
@JoinColumn({
name: 'id_edificio',
referencedColumnName: 'id_edificio',
foreignKeyConstraintName: 'FK_edificio_fk',
})
edificio: Edificio;
@ManyToOne(() => Adscripcion, (adscripcion) => adscripcion.profesores)
@JoinColumn({
name: 'id_adscripcion',
referencedColumnName: 'id_adscripcion',
foreignKeyConstraintName: 'FK_adscripcion_fk',
})
adscripcion: Adscripcion;
@ManyToOne(() => Categoria, (categoria) => categoria.profesores)
@JoinColumn({
name: 'id_categoria',
referencedColumnName: 'id_categoria',
foreignKeyConstraintName: 'FK_categoria_fk',
})
categoria: Categoria;
@ManyToOne(() => Usuario, (usuario) => usuario.profesores)
@JoinColumn({
name: 'id_usuario',
referencedColumnName: 'id_usuario',
foreignKeyConstraintName: 'FK_usuario_fk',
})
usuario: Usuario;
@OneToMany(
() => DatosAcademicos,
(datosAcademicos) => datosAcademicos.profesor,
{ cascade: true },
)
datosAcademicos: DatosAcademicos[];
@OneToMany(() => LineasInvestigacion, (linea) => linea.profesor, {
cascade: true,
})
@OneToMany(() => LineasInvestigacion, (linea) => linea.profesor)
lineasInvestigacion: LineasInvestigacion[];
@OneToMany(() => ProyectosAcademicos, (proyecto) => proyecto.profesor, {
cascade: true,
})
@OneToMany(() => ProyectosAcademicos, (proyecto) => proyecto.profesor)
proyectosAcademicos: ProyectosAcademicos[];
}
/* Datos Personales Eliminados */
/* @Column({ type: 'varchar', length: 13, nullable: true })
tel_personal: string; */
/* @Column({ type: 'varchar', length: 65, nullable: true })
correo_per: string; */
+11 -10
View File
@@ -40,12 +40,12 @@ interface RequestWithUser extends Request {
@ApiBearerAuth()
@ApiTags('Profesor')
@Controller('profesor')
@UseGuards(JwtAuthGuard, RolesGuard)
export class ProfesorController {
constructor(private profesorService: ProfesorService) {}
@Post()
@ApiOperation({ summary: 'Crear un profesor' })
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.Responsable, Role.Admin)
async register(@Body() data: CreateProfesorDto, @Req() req: RequestWithUser) {
return this.profesorService.register(data, req.user);
@@ -97,19 +97,19 @@ export class ProfesorController {
description: 'Filtrar por nombre del profesor',
})
@ApiQuery({
name: 'id_adscripcion',
name: 'adscripcion',
required: false,
example: 2,
description: 'Filtrar por ID de adscripción',
})
@ApiQuery({
name: 'id_edificio',
name: 'edificio',
required: false,
example: 3,
description: 'Filtrar por ID de edificio',
})
@ApiQuery({
name: 'id_categoria',
name: 'categoria',
required: false,
example: 1,
description: 'Filtrar por ID de categoría',
@@ -118,9 +118,9 @@ export class ProfesorController {
@Query('page') page: number = 1,
@Query('limit') limit: number = 12,
@Query('nombre') nombre?: string,
@Query('id_adscripcion') id_adscripcion?: number,
@Query('id_edificio') id_edificio?: number,
@Query('id_categoria') id_categoria?: number,
@Query('adscripcion') adscripcion?: number,
@Query('edificio') edificio?: number,
@Query('categoria') categoria?: number,
) {
// Validaciones básicas
page = page < 1 ? 1 : page;
@@ -129,9 +129,9 @@ export class ProfesorController {
// Filtros dinámicos
const filters: any = {};
if (nombre) filters.nombre = nombre;
if (id_adscripcion) filters.id_adscripcion = Number(id_adscripcion);
if (id_edificio) filters.id_edificio = Number(id_edificio);
if (id_categoria) filters.id_categoria = Number(id_categoria);
if (adscripcion) filters.adscripcion = Number(adscripcion);
if (edificio) filters.edificio = Number(edificio);
if (categoria) filters.categoria = Number(categoria);
return await this.profesorService.findAllPaginated(page, limit, filters);
}
@@ -161,6 +161,7 @@ export class ProfesorController {
summary:
'Elimina un profesor con sus proyectos y lineas de investigacion por su id_profesor',
})
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.Responsable, Role.Admin)
async remove(@Param('id_profesor', ParseIntPipe) id_profesor: number) {
return this.profesorService.remove(id_profesor);
+24 -31
View File
@@ -50,25 +50,18 @@ export class ProfesorService {
id_profesor: number,
data: UpdateProfesorDto,
): Promise<Profesor> {
const { grado_maximo } = data;
const profesor = await this.findOne(id_profesor);
Object.assign(profesor, data);
return await this.profesorRepository.save(profesor);
}
private async findAvailableId(): Promise<number> {
let id = 1;
let exists = true;
while (exists) {
if (
!(await this.profesorRepository.findOne({ where: { id_profesor: id } }))
) {
Logger.debug(`id_profesor = ${id}`);
exists = false;
return id;
}
id++;
if (grado_maximo) {
await this.datosAcademicosService.updateGradoMaximo(
profesor.id_profesor,
grado_maximo,
);
}
return await this.profesorRepository.save(profesor);
}
//register teacher
@@ -175,6 +168,18 @@ export class ProfesorService {
],
});
if (profesor.lineasInvestigacion.length === 0) {
await this.lineasInvestigacionService.create(profesor);
}
if (profesor.proyectosAcademicos.length === 0) {
await this.proyectosAcademicosService.create(profesor);
}
if (profesor.datosAcademicos.length === 0) {
await this.datosAcademicosService.create(profesor, '');
}
if (!profesor) {
throw new HttpException('Profesor not found', 404);
}
@@ -189,7 +194,8 @@ export class ProfesorService {
): Promise<{ profesores: Profesor[]; total: number; totalPages: number }> {
const query = this.profesorRepository.createQueryBuilder('profesor');
// 🔹 Filtro por Nombre (LIKE %nombre%)
console.log(filters);
if (filters.nombre) {
Logger.debug('Filter by name');
query.andWhere('LOWER(profesor.nombre) LIKE LOWER(:nombre)', {
@@ -197,15 +203,13 @@ export class ProfesorService {
});
}
// 🔹 Filtro por Adscripción
if (filters.adscripcion && !isNaN(filters.adscripcion)) {
Logger.debug('Filter by adscription');
query.andWhere('profesor.adscripcion = :adscripcion', {
query.andWhere('profesor.id_adscripcion = :adscripcion', {
adscripcion: Number(filters.adscripcion),
});
}
// 🔹 Filtro por Categoría
if (filters.categoria && !isNaN(filters.categoria)) {
Logger.debug('Filter by category');
query.andWhere('profesor.categoria = :categoria', {
@@ -213,26 +217,15 @@ export class ProfesorService {
});
}
// 🔹 Filtro por Edificio
if (filters.edificio && !isNaN(filters.edificio)) {
Logger.debug('Filter by building');
query.andWhere('profesor.edificio = :edificio', {
query.andWhere('profesor.id_edificio = :edificio', {
edificio: Number(filters.edificio),
});
}
// 🔹 Filtro por Activo
if (typeof filters.activo !== 'undefined') {
const isActive = filters.activo === 'true' || filters.activo === true;
Logger.debug('Filter by active status');
query.andWhere('profesor.activo = :activo', { activo: isActive });
} else {
query.orderBy('profesor.activo', 'DESC'); // Si no se especifica, prioriza activos
}
query.addOrderBy('profesor.nombre', 'ASC');
// 🔹 Ejecutar consulta con paginación
const [profesores, total] = await query
.skip((page - 1) * limit)
.take(limit)
@@ -18,9 +18,7 @@ export class ProyectosAcademicos {
@Column({ type: 'varchar', length: 700, nullable: true })
proyectos_html: string;
@ManyToOne(() => Profesor, (profesor) => profesor.proyectosAcademicos, {
onDelete: 'CASCADE',
})
@ManyToOne(() => Profesor, (profesor) => profesor.proyectosAcademicos)
@JoinColumn({
name: 'id_profesor',
})
@@ -17,11 +17,10 @@ import { JwtAuthGuard } from 'src/permissions/jwt.guard';
@ApiBearerAuth()
@ApiTags('proyectos-academicos')
@Controller('proyectos-academicos')
@UseGuards(JwtAuthGuard, RolesGuard)
export class ProyectosAcademicosController {
constructor(private proyectosAcademicosService: ProyectosAcademicosService) {}
@Patch(':id_proyecto')
@Patch(':id_profesor')
@ApiOperation({
summary: 'Actualizar los proyectos academicos de un profesor',
description:
@@ -47,9 +46,9 @@ export class ProyectosAcademicosController {
},
})
async updateProyectosAcademicos(
@Param('id_proyecto') id_proyecto: number,
@Param('id_profesor') id_profesor: number,
@Body('proyectos_html') proyectos_html: string,
@Body('id_profesor') id_profesor: number,
@Body('id_proyecto') id_proyecto: number,
) {
if (!proyectos_html || proyectos_html.trim() === '') {
throw new HttpException(
@@ -31,20 +31,30 @@ export class ProyectosAcademicosService {
) {
const profesor = await this.profesorService.findOne(id_profesor);
const proyectos = await this.proyectosAcademicosRepository.findOne({
let proyecto = await this.proyectosAcademicosRepository.findOne({
where: { profesor: profesor, id_proyecto },
});
if (!proyectos) {
if (!proyecto) {
Logger.error(
`No se encontrarón proyectos academicos del profesor con ID ${id_profesor} al intentar actualizarla \n Se procede a crear uno nuevo`,
`No se encontraron proyectos académicos del profesor con ID ${id_profesor} al intentar actualizar. Se procede a crear uno nuevo.`,
);
return await this.create(profesor);
// Crear nuevo proyecto con el contenido
proyecto = this.proyectosAcademicosRepository.create({
profesor: profesor,
proyectos_html: nuevoContenido, // Se asigna el nuevo contenido directamente
});
// Guardar el nuevo proyecto
await this.proyectosAcademicosRepository.save(proyecto);
} else {
// Si ya existe, actualizar su contenido
proyecto.proyectos_html = nuevoContenido;
await this.proyectosAcademicosRepository.save(proyecto);
}
proyectos.proyectos_html = nuevoContenido;
return await this.proyectosAcademicosRepository.save(proyectos);
return proyecto; // Devolver el proyecto actualizado o recién creado
}
async removeByProfesorId(id_profesor: number): Promise<void> {
+1 -3
View File
@@ -26,7 +26,7 @@ export class Usuario {
@Column({ type: 'varchar', length: 60, nullable: false })
usuario: string;
@Column({ type: 'varchar', length: 60, nullable: false })
@Column({ type: 'varchar', length: 60, nullable: false }) // ALTER TABLE usuario CHANGE COLUMN contraseña password VARCHAR(60);
password: string;
@Column({ type: 'varchar', length: 255, nullable: true })
@@ -41,8 +41,6 @@ export class Usuario {
@ManyToOne(() => TipoUsuario, (tipoUsuario) => tipoUsuario.usuarios)
@JoinColumn({
name: 'id_tipo_usuario',
referencedColumnName: 'id_tipo_usuario',
foreignKeyConstraintName: 'FK_tipo_usuario',
})
tipoUsuario: TipoUsuario;