Se agregó el inicio de sesion de google

This commit is contained in:
2025-09-02 13:58:37 -06:00
parent 8ad6db15dc
commit d8d2154cbe
15 changed files with 873 additions and 298 deletions
+73 -1
View File
@@ -1,10 +1,13 @@
import { AuthDocumentation } from './auth.documentation';
import { Controller, Post, Body, UseGuards, Request, Get } from '@nestjs/common';
import { Controller, Post, Body, UseGuards, Request, Get, Req, Res } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { AuthGuard } from '@nestjs/passport';
import { RegisterDto } from './dto/register.dto';
import { ApiBearerAuth } from '@nestjs/swagger';
import { SetPasswordDto } from './dto/createPassword.dto';
import { Response } from 'express';
import { WhiteDto } from './dto/whiteList.dto';
@Controller()
export class AuthController {
@@ -34,6 +37,8 @@ export class AuthController {
};
}
@@ -43,6 +48,73 @@ export class AuthController {
return this.authService.register(dto);
}
@Post('white-list')
@AuthDocumentation.white()
async newRegister(@Body() dto:WhiteDto){
return this.authService.white(dto)
}
//Inicio de sesión con GOOGLE
@Get('google')
@UseGuards(AuthGuard('google'))
async googleLogin() {
return { msg: 'Redirigiendo a Google' };
}
// Callback de Google
// @Get('google/callback')
// @UseGuards(AuthGuard('google'))
// async googleCallback(@Req() req) {
// const user = req.user;
// if (user.needsPassword) {
// return {
// status: 'NEEDS_PASSWORD',
// email: user.correo,
// message: 'El usuario debe crear una contraseña antes de continuar',
// };
// }
// return this.authService.login(user); // genera JWT
// }
// auth.controller.ts
@Get('google/callback')
@UseGuards(AuthGuard('google'))
async googleCallback(@Req() req, @Res() res: Response) {
const user = req.user;
if (user.needsPassword) {
return res.redirect(
`${process.env.FRONTEND_URL}/password?email=${user.correo}`,
);
}
const jwt = await this.authService.login(user);
return res.redirect(
`${process.env.FRONTEND_URL}/oauth-callback?token=${jwt.access_token}`,
);
}
//Inicio de sesión con GOOGLE
//Set-contraseña
// auth.controller.ts
@Post('set-password')
async setPassword(@Body() dto: SetPasswordDto) {
return this.authService.setPassword(dto);
}
}
+36
View File
@@ -101,4 +101,40 @@ export class AuthDocumentation {
);
}
static white() {
return applyDecorators(
ApiTags('Auth'),
ApiOperation({
summary: 'Registrar usuario',
description: 'Crea un nuevo usuario con correo y origen para una white list.'
}),
ApiConsumes('application/json'),
ApiBody({
schema: {
type: 'object',
properties: {
email: { type: 'string', format: 'email', example: 'nuevo@dominio.com' },
origen: { type: 'string', example: 'RED', description: 'Origen del usuario' }
}
}
}),
ApiResponse({
status: 201,
description: 'Usuario creado correctamente',
schema: {
type: 'object',
properties: {
id_usuario: { type: 'number', example: 1 },
correo: { type: 'string', example: 'nuevo@dominio.com' },
origen: { type: 'string', example: 'RED' }
}
}
}),
ApiResponse({ status: 409, description: 'El correo ya está registrado.' }),
);
}
}
+2 -1
View File
@@ -8,6 +8,7 @@ import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { Origen, UsuariosDelSistema } from '../entities/entities';
import { TypeOrmModule } from '@nestjs/typeorm';
import { GoogleStrategy } from './google.strategy';
@Module({
imports: [
@@ -22,7 +23,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
}),
}),
],
providers: [AuthService, JwtStrategy],
providers: [AuthService, JwtStrategy, GoogleStrategy],
controllers: [AuthController],
})
export class AuthModule { }
+90
View File
@@ -6,6 +6,8 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Origen, UsuariosDelSistema } from '../entities/entities';
import * as bcrypt from 'bcrypt';
import { RegisterDto } from './dto/register.dto';
import { SetPasswordDto } from './dto/createPassword.dto';
import { WhiteDto } from './dto/whiteList.dto';
@Injectable()
export class AuthService {
@@ -75,4 +77,92 @@ export class AuthService {
const { contraseña, ...result } = saved;
return result;
}
async white(dto: WhiteDto) {
// 1) Verificar duplicado
const exists = await this.userRepo.findOne({ where: { correo: dto.email } });
if (exists) {
throw new ConflictException('El correo ya está registrado');
}
let origen = await this.origenRepo.findOne({ where: { origen: dto.origen } });
if (!origen) {
origen = this.origenRepo.create({ origen: dto.origen });
origen = await this.origenRepo.save(origen);
}
// 3) Crear entidad Usuario
const user = this.userRepo.create({
correo: dto.email,
origen: origen,
});
// 4) Guardar en BD
const saved = await this.userRepo.save(user);
// 5) Devolver sin contraseña
const { contraseña, ...result } = saved;
return result;
}
async validateGoogleUser(email: string) {
const user = await this.userRepo.findOne({ where: { correo: email }, relations: ['origen'] });
if (!user) {
// No permitir crear cuenta
return null;
}
if (!user.contraseña) {
// Usuario existe pero nunca puso contraseña → permitirle definirla
return {
...user,
needsPassword: true,
};
}
// Usuario con contraseña → login normal
return user;
}
//Poner contraseñas
// auth.service.ts
async setPassword(dto: SetPasswordDto) {
const user = await this.userRepo.findOne({
where: { correo: dto.email },
relations: ['origen'],
});
if (!user) {
throw new UnauthorizedException('Usuario no encontrado');
}
if (user.contraseña) {
throw new ConflictException('Este usuario ya tiene contraseña');
}
// Hash contraseña nueva
const hash = await bcrypt.hash(dto.password, 10);
user.contraseña = hash;
const updated = await this.userRepo.save(user);
// Retornar con JWT inmediato (login automático)
const payload = { sub: updated.id_usuario, email: updated.correo, tipo: updated.origen.origen };
return {
access_token: this.jwtService.sign(payload),
};
}
}
+11
View File
@@ -0,0 +1,11 @@
// dto/set-password.dto.ts
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class SetPasswordDto {
@IsEmail()
email: string;
@IsNotEmpty()
@MinLength(6)
password: string;
}
+10
View File
@@ -0,0 +1,10 @@
import { IsEmail, IsString, MinLength, MaxLength, IsOptional, IsNumber } from 'class-validator';
export class WhiteDto {
@IsEmail()
email: string;
@IsString()
@MaxLength(60)
origen: string;
}
+39
View File
@@ -0,0 +1,39 @@
// google.strategy.ts
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(
private readonly authService: AuthService,
private readonly config: ConfigService,
) {
super({
clientID: config.get<string>('GOOGLE_CLIENT_ID'),
clientSecret: config.get<string>('GOOGLE_CLIENT_SECRET'),
callbackURL: config.get<string>('GOOGLE_CALLBACK_URL'),
scope: ['email', 'profile'],
});
}
async validate(
accessToken: string,
refreshToken: string,
profile: any,
done: VerifyCallback,
): Promise<any> {
const { emails } = profile;
const email = emails[0].value;
const user = await this.authService.validateGoogleUser(email);
if (!user) {
throw new UnauthorizedException('Usuario no registrado en el sistema');
}
done(null, user);
}
}
+1 -1
View File
@@ -196,7 +196,7 @@ export class TipoUsuario {
@PrimaryGeneratedColumn({ name: 'id_tipo_usuario', type: 'int' })
id_tipo_usuario: number;
@Column({ name: 'tipo_usuario', type: 'varchar', length: 20 })
@Column({ name: 'tipo_usuario', type: 'varchar', length: 30 })
tipo_usuario: string;
@OneToMany(() => UsuarioTipoUsuario, utu => utu.tipoUsuario)
+26 -1
View File
@@ -1,5 +1,5 @@
// src/excel/excel.controller.ts
import { Controller, Post, Get, UseGuards, Request, Res, UploadedFile, UseInterceptors, HttpStatus, Param, ParseIntPipe, Body, BadRequestException } from '@nestjs/common';
import { Controller, Post, Get, UseGuards, Request, Res, UploadedFile, UseInterceptors, HttpStatus, Param, ParseIntPipe, Body, BadRequestException, Delete } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiBearerAuth } from '@nestjs/swagger';
import { FileInterceptor } from '@nestjs/platform-express';
@@ -69,6 +69,7 @@ export class ExcelController {
@ExcelDocumentation.loadExcel()
async loadExcel(@Request() req, @UploadedFile() file: Express.Multer.File) {
const origen = req.user.origen; // ahora sí existe
if (origen != 'LOAD') {
await this.movimientoService.log(
origen,
@@ -78,6 +79,7 @@ export class ExcelController {
throw new Error('Origen no permitido para carga.');
}
try {
const result = await this.excelService.loadFile(file.buffer);
//Actualizar el estado de los movimientos
if (!result) {
@@ -120,6 +122,24 @@ export class ExcelController {
});
await this.excelService.enviarCargaMasiva();
await this.excelService.enviarInforme('AT',"Carga de datos en Servicios PCpuma",
`Se ha hecho una carga en el sistema \n ${result.conteoTiposAt}`
)
await this.excelService.enviarInforme('RED',"Carga de datos en Servicios PCpuma",
`Se ha hecho una carga en el sistema \n ${result.conteoTiposRed}`
)
await this.excelService.enviarInforme('SOLICITA',"Carga de datos en Servicios PCpuma",
`Se ha hecho una carga en el sistema \n ${result.conteoTiposCorreo}`
)
await this.excelService.enviarInforme('CORREO',"Carga de datos en Servicios PCpuma",
`Se ha hecho una carga en el sistema \n ${result.conteoTiposCorreo}`
)
return result
@@ -300,6 +320,11 @@ export class ExcelController {
}
// @Delete()
// async borrar(){
// return await this.excelService.borrarUsers()
// }
+242 -166
View File
@@ -198,178 +198,244 @@ export class ExcelService {
* Valida y luego persiste usuarios y relaciones básicas
*/
async loadFile(buffer: Buffer) {
const file = await this.parseFile(buffer);
const status = this.validateRows(file);
const errs = status.errors;
const rows = status.rowsGood;
const file = await this.parseFile(buffer);
const status = this.validateRows(file);
const errs = status.errors;
const rows = status.rowsGood;
if (rows.length === 0) {
throw new BadRequestException('No se encontraron filas válidas para cargar. ' + errs);
}
if (rows.length === 0) {
throw new BadRequestException(
'No se encontraron filas válidas para cargar. ' + errs,
);
}
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
const conteoTiposRed: Record<string, number> = {};
const conteoTiposAt: Record<string, number> = {};
const conteoTiposSolicita: Record<string, number> = {};
const conteoTiposCorreo: Record<string, number> = {};
try {
const movimiento = await this.movimientoService.logger(
queryRunner,
'LOAD',
'LOADING',
undefined,
'CARGA MASIVA DE USUARIOS'
);
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
let contador = 0;
try {
const movimiento = await this.movimientoService.logger(
queryRunner,
'LOAD',
'LOADING',
undefined,
'CARGA MASIVA DE USUARIOS',
);
for (const r of rows) {
// 🔹 1. Pre-cargar cuentas/rfc ya existentes
const cuentas = rows.map((r) => r.cuenta).filter(Boolean);
const rfcs = rows.map((r) => r.rfc).filter(Boolean);
const whereCond: Array<{ num_cuenta?: string; rfc?: string }> = [{ num_cuenta: r.cuenta }];
if (r.rfc) {
whereCond.push({ rfc: r.rfc });
}
const existentes = await queryRunner.manager.find(Usuario, {
where: [{ num_cuenta: In(cuentas) }, { rfc: In(rfcs) }],
select: ['num_cuenta', 'rfc'],
});
const userExists = await queryRunner.manager.findOne(Usuario, {
where: whereCond,
});
const setCuentas = new Set(existentes.map((u) => u.num_cuenta));
const setRfcs = new Set(existentes.map((u) => u.rfc));
// 🔹 2. Pre-cargar catálogos
const generosUnicos = [...new Set(rows.map((r) => r.sexo).filter(Boolean))];
const carrerasUnicasClave = [
...new Set(rows.map((r) => r.clave).filter(Boolean)),
];
const carrerasUnicasNombre = [
...new Set(rows.map((r) => r.nomCarr).filter(Boolean)),
];
const tiposUnicos = [...new Set(rows.map((r) => r.tipo?.trim()).filter(Boolean))];
const [generosExist, carrerasExist, tiposExist] = await Promise.all([
queryRunner.manager.find(Genero, { where: { genero: In(generosUnicos) } }),
queryRunner.manager.find(Carrera, {
where: [{ clave: In(carrerasUnicasClave) }, { carrera: In(carrerasUnicasNombre) }],
}),
queryRunner.manager.find(TipoUsuario, {
where: { tipo_usuario: In(tiposUnicos) },
}),
]);
if (userExists) {
errs.push(`Cuenta ${r.cuenta} ya existe, se omite.`);
contador++;
continue;
}
const mapGenero = new Map(generosExist.map((g) => [g.genero, g]));
const mapCarreraClave = new Map(carrerasExist.map((c) => [c.clave, c]));
const mapCarreraNom = new Map(carrerasExist.map((c) => [c.carrera, c]));
const mapTipo = new Map(tiposExist.map((t) => [t.tipo_usuario, t]));
if (!r.tipo || !r.nombres || !r.apellidopa || !r.apellidoma) {
errs.push(`Fila con cuenta ${r.cuenta} tiene campos faltantes.`);
contador++;
continue;
}
// 🔹 3. Arreglos para inserciones masivas
const usuarios: Usuario[] = [];
const carrerasUsuario: CarreraUsuario[] = [];
const usuariosTipos: UsuarioTipoUsuario[] = [];
const serviciosActivos: ServActivos[] = [];
let genero = await queryRunner.manager.findOne(Genero, { where: { genero: r.sexo } });
if (!genero) {
genero = queryRunner.manager.create(Genero, { genero: r.sexo });
await queryRunner.manager.save(genero);
}
let contador = 0;
let carrera
if (r.clave || r.nomCarr) {
if (!r.clave) {
carrera = await queryRunner.manager.findOne(Carrera, { where: { carrera: r.nomCarr } });
} else if (!r.nomCarr) {
carrera = await queryRunner.manager.findOne(Carrera, { where: { clave: r.clave } });
}
if (!carrera) {
carrera = queryRunner.manager.create(Carrera, {
clave: r.clave ?? "",
carrera: r.nomCarr.slice(0, 100),
});
await queryRunner.manager.save(carrera);
}
} else {
carrera = null;
}
let tipo = await queryRunner.manager.findOne(TipoUsuario, {
where: { tipo_usuario: r.tipo.trim() },
});
if (!tipo) {
tipo = queryRunner.manager.create(TipoUsuario, { tipo_usuario: r.tipo.trim() });
await queryRunner.manager.save(tipo);
}
const generacion = /^[0-9]{4}$/.test(r.gen?.toString().trim())
? parseInt(r.gen.toString().trim(), 10)
: null;
const usuario = queryRunner.manager.create(Usuario, {
num_cuenta: r.cuenta,
nombre: r.nombres,
a_paterno: r.apellidopa,
a_materno: r.apellidoma,
rfc: r.rfc,
fecha_nacimiento: r.fechnac,
generacion,
genero,
movimiento,
});
const user = await queryRunner.manager.save(usuario);
if (carrera) {
const carreraUsuario = queryRunner.manager.create(CarreraUsuario, {
usuario: user,
carrera,
});
await queryRunner.manager.save(carreraUsuario);
}
const usuarioTipo = queryRunner.manager.create(UsuarioTipoUsuario, {
usuario: user,
tipoUsuario: tipo,
});
await queryRunner.manager.save(usuarioTipo);
const servData = {
usuario: user,
RedStatus: 'Inactivo',
ATStatus: 'Inactivo',
CorreoStatus: 'Inactivo',
PrestamosStatus: 'Inactivo',
};
switch (r.tipo.trim()) {
case 'Diplomado':
Object.assign(servData, { Correo: true });
break;
case 'Extra Largo':
Object.assign(servData, { Correo: true, Prestamos: true });
break;
case 'Servicio Social':
Object.assign(servData, { Correo: true, AT: true, Red: true });
break;
case 'Idiomas R (UNAM)':
case 'Idiomas Sabatino':
Object.assign(servData, { Correo: true, Red: true });
break;
case 'Reinscrito':
case 'Posgrado':
case 'Intercambio UNAM':
case 'Movilidad':
case 'Ampliación de Conocimiento':
case 'Licenciatura':
case 'Profesor':
Object.assign(servData, { Correo: true, AT: true, Red: true, Prestamos: true });
break;
case 'Trabajadores':
Object.assign(servData, { Red: true });
break;
}
const serv = queryRunner.manager.create(ServActivos, servData);
await queryRunner.manager.save(serv);
for (const r of rows) {
if (setCuentas.has(r.cuenta) || (r.rfc && setRfcs.has(r.rfc))) {
errs.push(`Cuenta ${r.cuenta} ya existe, se omite.`);
contador++;
continue;
}
await queryRunner.commitTransaction();
return { inserted: rows.length - contador, id_movimiento: movimiento.id_mov, errors: errs };
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
if (!r.tipo || !r.nombres || !r.apellidopa || !r.apellidoma) {
errs.push(`Fila con cuenta ${r.cuenta} tiene campos faltantes.`);
contador++;
continue;
}
// Genero
let genero = mapGenero.get(r.sexo);
if (!genero) {
genero = queryRunner.manager.create(Genero, { genero: r.sexo });
mapGenero.set(r.sexo, genero);
}
// Carrera
let carrera: Carrera|undefined;
if (r.clave && mapCarreraClave.has(r.clave)) {
carrera = mapCarreraClave.get(r.clave);
} else if (r.nomCarr && mapCarreraNom.has(r.nomCarr)) {
carrera = mapCarreraNom.get(r.nomCarr);
} else if (r.clave || r.nomCarr) {
carrera = queryRunner.manager.create(Carrera, {
clave: r.clave ?? '',
carrera: r.nomCarr?.slice(0, 100) ?? '',
});
mapCarreraClave.set(carrera.clave, carrera);
mapCarreraNom.set(carrera.carrera, carrera);
}
// Tipo
let tipo = mapTipo.get(r.tipo.trim());
if (!tipo) {
tipo = queryRunner.manager.create(TipoUsuario, {
tipo_usuario: r.tipo.trim(),
});
mapTipo.set(r.tipo.trim(), tipo);
}
const generacion =
/^[0-9]{4}$/.test(r.gen?.toString().trim()) ?
parseInt(r.gen.toString().trim(), 10) : null;
const usuario = queryRunner.manager.create(Usuario, {
num_cuenta: r.cuenta,
nombre: r.nombres,
a_paterno: r.apellidopa,
a_materno: r.apellidoma,
rfc: r.rfc,
fecha_nacimiento: r.fechnac,
generacion,
genero,
movimiento,
});
usuarios.push(usuario);
if (carrera) {
const carreraUsuario = queryRunner.manager.create(CarreraUsuario, {
usuario,
carrera,
});
carrerasUsuario.push(carreraUsuario);
}
usuariosTipos.push(
queryRunner.manager.create(UsuarioTipoUsuario, {
usuario,
tipoUsuario: tipo,
}),
);
// Servicios activos
const servData: Partial<ServActivos> = {
usuario,
RedStatus: 'Inactivo',
ATStatus: 'Inactivo',
CorreoStatus: 'Inactivo',
PrestamosStatus: 'Inactivo',
};
switch (r.tipo.trim()) {
case 'Diplomado':
Object.assign(servData, { Correo: true });
conteoTiposCorreo[r.tipo] = (conteoTiposCorreo[r.tipo] ?? 0) + 1;
break;
case 'Extra Largo':
Object.assign(servData, { Correo: true, Prestamos: true });
conteoTiposCorreo[r.tipo] = (conteoTiposCorreo[r.tipo] ?? 0) + 1;
conteoTiposSolicita[r.tipo] = (conteoTiposSolicita[r.tipo] ?? 0) + 1;
break;
case 'Servicio Social':
Object.assign(servData, { Correo: true, AT: true, Red: true });
conteoTiposCorreo[r.tipo] = (conteoTiposCorreo[r.tipo] ?? 0) + 1;
conteoTiposAt[r.tipo] = (conteoTiposAt[r.tipo] ?? 0) + 1;
conteoTiposRed[r.tipo] = (conteoTiposRed[r.tipo] ?? 0) + 1;
break;
case 'Idiomas R (UNAM)':
case 'Idiomas Sabatino':
Object.assign(servData, { Correo: true, Red: true });
conteoTiposCorreo[r.tipo] = (conteoTiposCorreo[r.tipo] ?? 0) + 1;
conteoTiposRed[r.tipo] = (conteoTiposRed[r.tipo] ?? 0) + 1;
break;
case 'Reinscrito':
case 'Posgrado':
case 'Intercambio UNAM':
case 'Movilidad':
case 'Ampliación de Conocimiento':
case 'Licenciatura':
case 'Profesor':
Object.assign(servData, {
Correo: true,
AT: true,
Red: true,
Prestamos: true,
});
conteoTiposCorreo[r.tipo] = (conteoTiposCorreo[r.tipo] ?? 0) + 1;
conteoTiposRed[r.tipo] = (conteoTiposRed[r.tipo] ?? 0) + 1;
conteoTiposAt[r.tipo] = (conteoTiposAt[r.tipo] ?? 0) + 1;
conteoTiposSolicita[r.tipo] = (conteoTiposSolicita[r.tipo] ?? 0) + 1;
break;
case 'Trabajadores':
Object.assign(servData, { Red: true });
conteoTiposRed[r.tipo] = (conteoTiposRed[r.tipo] ?? 0) + 1;
break;
}
serviciosActivos.push(queryRunner.manager.create(ServActivos, servData));
}
// 🔹 4. Guardar en batch
await queryRunner.manager.save([...mapGenero.values()]);
await queryRunner.manager.save([...mapCarreraClave.values()]);
await queryRunner.manager.save([...mapTipo.values()]);
await queryRunner.manager.save(usuarios);
await queryRunner.manager.save(carrerasUsuario);
await queryRunner.manager.save(usuariosTipos);
await queryRunner.manager.save(serviciosActivos);
await queryRunner.commitTransaction();
return {
inserted: rows.length - contador,
id_movimiento: movimiento.id_mov,
errors: errs,
conteoTiposAt,
conteoTiposCorreo,
conteoTiposRed,
conteoTiposSolicita,
};
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
}
@@ -571,13 +637,10 @@ export class ExcelService {
},
servActivo: {
RedStatus: 'Inactivo',
Red: true, // solo los que tienen el servicio de red activo
Red: true, // solo los que tienen el servicio de red
}
},
relations: [
'genero',
'carreraUsuarios', // relación intermedia
'carreraUsuarios.carrera', // carrera asociada a través de la intermedia
'usuarioTipos',
'usuarioTipos.tipoUsuario',
],
@@ -737,7 +800,7 @@ export class ExcelService {
throw new BadRequestException('No se encontraron usuarios para enviar correo');
}
// Aquí puedes enviar el correo usando MailService
// Aquí puedes enviar el correo usnado MailService
await this.mailService.sendMail({
to: usuarios_red.correo ?? '',
subject: 'Carga de Usuarios',
@@ -756,12 +819,7 @@ export class ExcelService {
text: 'Se ha realizado una carga de usuarios en el sistema.',
html: '',
});
await this.mailService.sendMail({
to: usuarios_solicita.correo ?? '',
subject: 'Carga de Usuarios',
text: 'Se ha realizado una carga de usuarios en el sistema.',
html: '',
});
@@ -770,6 +828,24 @@ export class ExcelService {
async enviarInforme(origen:string, subject:string ,text:string, html:string = "" ):Promise<void>{
const user = await this.usuariosDelSistemaRepo.find({where:{origen:{origen}}})
user.forEach(async(u)=>{
await this.mailService.sendMail({
to: u.correo ?? '',
subject,
text,
html,
});
})
}
+7
View File
@@ -1,9 +1,16 @@
import * as crypto from "crypto";
// Hacer que esté disponible como global
(global as any).crypto = crypto;
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { GlobalExceptionFilter } from './helpers/exception.filter';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({ exposedHeaders: ['Content-Disposition'] });
+16 -4
View File
@@ -460,11 +460,19 @@ export class UsuariosService {
}
}
let carrera = await queryRunner.manager.findOne(Carrera, { where: { carrera: usuario.carrera } });
if (!carrera) {
const nuevaCarrera = queryRunner.manager.create(Carrera, { carrera: usuario.carrera, clave: usuario.clave_carrera });
carrera = await queryRunner.manager.save(nuevaCarrera);
let carrera;
if(usuario.carrera!=undefined){
carrera = await queryRunner.manager.findOne(Carrera, { where: { carrera: usuario.carrera } });
}else{
carrera= await queryRunner.manager.findOne(Carrera, {where:{carrera: ""}})
if(!carrera){
const nuevaCarrera = queryRunner.manager.create(Carrera, { carrera: "", clave: "" });
carrera = await queryRunner.manager.save(nuevaCarrera);
}
}
const nuevo = queryRunner.manager.create(Usuario, {
@@ -480,6 +488,8 @@ export class UsuariosService {
contraseña: usuario.contraseña ?? null, // Asegúrate de manejar la contraseña adecuadamente
});
console.log(nuevo)
const savedUser = await queryRunner.manager.save(nuevo);
const userCarrera = queryRunner.manager.create(CarreraUsuario, { carrera, usuario: savedUser });
@@ -622,6 +632,8 @@ export class UsuariosService {
console.log(savedUser)
await queryRunner.commitTransaction();
return { saved: savedUser, movId: mov.id_mov };
} catch (error) {