diff --git a/img/fondo_certificado_mecanica_basica.jpg b/img/fondo_certificado_mecanica_basica.jpg new file mode 100644 index 0000000..a28b307 Binary files /dev/null and b/img/fondo_certificado_mecanica_basica.jpg differ diff --git a/src/certs/CertData.ts b/src/certs/CertData.ts new file mode 100644 index 0000000..9298227 --- /dev/null +++ b/src/certs/CertData.ts @@ -0,0 +1,38 @@ + +export type CertData = { + matpel: T, + /** + * Full name in format: "FIRST_NAMES LAST_NAME_1 LAST_NAME_2" + */ + personFullName: string, + /** + * DNI as a string. + * + * E.g.: "04123523" + */ + personDni: string, + /** + * Cert code as a string. Must have length = 4. + * + * E.g.: "0322" + */ + certCode: string, + /** + * Year of emission as a string. + * + * E.g.: "2023" + */ + certYear: string, + /** + * Month of emission. + * + * E.g.: "05" + */ + certMonth: string, + /** + * Day of emission. + * + * E.g.: "23" + */ + certDay: string, +} diff --git a/src/certs/MATPEL.ts b/src/certs/MATPEL.ts index 95f6eee..3f90357 100644 --- a/src/certs/MATPEL.ts +++ b/src/certs/MATPEL.ts @@ -2,13 +2,12 @@ import { Document, Packer, Paragraph, PageOrientation, ImageRun, HorizontalPositionRelativeFrom, VerticalPositionRelativeFrom, FrameAnchorType, - HorizontalPositionAlign, - VerticalPositionAlign, TextRun, AlignmentType, } from "docx"; import * as QR from "qrcode"; import { Matpel, cm, cmText, cmToEmu, getImage, getMatpelHours, getMatpelLabel } from "./utils"; +import { CertData } from "./CertData"; @@ -104,50 +103,13 @@ const imgEATE = getImage({ -export type CertData = { - matpel: Matpel, - /** - * Full name in format: "FIRST_NAMES LAST_NAME_1 LAST_NAME_2" - */ - personFullName: string, - /** - * DNI as a string. - * - * E.g.: "04123523" - */ - personDni: string, - /** - * Cert code as a string. Must have length = 4. - * - * E.g.: "0322" - */ - certCode: string, - /** - * Year of emission as a string. - * - * E.g.: "2023" - */ - certYear: string, - /** - * Month of emission. - * - * E.g.: "05" - */ - certMonth: string, - /** - * Day of emission. - * - * E.g.: "23" - */ - certDay: string, -} /** * Generates a certificate in format DOCX and in-place. * * @param props Data to use in the certificate * @returns A buffer of the DOCX document */ -export async function matpelCert(props: CertData): Promise { +export async function matpelCert(props: CertData): Promise { const certificateName = new Paragraph({ frame: { position: { diff --git a/src/certs/MECANICA_BASICA.ts b/src/certs/MECANICA_BASICA.ts new file mode 100644 index 0000000..fc8766f --- /dev/null +++ b/src/certs/MECANICA_BASICA.ts @@ -0,0 +1,167 @@ +import { + Document, Packer, Paragraph, PageOrientation, ImageRun, + HorizontalPositionRelativeFrom, VerticalPositionRelativeFrom, + FrameAnchorType, + TextRun, + AlignmentType, +} from "docx"; +import * as QR from "qrcode"; +import { Matpel, cm, cmText, cmToEmu, createSimpleText, getImage, getMatpelHours, getMatpelLabel, getQR } from "./utils"; +import { CertData } from "./CertData"; + +const imgFondoDoc = getImage({ + name: "fondo_certificado_mecanica_basica.jpg", + height: 21.23, + width: 29.8, + horizontalOffset: 0, + verticalOffset: 0, + behindDocument: true, +}); + +const imgCIP = getImage({ + name: "colegio_ingenieros_logo.png", + height: 2.15, + width: 2.15, + horizontalOffset: 0.22, + verticalOffset: 16.45, +}); + +const imgMichigan = getImage({ + name: "michigan_logo.png", + height: 1.96, + width: 2.22, + horizontalOffset: 0.31, + verticalOffset: 18.86, +}); + +const tCertificate = createSimpleText({ + xPosition: 7.75, + yPosition: 1.64, + width: 9, + height: 1.56, + text: "CERTIFICADO", + size: 60, + bold: true, +}); + +// Se expide el presente certificado a: +const tExpediteText = createSimpleText({ + xPosition: 0.76, + yPosition: 3.45, + width: 7.88, + height: 1, + text: "Se expide el presente certificado a:", + size: 22, +}); + +// MECANICA BASICA +const tCourse = createSimpleText({ + xPosition: 1.43, + yPosition: 7, + width: 20.08, + height: 1.5, + text: "MECÁNICA BÁSICA", + size: 72, + bold: true, +}); + +// En los temas de... +const tTopics = createSimpleText({ + xPosition: 1.43, + yPosition: 8.75, + width: 20.08, + height: 1.5, + text: "En los temas de: Lineamientos del Ministerio de Transportes y Comunicaciones (MTC), Operatividad efectiva del equipo. Procedimiento de operación correcta. Equipamiento y mantenimiento preventivo, Medidas de Control y Tipos de IPERC, Cuidados de la maquina durante la operación, Uso de pedales y controles, Funcionamiento del motor", + size: 22, + alignment: AlignmentType.LEFT, +}); + +export async function mecanicaBasicaCert(props: CertData): Promise { + const imgQR = await getQR({ + dni: props.personDni, + height: 2.2, + width: 2.2, + horizontalOffset: 24.63, + verticalOffset: 17.01, + }); + + // FERNANDO ARAOZ + const tName = createSimpleText({ + xPosition: 0.38, + yPosition: 4.35, + width: 24.08, + height: 1.51, + text: props.personFullName, + size: 52, + bold: true, + underline: {}, + }); + + // Fecha de Emision: ... + const tContentPart1 = new Paragraph({ + frame: { + position: { + x: cmText(1.43), + y: cmText(6.16), + }, + width: cmText(20.08), + height: cmText(1), + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + }, + children: [ + new TextRun({ + text: "Identificado con DNI N° ", + font: "Arial", + size: 22, + }), + new TextRun({ + text: props.personDni, + font: "Arial", + size: 22, + bold: true, + }), + new TextRun({ + text: ", al haber aprobado el curso de capacitación en:", + font: "Arial", + size: 22, + }), + ], + alignment: AlignmentType.LEFT, + }); + + const doc = new Document({ + sections: [ + { + properties: { + page: { + size: { + orientation: PageOrientation.LANDSCAPE, + }, + }, + }, + children: [ + tCertificate, + tExpediteText, + tName, + tContentPart1, + tCourse, + tTopics, + new Paragraph({ + children: [ + imgFondoDoc, + imgCIP, + imgMichigan, + imgQR, + ], + }), + ], + }, + ], + }); + + // Return document as a buffer + return await Packer.toBuffer(doc); +} diff --git a/src/certs/utils.ts b/src/certs/utils.ts index a94e13e..f794053 100644 --- a/src/certs/utils.ts +++ b/src/certs/utils.ts @@ -1,6 +1,7 @@ -import { HorizontalPositionRelativeFrom, ImageRun, VerticalPositionRelativeFrom, convertMillimetersToTwip } from "docx"; +import { AlignmentType, FrameAnchorType, HorizontalPositionRelativeFrom, ImageRun, Paragraph, TextRun, VerticalPositionRelativeFrom, convertMillimetersToTwip } from "docx"; import * as fs from "fs"; import { join } from "path"; +import * as QR from "qrcode"; // To define centimeters in a image's size @@ -50,6 +51,77 @@ export function getImage(data: ImgConfig): ImageRun { }); } +export async function getQR(data : { + dni: string, + height: number, + width: number, + horizontalOffset: number, + verticalOffset: number, + behindDocument?: boolean, +}): Promise { + const qr = await QR.toDataURL(`https://www.eegsac.com/alumnoscertificados.php?DNI=${data.dni}`, {margin: 1}); + + return new ImageRun({ + // Magic path, based on dist folder + data: qr, + transformation: { + height: cm(data.height), + width: cm(data.width), + }, + floating: { + zIndex: 0, + horizontalPosition: { + relative: HorizontalPositionRelativeFrom.LEFT_MARGIN, + offset: cmToEmu(data.horizontalOffset), + }, + verticalPosition: { + relative: VerticalPositionRelativeFrom.TOP_MARGIN, + offset: cmToEmu(data.verticalOffset), + }, + behindDocument: data.behindDocument ?? false, + }, + }); +} + +export function createSimpleText(data: { + xPosition: number, + yPosition: number, + width: number, + height: number, + text: string, + font?: string, + bold?: boolean, + size: number, + underline?: Record, + alignment?: AlignmentType, +}): Paragraph { + + return new Paragraph({ + frame: { + position: { + x: cmText(data.xPosition), + y: cmText(data.yPosition), + }, + width: cmText(data.width), + height: cmText(data.height), + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + }, + children: [ + new TextRun({ + text: data.text, + bold: data.bold, + font: data.font ?? "Arial", + size: data.size, + underline: data.underline, + }), + ], + alignment: data.alignment ?? AlignmentType.CENTER, + }); +} + export enum Matpel { _1, diff --git a/src/controller/certificate/certificate.service.ts b/src/controller/certificate/certificate.service.ts index a6015f6..d440e5c 100644 --- a/src/controller/certificate/certificate.service.ts +++ b/src/controller/certificate/certificate.service.ts @@ -6,8 +6,11 @@ import { CertificateDto } from "./certificate.dto"; import { Persona } from "../../model/Persona/persona.entity"; import { CursoGIE } from "../../model/CursoGIE/cursoGIE.entity"; import { GenerateCertificateDto } from "./dto/GenerateCertificate.dto"; -import { CertData, matpelCert } from "src/certs/MATPEL"; +import { matpelCert } from "src/certs/MATPEL"; import { Matpel, getMatpelFileName } from "src/certs/utils"; +import { CertData } from "src/certs/CertData"; +import { getMatpel } from "./generator/matpel"; +import { getMecanicaBasica } from "./generator/mecanicaBasica"; @Injectable() @@ -68,34 +71,55 @@ export class CertificateService { const date = register.fecha_inscripcion.toString(); const [, certYear, certMonth, certDay] = /(\d\d\d\d)-(\d\d)-(\d\d)/.exec(date)!; - // Matpel 1, 2 & 3 - // TODO: Get actual ids from db instead of hard coding them? - if (register.curso !== 10 && register.curso !== 11 && register.curso !== 12) { - throw new BadRequestException("Curso no soportado"); + // IDs from db: educa7ls_plataforma > cursosGIE + switch (register.curso) { + // MECANICA BASICA + case 2: { + return await getMecanicaBasica( + person, + register, + certYear, + certMonth, + certDay, + ); + } + // MATPEL 1 + case 10: { + return await getMatpel( + Matpel._1, + person, + register, + certYear, + certMonth, + certDay, + ); + } + // MATPEL 2 + case 11: { + return await getMatpel( + Matpel._2, + person, + register, + certYear, + certMonth, + certDay, + ); + } + // MATPEL 3 + case 12: { + return await getMatpel( + Matpel._3, + person, + register, + certYear, + certMonth, + certDay, + ); + } + default: { + throw new BadRequestException("Curso no soportado"); + } } - - let matpel = Matpel._1; - if (register.curso === 10) { - matpel = Matpel._1; - } else if (register.curso === 11) { - matpel = Matpel._2; - } else if (register.curso === 12) { - matpel = Matpel._3; - } - - const personFullName = `${person.nombres} ${person.apellidoPaterno} ${person.apellidoMaterno}`; - - const data: CertData = { - matpel, - personFullName: personFullName, - personDni: register.dni, - certCode: register.codigo.toString().padStart(4, "0"), - certYear, - certMonth, - certDay, - }; - - return [await matpelCert(data), `${getMatpelFileName(matpel)} - ${personFullName.toUpperCase()}.docx`]; } async create(data: CertificateDto) { diff --git a/src/controller/certificate/generator/matpel.ts b/src/controller/certificate/generator/matpel.ts new file mode 100644 index 0000000..0307bdf --- /dev/null +++ b/src/controller/certificate/generator/matpel.ts @@ -0,0 +1,28 @@ +import { CertData } from "src/certs/CertData"; +import { matpelCert } from "src/certs/MATPEL"; +import { Matpel, getMatpelFileName } from "src/certs/utils"; +import { Persona } from "src/model/Persona/persona.entity"; +import { RegistroGIE } from "src/model/RegistroGIE/registroGIE.entity"; + +export async function getMatpel( + matpel: Matpel, + person: Persona, + register: RegistroGIE, + certYear: string, + certMonth: string, + certDay: string +): Promise<[Buffer, string]> { + const personFullName = `${person.nombres} ${person.apellidoPaterno} ${person.apellidoMaterno}`; + + const data: CertData = { + matpel, + personFullName: personFullName, + personDni: register.dni, + certCode: register.codigo.toString().padStart(4, "0"), + certYear, + certMonth, + certDay, + }; + + return [await matpelCert(data), `${getMatpelFileName(matpel)} - ${personFullName.toUpperCase()}.docx`]; +} diff --git a/src/controller/certificate/generator/mecanicaBasica.ts b/src/controller/certificate/generator/mecanicaBasica.ts new file mode 100644 index 0000000..eeb0ea3 --- /dev/null +++ b/src/controller/certificate/generator/mecanicaBasica.ts @@ -0,0 +1,26 @@ +import { CertData } from "src/certs/CertData"; +import { mecanicaBasicaCert } from "src/certs/MECANICA_BASICA"; +import { Persona } from "src/model/Persona/persona.entity"; +import { RegistroGIE } from "src/model/RegistroGIE/registroGIE.entity"; + +export async function getMecanicaBasica( + person: Persona, + register: RegistroGIE, + certYear: string, + certMonth: string, + certDay: string +): Promise<[Buffer, string]> { + const personFullName = `${person.nombres} ${person.apellidoPaterno} ${person.apellidoMaterno}`; + + const data: CertData = { + matpel: null, + personFullName: personFullName, + personDni: register.dni, + certCode: register.codigo.toString().padStart(4, "0"), + certYear, + certMonth, + certDay, + }; + + return [await mecanicaBasicaCert(data), `MECANICA BASICA - ${personFullName.toUpperCase()}.docx`]; +}