1 Commits

Author SHA1 Message Date
f1bdce317f Exportar excel con imagen y ahora guarda las imagenes como .png 2026-02-05 18:09:05 -04:00
13 changed files with 250 additions and 332 deletions

View File

@@ -15,5 +15,5 @@ DATABASE_URL="postgresql://postgres:local**@localhost:5432/caja_ahorro" #url con
#Mail Configuration #Mail Configuration
MAIL_HOST=gmail MAIL_HOST=gmail
MAIL_USERNAME= MAIL_USERNAME="123"
MAIL_PASSWORD= MAIL_PASSWORD="123"

View File

@@ -42,6 +42,7 @@
"@nestjs/platform-express": "11.0.0", "@nestjs/platform-express": "11.0.0",
"dotenv": "16.5.0", "dotenv": "16.5.0",
"drizzle-orm": "0.40.0", "drizzle-orm": "0.40.0",
"exceljs": "^4.4.0",
"express": "5.1.0", "express": "5.1.0",
"joi": "17.13.3", "joi": "17.13.3",
"moment": "2.30.1", "moment": "2.30.1",
@@ -50,8 +51,7 @@
"pino-pretty": "13.0.0", "pino-pretty": "13.0.0",
"reflect-metadata": "0.2.0", "reflect-metadata": "0.2.0",
"rxjs": "7.8.1", "rxjs": "7.8.1",
"sharp": "^0.34.5", "sharp": "^0.34.5"
"xlsx-populate": "^1.21.0"
}, },
"devDependencies": { "devDependencies": {
"@nestjs-modules/mailer": "^2.0.2", "@nestjs-modules/mailer": "^2.0.2",

View File

@@ -13,6 +13,7 @@ import {
StreamableFile, StreamableFile,
Header Header
} from '@nestjs/common'; } from '@nestjs/common';
import { Readable } from 'stream';
import { FilesInterceptor } from '@nestjs/platform-express'; import { FilesInterceptor } from '@nestjs/platform-express';
import { import {
ApiConsumes, ApiConsumes,
@@ -33,12 +34,13 @@ import { Public } from '@/common/decorators';
export class TrainingController { export class TrainingController {
constructor(private readonly trainingService: TrainingService) { } constructor(private readonly trainingService: TrainingService) { }
// export training with excel
@Public() @Public()
@Get('export/:id') @Get('export/:id')
@ApiOperation({ summary: 'Export training template' }) @ApiOperation({ summary: 'Export training with excel' })
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Return training template.', description: 'Return training with excel.',
content: { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { schema: { type: 'string', format: 'binary' } } } content: { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { schema: { type: 'string', format: 'binary' } } }
}) })
@Header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') @Header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
@@ -48,9 +50,10 @@ export class TrainingController {
throw new Error('ID is required'); throw new Error('ID is required');
} }
const data = await this.trainingService.exportTemplate(Number(id)); const data = await this.trainingService.exportTemplate(Number(id));
return new StreamableFile(data); return new StreamableFile(Readable.from([data]));
} }
// get all training records
@Get() @Get()
@ApiOperation({ @ApiOperation({
summary: 'Get all training records with pagination and filters', summary: 'Get all training records with pagination and filters',
@@ -68,6 +71,7 @@ export class TrainingController {
}; };
} }
// get training statistics
@Get('statistics') @Get('statistics')
@ApiOperation({ summary: 'Get training statistics' }) @ApiOperation({ summary: 'Get training statistics' })
@ApiResponse({ status: 200, description: 'Return training statistics.' }) @ApiResponse({ status: 200, description: 'Return training statistics.' })
@@ -76,6 +80,7 @@ export class TrainingController {
return { message: 'Training statistics fetched successfully', data }; return { message: 'Training statistics fetched successfully', data };
} }
// get training record by id
@Get(':id') @Get(':id')
@ApiOperation({ summary: 'Get a training record by ID' }) @ApiOperation({ summary: 'Get a training record by ID' })
@ApiResponse({ status: 200, description: 'Return the training record.' }) @ApiResponse({ status: 200, description: 'Return the training record.' })
@@ -85,6 +90,7 @@ export class TrainingController {
return { message: 'Training record fetched successfully', data }; return { message: 'Training record fetched successfully', data };
} }
// create training record
@Post() @Post()
@UseInterceptors(FilesInterceptor('files', 3)) @UseInterceptors(FilesInterceptor('files', 3))
@ApiConsumes('multipart/form-data') @ApiConsumes('multipart/form-data')
@@ -101,6 +107,7 @@ export class TrainingController {
return { message: 'Training record created successfully', data }; return { message: 'Training record created successfully', data };
} }
// update training record
@Patch(':id') @Patch(':id')
@UseInterceptors(FilesInterceptor('files', 3)) @UseInterceptors(FilesInterceptor('files', 3))
@ApiConsumes('multipart/form-data') @ApiConsumes('multipart/form-data')
@@ -123,6 +130,7 @@ export class TrainingController {
return { message: 'Training record updated successfully', data }; return { message: 'Training record updated successfully', data };
} }
// delete training record
@Delete(':id') @Delete(':id')
@ApiOperation({ summary: 'Delete a training record' }) @ApiOperation({ summary: 'Delete a training record' })
@ApiResponse({ @ApiResponse({

View File

@@ -1,16 +1,19 @@
import { HttpException, HttpStatus, Inject, Injectable, NotFoundException } from '@nestjs/common'; import { HttpException, HttpStatus, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { and, eq, gte, ilike, lte, or, SQL, sql } from 'drizzle-orm'; import { and, eq, getTableColumns, gte, ilike, lte, or, SQL, sql } from 'drizzle-orm';
import { NodePgDatabase } from 'drizzle-orm/node-postgres'; import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { DRIZZLE_PROVIDER } from 'src/database/drizzle-provider'; import { DRIZZLE_PROVIDER } from 'src/database/drizzle-provider';
import * as schema from 'src/database/index'; import * as schema from 'src/database/index';
import { municipalities, parishes, states, trainingSurveys } from 'src/database/index'; import { municipalities, parishes, states, trainingSurveys } from 'src/database/index';
import XlsxPopulate from 'xlsx-populate'; // import XlsxPopulate from 'xlsx-populate';
import ExcelJS from 'exceljs';
import { PaginationDto } from '../../common/dto/pagination.dto'; import { PaginationDto } from '../../common/dto/pagination.dto';
import { CreateTrainingDto } from './dto/create-training.dto'; import { CreateTrainingDto } from './dto/create-training.dto';
import { TrainingStatisticsFilterDto } from './dto/training-statistics-filter.dto'; import { TrainingStatisticsFilterDto } from './dto/training-statistics-filter.dto';
import { UpdateTrainingDto } from './dto/update-training.dto'; import { UpdateTrainingDto } from './dto/update-training.dto';
import sharp from 'sharp';
@Injectable() @Injectable()
export class TrainingService { export class TrainingService {
@@ -215,9 +218,17 @@ export class TrainingService {
async findOne(id: number) { async findOne(id: number) {
const find = await this.drizzle const find = await this.drizzle
.select() .select({
...getTableColumns(trainingSurveys),
stateName: states.name,
municipalityName: municipalities.name,
parishName: parishes.name,
})
.from(trainingSurveys) .from(trainingSurveys)
.where(eq(trainingSurveys.id, id)); .leftJoin(states, eq(trainingSurveys.state, states.id))
.leftJoin(municipalities, eq(trainingSurveys.municipality, municipalities.id))
.leftJoin(parishes, eq(trainingSurveys.parish, parishes.id))
.where(eq(trainingSurveys.id, id))
if (find.length === 0) { if (find.length === 0) {
throw new HttpException( throw new HttpException(
@@ -239,9 +250,14 @@ export class TrainingService {
const savedPaths: string[] = []; const savedPaths: string[] = [];
for (const file of files) { for (const file of files) {
const fileName = `${Date.now()}-${Math.round(Math.random() * 1e9)}${path.extname(file.originalname)}`; const fileName = `${Date.now()}-${Math.round(Math.random() * 1e9)}.png`;
const filePath = path.join(uploadDir, fileName); const filePath = path.join(uploadDir, fileName);
fs.writeFileSync(filePath, file.buffer);
// Convertir a PNG usando sharp antes de guardar
await sharp(file.buffer)
.png()
.toFile(filePath);
savedPaths.push(`/assets/training/${fileName}`); savedPaths.push(`/assets/training/${fileName}`);
} }
return savedPaths; return savedPaths;
@@ -270,7 +286,6 @@ export class TrainingService {
const photoPaths = await this.saveFiles(files); const photoPaths = await this.saveFiles(files);
// 2. Extraer solo visitDate para formatearlo. // 2. Extraer solo visitDate para formatearlo.
// Ya NO extraemos state, municipality, etc. porque no vienen en el DTO.
const { visitDate, state, municipality, parish, ...rest } = createTrainingDto; const { visitDate, state, municipality, parish, ...rest } = createTrainingDto;
const [newRecord] = await this.drizzle const [newRecord] = await this.drizzle
@@ -362,200 +377,18 @@ export class TrainingService {
}; };
} }
// async exportTemplate() {
// const templatePath = path.join(
// __dirname,
// 'export_template',
// 'excel.osp.xlsx',
// );
// const templateBuffer = fs.readFileSync(templatePath);
// const workbook: any = await XlsxPopulate.fromDataAsync(templateBuffer);
// const sheet = workbook.sheet(0);
// const records = await this.drizzle
// .select({
// firstname: trainingSurveys.firstname,
// lastname: trainingSurveys.lastname,
// visitDate: trainingSurveys.visitDate,
// stateName: states.name,
// municipalityName: municipalities.name,
// parishName: parishes.name,
// communeName: trainingSurveys.communeName,
// siturCodeCommune: trainingSurveys.siturCodeCommune,
// communalCouncil: trainingSurveys.communalCouncil,
// siturCodeCommunalCouncil: trainingSurveys.siturCodeCommunalCouncil,
// productiveActivity: trainingSurveys.productiveActivity,
// ospName: trainingSurveys.ospName,
// ospAddress: trainingSurveys.ospAddress,
// ospRif: trainingSurveys.ospRif,
// ospType: trainingSurveys.ospType,
// currentStatus: trainingSurveys.currentStatus,
// companyConstitutionYear: trainingSurveys.companyConstitutionYear,
// ospResponsibleFullname: trainingSurveys.ospResponsibleFullname,
// ospResponsibleCedula: trainingSurveys.ospResponsibleCedula,
// ospResponsibleRif: trainingSurveys.ospResponsibleRif,
// ospResponsiblePhone: trainingSurveys.ospResponsiblePhone,
// ospResponsibleEmail: trainingSurveys.ospResponsibleEmail,
// civilState: trainingSurveys.civilState,
// familyBurden: trainingSurveys.familyBurden,
// numberOfChildren: trainingSurveys.numberOfChildren,
// generalObservations: trainingSurveys.generalObservations,
// paralysisReason: trainingSurveys.paralysisReason,
// productList: trainingSurveys.productList,
// infrastructureMt2: trainingSurveys.infrastructureMt2,
// photo1: trainingSurveys.photo1,
// photo2: trainingSurveys.photo2,
// photo3: trainingSurveys.photo3,
// })
// .from(trainingSurveys)
// .leftJoin(states, eq(trainingSurveys.state, states.id))
// .leftJoin(
// municipalities,
// eq(trainingSurveys.municipality, municipalities.id),
// )
// .leftJoin(parishes, eq(trainingSurveys.parish, parishes.id))
// .execute();
// let currentRow = 2;
// for (const record of records) {
// const date = new Date(record.visitDate);
// const dateStr = date.toLocaleDateString('es-VE');
// const timeStr = date.toLocaleTimeString('es-VE');
// sheet.cell(`A${currentRow}`).value(record.firstname);
// sheet.cell(`B${currentRow}`).value(record.lastname);
// sheet.cell(`C${currentRow}`).value(dateStr);
// sheet.cell(`D${currentRow}`).value(timeStr);
// sheet.cell(`E${currentRow}`).value(record.stateName || '');
// sheet.cell(`F${currentRow}`).value(record.municipalityName || '');
// sheet.cell(`G${currentRow}`).value(record.parishName || '');
// sheet.cell(`H${currentRow}`).value(record.communeName);
// sheet.cell(`I${currentRow}`).value(record.siturCodeCommune);
// sheet.cell(`J${currentRow}`).value(record.communalCouncil);
// sheet.cell(`K${currentRow}`).value(record.siturCodeCommunalCouncil);
// sheet.cell(`L${currentRow}`).value(record.productiveActivity);
// sheet.cell(`M${currentRow}`).value(''); // requerimiento financiero description
// sheet.cell(`N${currentRow}`).value(record.ospName);
// sheet.cell(`O${currentRow}`).value(record.ospAddress);
// sheet.cell(`P${currentRow}`).value(record.ospRif);
// sheet.cell(`Q${currentRow}`).value(record.ospType);
// sheet.cell(`R${currentRow}`).value(record.currentStatus);
// sheet.cell(`S${currentRow}`).value(record.companyConstitutionYear);
// const products = (record.productList as any[]) || [];
// const totalProducers = products.reduce(
// (sum, p) =>
// sum + (Number(p.menCount) || 0) + (Number(p.womenCount) || 0),
// 0,
// );
// const productsDesc = products.map((p) => p.name).join(', ');
// sheet.cell(`T${currentRow}`).value(totalProducers);
// sheet.cell(`U${currentRow}`).value(productsDesc);
// sheet.cell(`V${currentRow}`).value(record.infrastructureMt2);
// sheet.cell(`W${currentRow}`).value('');
// sheet.cell(`X${currentRow}`).value(record.paralysisReason || '');
// sheet.cell(`Y${currentRow}`).value(record.ospResponsibleFullname);
// sheet.cell(`Z${currentRow}`).value(record.ospResponsibleCedula);
// sheet.cell(`AA${currentRow}`).value(record.ospResponsibleRif);
// sheet.cell(`AB${currentRow}`).value(record.ospResponsiblePhone);
// sheet.cell(`AC${currentRow}`).value(record.ospResponsibleEmail);
// sheet.cell(`AD${currentRow}`).value(record.civilState);
// sheet.cell(`AE${currentRow}`).value(record.familyBurden);
// sheet.cell(`AF${currentRow}`).value(record.numberOfChildren);
// sheet.cell(`AG${currentRow}`).value(record.generalObservations || '');
// sheet.cell(`AH${currentRow}`).value(record.photo1 || '');
// sheet.cell(`AI${currentRow}`).value(record.photo2 || '');
// sheet.cell(`AJ${currentRow}`).value(record.photo3 || '');
// currentRow++;
// }
// return await workbook.outputAsync();
// }
async exportTemplate(id: number) { async exportTemplate(id: number) {
// Validar que el registro exista // Validar que el registro exista
const exist = await this.findOne(id); const record = await this.findOne(id);
if (!exist) throw new NotFoundException(`No se encontro el registro`); if (!record) throw new NotFoundException(`No se encontró el registro`);
// Obtener los datos del registro // Formatear fecha y hora
const records = await this.drizzle const dateObj = new Date(record.visitDate);
.select({ const fechaFormateada = dateObj.toLocaleDateString('es-ES');
// id: trainingSurveys.id, const horaFormateada = dateObj.toLocaleTimeString('es-ES', {
visitDate: trainingSurveys.visitDate, hour: '2-digit',
ospName: trainingSurveys.ospName, minute: '2-digit',
productiveSector: trainingSurveys.productiveSector, });
ospAddress: trainingSurveys.ospAddress,
ospRif: trainingSurveys.ospRif,
siturCodeCommune: trainingSurveys.siturCodeCommune,
communeEmail: trainingSurveys.communeEmail,
communeRif: trainingSurveys.communeRif,
communeSpokespersonName: trainingSurveys.communeSpokespersonName,
communeSpokespersonPhone: trainingSurveys.communeSpokespersonPhone,
siturCodeCommunalCouncil: trainingSurveys.siturCodeCommunalCouncil,
communalCouncilRif: trainingSurveys.communalCouncilRif,
communalCouncilSpokespersonName: trainingSurveys.communalCouncilSpokespersonName,
communalCouncilSpokespersonPhone: trainingSurveys.communalCouncilSpokespersonPhone,
ospType: trainingSurveys.ospType,
productiveActivity: trainingSurveys.productiveActivity, // Sector Productivo
companyConstitutionYear: trainingSurveys.companyConstitutionYear,
infrastructureMt2: trainingSurveys.infrastructureMt2,
hasTransport: trainingSurveys.hasTransport,
structureType: trainingSurveys.structureType,
isOpenSpace: trainingSurveys.isOpenSpace,
ospResponsibleFullname: trainingSurveys.ospResponsibleFullname,
ospResponsibleCedula: trainingSurveys.ospResponsibleCedula,
ospResponsiblePhone: trainingSurveys.ospResponsiblePhone,
productList: trainingSurveys.productList,
equipmentList: trainingSurveys.equipmentList,
productionList: trainingSurveys.productionList,
// photo1: trainingSurveys.photo1
})
.from(trainingSurveys)
.where(eq(trainingSurveys.id, id))
// .leftJoin(states, eq(trainingSurveys.state, states.id))
// .leftJoin(municipalities,eq(trainingSurveys.municipality, municipalities.id))
// .leftJoin(parishes, eq(trainingSurveys.parish, parishes.id))
let equipmentList: any[] = Array.isArray(records[0].equipmentList) ? records[0].equipmentList : [];
let productList: any[] = Array.isArray(records[0].productList) ? records[0].productList : [];
let productionList: any[] = Array.isArray(records[0].productionList) ? records[0].productionList : [];
console.log('equipmentList', equipmentList);
console.log('productList', productList);
console.log('productionList', productionList);
let equipmentListArray: any[] = [];
let productListArray: any[] = [];
let productionListArray: any[] = [];
const equipmentListCount = equipmentList.length;
for (let i = 0; i < equipmentListCount; i++) {
equipmentListArray.push([equipmentList[i].machine, '', equipmentList[i].quantity]);
}
const productListCount = productList.length;
for (let i = 0; i < productListCount; i++) {
productListArray.push([productList[i].productName, productList[i].dailyCount, productList[i].weeklyCount, productList[i].monthlyCount]);
}
const productionListCount = productionList.length;
for (let i = 0; i < productionListCount; i++) {
productionListArray.push([productionList[i].rawMaterial, '', productionList[i].quantity]);
}
// Ruta de la plantilla // Ruta de la plantilla
const templatePath = path.join( const templatePath = path.join(
@@ -564,53 +397,143 @@ export class TrainingService {
'excel.osp.xlsx', 'excel.osp.xlsx',
); );
// Cargar la plantilla // Cargar la plantilla con ExcelJS
const book = await XlsxPopulate.fromFileAsync(templatePath); const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile(templatePath);
const worksheet = workbook.getWorksheet(1); // Usar la primera hoja
const isoString = records[0].visitDate; if (!worksheet) {
const dateObj = new Date(isoString); throw new Error('No se pudo encontrar la hoja de trabajo en la plantilla');
const fechaFormateada = dateObj.toLocaleDateString('es-ES'); }
const horaFormateada = dateObj.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
// Llenar los datos // Llenar los datos principales
book.sheet(0).cell('A6').value(records[0].productiveSector); worksheet.getCell('A6').value = record.productiveSector;
book.sheet(0).cell('D6').value(records[0].ospName); worksheet.getCell('B8').value = record.stateName;
book.sheet(0).cell('L5').value(fechaFormateada); worksheet.getCell('E8').value = record.municipalityName;
book.sheet(0).cell('L6').value(horaFormateada); worksheet.getCell('B9').value = record.parishName;
book.sheet(0).cell('B10').value(records[0].ospAddress); worksheet.getCell('D6').value = record.ospName;
book.sheet(0).cell('C11').value(records[0].communeEmail); worksheet.getCell('L5').value = fechaFormateada;
book.sheet(0).cell('C12').value(records[0].communeSpokespersonName); worksheet.getCell('L6').value = horaFormateada;
book.sheet(0).cell('G11').value(records[0].communeRif); worksheet.getCell('B10').value = record.ospAddress;
book.sheet(0).cell('G12').value(records[0].communeSpokespersonPhone); worksheet.getCell('C11').value = record.communeEmail;
book.sheet(0).cell('C13').value(records[0].siturCodeCommune); worksheet.getCell('C12').value = record.communeSpokespersonName;
book.sheet(0).cell('G13').value(records[0].siturCodeCommunalCouncil); worksheet.getCell('G11').value = record.communeRif;
book.sheet(0).cell('G14').value(records[0].communalCouncilRif); worksheet.getCell('G12').value = record.communeSpokespersonPhone;
book.sheet(0).cell('C15').value(records[0].communalCouncilSpokespersonName); worksheet.getCell('C13').value = record.siturCodeCommune;
book.sheet(0).cell('G15').value(records[0].communalCouncilSpokespersonPhone); worksheet.getCell('G13').value = record.siturCodeCommunalCouncil;
book.sheet(0).cell('C16').value(records[0].ospType); worksheet.getCell('G14').value = record.communalCouncilRif;
book.sheet(0).cell('C17').value(records[0].ospName); worksheet.getCell('C15').value = record.communalCouncilSpokespersonName;
book.sheet(0).cell('C18').value(records[0].productiveActivity); worksheet.getCell('G15').value = record.communalCouncilSpokespersonPhone;
book.sheet(0).cell('C19').value('Proveedores'); worksheet.getCell('C16').value = record.ospType;
book.sheet(0).cell('C20').value(records[0].companyConstitutionYear); worksheet.getCell('C17').value = record.ospName;
book.sheet(0).cell('C21').value(records[0].infrastructureMt2); worksheet.getCell('C18').value = record.productiveActivity;
book.sheet(0).cell('G17').value(records[0].ospRif); worksheet.getCell('C19').value = 'Proveedores';
worksheet.getCell('C20').value = record.companyConstitutionYear;
worksheet.getCell('C21').value = record.infrastructureMt2;
worksheet.getCell('G17').value = record.ospRif;
book.sheet(0).cell(records[0].hasTransport === true ? 'J19' : 'L19').value('X'); worksheet.getCell(record.hasTransport === true ? 'J19' : 'L19').value = 'X';
book.sheet(0).cell(records[0].structureType === 'CASA' ? 'J20' : 'L20').value('X'); worksheet.getCell(record.structureType === 'CASA' ? 'J20' : 'L20').value =
book.sheet(0).cell(records[0].isOpenSpace === true ? 'J21' : 'L21').value('X'); 'X';
worksheet.getCell(record.isOpenSpace === true ? 'J21' : 'L21').value = 'X';
book.sheet(0).cell('A24').value(records[0].ospResponsibleFullname); worksheet.getCell('A24').value = record.ospResponsibleFullname;
book.sheet(0).cell('C24').value(records[0].ospResponsibleCedula); worksheet.getCell('C24').value = record.ospResponsibleCedula;
book.sheet(0).cell('E24').value(records[0].ospResponsiblePhone); worksheet.getCell('E24').value = record.ospResponsiblePhone;
worksheet.getCell('J24').value = 'N Femenino'; // Placeholder si no hay dato
worksheet.getCell('L24').value = 'N Masculino'; // Placeholder si no hay dato
book.sheet(0).cell('J24').value('N Femenino'); // const photo1 = record.photo1;
book.sheet(0).cell('L24').value('N Masculino'); // const photo2 = record.photo2;
// const photo3 = record.photo3;
book.sheet(0).range(`A28:C${equipmentListCount + 28}`).value(equipmentListArray); if (record.photo1) {
book.sheet(0).range(`E28:G${productionListCount + 28}`).value(productionListArray); const image = record.photo1.slice(17);
book.sheet(0).range(`I28:L${productListCount + 28}`).value(productListArray); const extension = image.split('.')[1];
return book.outputAsync(); // Validar que sea una imagen png, gif o jpeg
if (extension === 'png' || extension === 'gif' || extension === 'jpeg') {
// Ruta de la imagen
const imagePath = path.join(
__dirname,
'../../../',
`uploads/training/${image}`,
);
// Add an image to the workbook from a file buffer
const logoId = workbook.addImage({
filename: imagePath,
extension: extension,
});
// Anchor the image to a specific cell (e.g., A1)
worksheet.addImage(logoId, `I7:L17`); // Spans from A1 to C3
}
}
// let i = 1;
// while (i <= 3) {
// const element = record[`photo${i}`];
// if (element) {
// const image = element.slice(17);
// const extension: extensionType = image.split('.')[1];
// // Validar que sea una imagen png, gif o jpeg
// if (extension === 'png' || extension === 'gif' || extension === 'jpeg') {
// // Ruta de la imagen
// const imagePath = path.join(
// __dirname,
// '../../../',
// `uploads/training/${image}`,
// );
// // Add an image to the workbook from a file buffer
// const logoId = workbook.addImage({
// filename: imagePath,
// extension: extension,
// });
// // Anchor the image to a specific cell (e.g., A1)
// worksheet.addImage(logoId, `I7:L17`); // Spans from A1 to C3
// i = 4;
// }
// }
// i++;
// }
// Listas (Equipos, Materia Prima, Productos)
const equipmentList = Array.isArray(record.equipmentList)
? record.equipmentList
: [];
const productionList = Array.isArray(record.productionList)
? record.productionList
: [];
const productList = Array.isArray(record.productList)
? record.productList
: [];
// Colocar listas empezando en la fila 28
equipmentList.forEach((item: any, i: number) => {
const row = 28 + i;
worksheet.getCell(`A${row}`).value = item.machine;
worksheet.getCell(`C${row}`).value = item.quantity;
});
productionList.forEach((item: any, i: number) => {
const row = 28 + i;
worksheet.getCell(`E${row}`).value = item.rawMaterial;
worksheet.getCell(`G${row}`).value = item.quantity;
});
productList.forEach((item: any, i: number) => {
const row = 28 + i;
worksheet.getCell(`I${row}`).value = item.productName;
worksheet.getCell(`J${row}`).value = item.dailyCount;
worksheet.getCell(`K${row}`).value = item.weeklyCount;
worksheet.getCell(`L${row}`).value = item.monthlyCount;
});
return await workbook.xlsx.writeBuffer();
} }
} }

View File

@@ -21,14 +21,14 @@ export default function EditTrainingPage() {
} }
return ( return (
<PageContainer scrollable> // <PageContainer scrollable>
<div className="flex-1 space-y-4"> <div className="p-6 space-y-6">
<CreateTrainingForm <CreateTrainingForm
defaultValues={training} defaultValues={training}
onSuccess={() => router.push('/dashboard/formulario')} onSuccess={() => router.push('/dashboard/formulario')}
onCancel={() => router.back()} onCancel={() => router.back()}
/> />
</div> </div>
</PageContainer> // </PageContainer>
); );
} }

View File

@@ -23,8 +23,9 @@ export default async function Page({ searchParams }: PageProps) {
} = searchParamsCache.parse(await searchParams); } = searchParamsCache.parse(await searchParams);
return ( return (
<PageContainer> // <PageContainer>
<div className="flex flex-1 flex-col space-y-6"> // <div className="flex flex-1 flex-col space-y-6">
< div className="p-6 space-y-6" >
<TrainingHeader /> <TrainingHeader />
<TrainingTableAction /> <TrainingTableAction />
<TrainingList <TrainingList
@@ -34,6 +35,6 @@ export default async function Page({ searchParams }: PageProps) {
apiUrl={env.API_URL} apiUrl={env.API_URL}
/> />
</div > </div >
</PageContainer> // </PageContainer>
); );
} }

View File

@@ -2,6 +2,7 @@ import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
export default async function Dashboard() { export default async function Dashboard() {
console.log('La sesion es llamada');
const session = await auth(); const session = await auth();
if (!session?.user) { if (!session?.user) {

View File

@@ -44,6 +44,7 @@ const RootLayout = async ({
}: Readonly<{ }: Readonly<{
children: ReactNode; children: ReactNode;
}>) => { }>) => {
console.log('La sesion es llamada');
const session = await auth(); const session = await auth();
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>

View File

@@ -1,5 +1,5 @@
'use server'; 'use server';
import { safeFetchApi } from '@/lib'; import { safeFetchApi } from '@/lib/fetch.api';
import { loginResponseSchema, UserFormValue } from '../schemas/login'; import { loginResponseSchema, UserFormValue } from '../schemas/login';
type LoginActionSuccess = { type LoginActionSuccess = {

View File

@@ -90,7 +90,7 @@ export const createTrainingAction = async (
payloadToSend = rest as any; payloadToSend = rest as any;
} }
console.log(payloadToSend); // console.log(payloadToSend);
const [error, data] = await safeFetchApi( const [error, data] = await safeFetchApi(
TrainingMutate, TrainingMutate,

View File

@@ -305,24 +305,24 @@ export function CreateTrainingForm({
// 4. Aquí se agregan las NUEVAS fotos (binary) si el usuario seleccionó alguna // 4. Aquí se agregan las NUEVAS fotos (binary) si el usuario seleccionó alguna
selectedFiles.forEach((file) => { selectedFiles.forEach((file) => {
data.append('files', file); data.append('files', file);
console.log(file);
}); });
console.log(data);
const mutation = defaultValues?.id ? updateTraining : createTraining; const mutation = defaultValues?.id ? updateTraining : createTraining;
// mutation(data as any, { mutation(data as any, {
// onSuccess: () => { onSuccess: () => {
// form.reset(); form.reset();
// setSelectedFiles([]); setSelectedFiles([]);
// onSuccess?.(); onSuccess?.();
// }, },
// onError: (e) => { onError: (e) => {
// console.error(e); console.error(e);
// form.setError('root', { form.setError('root', {
// type: 'manual', type: 'manual',
// message: 'Error al guardar el registro', message: 'Error al guardar el registro',
// }); });
// }, },
// }); });
}; };
return ( return (

View File

@@ -77,7 +77,7 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
{/* <TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
@@ -92,7 +92,7 @@ export const CellAction: React.FC<CellActionProps> = ({ data, apiUrl }) => {
<p>Exportar Excel</p> <p>Exportar Excel</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> */} </TooltipProvider>
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>

View File

@@ -1,8 +1,7 @@
// lib/auth.config.ts
import { SignInAction } from '@/feactures/auth/actions/login-action'; import { SignInAction } from '@/feactures/auth/actions/login-action';
import { resfreshTokenAction } from '@/feactures/auth/actions/refresh-token-action'; import { resfreshTokenAction } from '@/feactures/auth/actions/refresh-token-action';
import { CredentialsSignin, NextAuthConfig, Session, User } from 'next-auth'; import { CredentialsSignin, NextAuthConfig, Session, User } from 'next-auth';
import { DefaultJWT } from 'next-auth/jwt'; // import { DefaultJWT } from 'next-auth/jwt';
import CredentialProvider from 'next-auth/providers/credentials'; import CredentialProvider from 'next-auth/providers/credentials';
@@ -92,8 +91,6 @@ const authConfig: NextAuthConfig = {
refresh_token: response?.tokens.refresh_token ?? '', refresh_token: response?.tokens.refresh_token ?? '',
refresh_expire_in: response?.tokens.refresh_expire_in ?? 0, refresh_expire_in: response?.tokens.refresh_expire_in ?? 0,
}; };
}, },
}), }),
], ],
@@ -101,11 +98,7 @@ const authConfig: NextAuthConfig = {
signIn: '/', //sigin page signIn: '/', //sigin page
}, },
callbacks: { callbacks: {
async jwt({ token, user }:{ async jwt({ token, user }: { user: User, token: any }) {
user: User
token: any
}) {
// 1. Manejar el inicio de sesión inicial // 1. Manejar el inicio de sesión inicial
// El `user` solo se proporciona en el primer inicio de sesión. // El `user` solo se proporciona en el primer inicio de sesión.
if (user) { if (user) {
@@ -120,7 +113,6 @@ const authConfig: NextAuthConfig = {
refresh_token: user.refresh_token, refresh_token: user.refresh_token,
refresh_expire_in: user.refresh_expire_in refresh_expire_in: user.refresh_expire_in
} }
// return token;
} }
// 2. Si no es un nuevo login, verificar la expiración del token // 2. Si no es un nuevo login, verificar la expiración del token
@@ -131,19 +123,11 @@ const authConfig: NextAuthConfig = {
return token; // Si no ha expirado, no hacer nada y devolver el token actual return token; // Si no ha expirado, no hacer nada y devolver el token actual
} }
// console.log("Now Access Expire:",token.access_expire_in);
// 3. Si el token de acceso ha expirado, verificar el refresh token // 3. Si el token de acceso ha expirado, verificar el refresh token
// console.log("Access token ha expirado. Verificando refresh token...");
if (now > (token.refresh_expire_in as number)) { if (now > (token.refresh_expire_in as number)) {
// console.log("Refresh token ha expirado. Forzando logout.");
return null; // Forzar el logout al devolver null return null; // Forzar el logout al devolver null
} }
// console.log("token:", token.refresh_token);
// 4. Si el token de acceso ha expirado pero el refresh token es válido, renovar // 4. Si el token de acceso ha expirado pero el refresh token es válido, renovar
console.log("Renovando token de acceso..."); console.log("Renovando token de acceso...");
try { try {
@@ -151,9 +135,6 @@ const authConfig: NextAuthConfig = {
const res = await resfreshTokenAction(refresh_token); const res = await resfreshTokenAction(refresh_token);
// console.log('res', res);
if (!res || !res.tokens) { if (!res || !res.tokens) {
throw new Error('Fallo en la respuesta de la API de refresco.'); throw new Error('Fallo en la respuesta de la API de refresco.');
} }
@@ -163,10 +144,12 @@ const authConfig: NextAuthConfig = {
token.access_expire_in = res.tokens.access_expire_in; token.access_expire_in = res.tokens.access_expire_in;
token.refresh_token = res.tokens.refresh_token; token.refresh_token = res.tokens.refresh_token;
token.refresh_expire_in = res.tokens.refresh_expire_in; token.refresh_expire_in = res.tokens.refresh_expire_in;
console.log("Token renovado exitosamente.");
return token; return token;
} catch (error) { } catch (error) {
console.error("Error al renovar el token: ", error); console.error(error);
return null; // Fallo al renovar, forzar logout return null; // Fallo al renovar, forzar logout
} }
}, },
@@ -182,6 +165,7 @@ const authConfig: NextAuthConfig = {
email: token.email as string, email: token.email as string,
role: token.role as Array<{ id: number; rol: string }>, role: token.role as Array<{ id: number; rol: string }>,
}; };
console.log("Session: Habilitado");
return session; return session;
}, },
}, },