diff --git a/img/fondo_seg_op_maq_pes.png b/img/fondo_seg_op_maq_pes.png new file mode 100644 index 0000000..188b7cd Binary files /dev/null and b/img/fondo_seg_op_maq_pes.png differ diff --git a/img/logo_hazescorp.png b/img/logo_hazescorp.png new file mode 100644 index 0000000..2817f61 Binary files /dev/null and b/img/logo_hazescorp.png differ diff --git a/img/mundo_maq.png b/img/mundo_maq.png new file mode 100644 index 0000000..8cf3933 Binary files /dev/null and b/img/mundo_maq.png differ diff --git a/src/app.module.ts b/src/app.module.ts index 18df5d0..fee3fd3 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,20 +10,32 @@ import { PersonService } from "./controller/person/person.service"; import { SubjectController } from "./controller/subject/subject.controller"; import { SubjectService } from "./controller/subject/subject.service"; import * as dotenv from "dotenv"; -import { MigratorController } from "./controller/migrator/migrator.controller"; // Must be done before initializing DB. console.log(dotenv.config()); +const user = process.env.MY_SQL_USER; +const host = process.env.MY_SQL_HOST; +const port = parseInt(process.env.MY_SQL_PORT ?? "3306", 10); +const password = process.env.MY_SQL_PASSWORD; +const db = process.env.MY_SQL_DB; + + @Module({ imports: [ TypeOrmModule.forRoot({ type: "mysql", - host: "localhost", + // To allow remote connections a manual URI is used, for some + url: `mysql://${user}:${password}@${host}:${port}/${db}`, + + // These values are rejected when using a remote connection, for some reason + /* + host: process.env.MY_SQL_HOST, port: parseInt(process.env.MY_SQL_PORT ?? "3306", 10), username: process.env.MY_SQL_USER, password: process.env.MY_SQL_PASSWORD, database: process.env.MY_SQL_DB, + */ entities: [ Persona, CursoGIE, diff --git a/src/certs/4X4.ts b/src/certs/4X4.ts index dfac144..67a79fe 100644 --- a/src/certs/4X4.ts +++ b/src/certs/4X4.ts @@ -145,6 +145,7 @@ const photoSection = new Paragraph({ export async function _4X4Cert(props: CertData): Promise { const imgQR = await getQR({ + iid: props.certIId, dni: props.personDni, height: 2.04, width: 2.04, diff --git a/src/certs/CertData.ts b/src/certs/CertData.ts index 9298227..05a223b 100644 --- a/src/certs/CertData.ts +++ b/src/certs/CertData.ts @@ -35,4 +35,8 @@ export type CertData = { * E.g.: "23" */ certDay: string, + /** + * Id of the certificate + */ + certIId: number, } diff --git a/src/certs/MANEJO_DEFENSIVO.ts b/src/certs/MANEJO_DEFENSIVO.ts index 9b99a31..dc34df7 100644 --- a/src/certs/MANEJO_DEFENSIVO.ts +++ b/src/certs/MANEJO_DEFENSIVO.ts @@ -148,6 +148,7 @@ const photoSection = new Paragraph({ export async function manejoDefensivoCert(props: CertData): Promise { const imgQR = await getQR({ + iid: props.certIId, dni: props.personDni, height: 2.45, width: 2.45, diff --git a/src/certs/MECANICA_BASICA.ts b/src/certs/MECANICA_BASICA.ts index 6a55a50..e6851d6 100644 --- a/src/certs/MECANICA_BASICA.ts +++ b/src/certs/MECANICA_BASICA.ts @@ -132,6 +132,7 @@ const photoSection = new Paragraph({ export async function mecanicaBasicaCert(props: CertData): Promise { const imgQR = await getQR({ + iid: props.certIId, dni: props.personDni, height: 2.2, width: 2.2, diff --git a/src/certs/SEG_OP_MAQ_PES.ts b/src/certs/SEG_OP_MAQ_PES.ts new file mode 100644 index 0000000..0cac9f4 --- /dev/null +++ b/src/certs/SEG_OP_MAQ_PES.ts @@ -0,0 +1,253 @@ +import { + Document, Packer, Paragraph, PageOrientation, + FrameAnchorType, + TextRun, + AlignmentType, + BorderStyle, +} from "docx"; +import { cmText, createSimpleText, getImage, getQR } from "./utils"; +import { CertData } from "./CertData"; + +const imgFondoDoc = getImage({ + name: "fondo_seg_op_maq_pes.png", + height: 21.02, + width: 29.63, + horizontalOffset: 0.03, + verticalOffset: 0.04, + behindDocument: true, +}); + +const imgCIP = getImage({ + name: "colegio_ingenieros_logo.png", + height: 2.02, + width: 2.02, + horizontalOffset: 25.53, + verticalOffset: 14.17, +}); + +const imgMundomaq = getImage({ + name: "mundo_maq.png", + height: 4.82, + width: 6.73, + horizontalOffset: 20.87, + verticalOffset: 0.9, +}); + +const imgHazescorp = getImage({ + name: "logo_hazescorp.png", + height: 2.95, + width: 3.33, + horizontalOffset: 0.4, + verticalOffset: 17.73, +}); + +const imgEEG = getImage({ + name: "eeg_logo.png", + height: 2.38, + width: 4.94, + horizontalOffset: 1.66, + verticalOffset: 1.6, +}); + + + +// SEGURIDAD EN LA OPERACIÓN DE MAQUINARIA PESADA +const tCourse = createSimpleText({ + xPosition: 5.5, + yPosition: 6.65, + width: 12.83, + height: 1.5, + text: "SEGURIDAD EN LA OPERACIÓN DE MAQUINARIA PESADA", + size: 44, + bold: true, +}); + +// En temas de... +const tTopics = createSimpleText({ + xPosition: 1.65, + yPosition: 8.9, + width: 20.42, + height: 1.5, + text: "En los temas de: Seguridad en maquinaria pesada, IPERC, Prevención de riesgos con Maquinas, Medidas de prevención con máquinas en movimientos de tierra, Seguridad en la operación, Procedimiento de mantenimiento, Correcto llenado de IPERC, Correcto llenado de Check List, Mantenimiento Preventivo, predictivo, correctivo.", + size: 22, + font: "Arial", + alignment: AlignmentType.JUSTIFIED, +}); + +// Con una duracion... +const tHours = createSimpleText({ + xPosition: 1.65, + yPosition: 10.7, + width: 13.15, + height: 0.5, + text: "Con una duración de 36 horas lectivas", + size: 22, + font: "Arial", + alignment: AlignmentType.LEFT, +}); + +// Se expide certificado... +const tFinishLabel = createSimpleText({ + xPosition: 5.3, + yPosition: 11.5, + width: 13.15, + height: 0.5, + text: "Se expide el presente certificado para los fines que se estime conveniente", + size: 22, + font: "Arial", + italics: true, + alignment: AlignmentType.CENTER, +}); + + +// Recuadro de foto +const photoSection = new Paragraph({ + frame: { + position: { + x: cmText(22.62), + y: cmText(7), + }, + height: cmText(3.57), + width: cmText(2.81), + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + }, + children: [], + border: { + top: { + style: BorderStyle.DASHED, + }, + bottom: { + style: BorderStyle.DASHED, + }, + left: { + style: BorderStyle.DASHED, + }, + right: { + style: BorderStyle.DASHED, + }, + }, + alignment: AlignmentType.LEFT, +}); + + +export async function segOpMaqPesCert(props: CertData): Promise { + const imgQR = await getQR({ + iid: props.certIId, + dni: props.personDni, + height: 2.01, + width: 2.01, + horizontalOffset: 25.53, + verticalOffset: 16.43, + }); + + // FERNANDO ARAOZ + const tName = createSimpleText({ + xPosition: 0, + yPosition: 4.15, + width: 23.56, + height: 1.5, + text: props.personFullName, + size: 52, + bold: true, + underline: {}, + }); + + // Identificado con DNI... + const tContentPart1 = new Paragraph({ + frame: { + position: { + x: cmText(1.4), + y: cmText(5.9), + }, + width: cmText(20.92), + height: cmText(0.6), + 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: 24, + bold: true, + }), + new TextRun({ + text: ", al haber aprobado el curso de capacitación sobre:", + font: "Arial", + size: 22, + }), + ], + alignment: AlignmentType.CENTER, + }); + + // Fecha de Emision: ... + const certificateDate = createSimpleText({ + xPosition: 19.25, + yPosition: 16.2, + width: 5.87, + height: 0.5, + text: `Fecha de Emisión:\t${props.certDay} / ${props.certMonth} / ${props.certYear}`, + font: "Times New Roman", + size: 20, + alignment: AlignmentType.LEFT, + }); + + // N° XXXX-20XX-EEG + const tCertCode = createSimpleText({ + xPosition: 22.15, + yPosition: 10.85, + width: 3.67, + height: 0.8, + text: `N° ${props.certCode}-${props.certYear}-EEG`, + size: 20, + alignment: AlignmentType.CENTER, + }); + + const doc = new Document({ + sections: [ + { + properties: { + page: { + size: { + orientation: PageOrientation.LANDSCAPE, + }, + }, + }, + children: [ + tName, + tContentPart1, + tCourse, + tTopics, + tHours, + tFinishLabel, + certificateDate, + photoSection, + tCertCode, + new Paragraph({ + children: [ + imgFondoDoc, + imgQR, + imgCIP, + imgMundomaq, + imgHazescorp, + imgEEG, + ], + }), + ], + }, + ], + }); + + // Return document as a buffer + return await Packer.toBuffer(doc); +} diff --git a/src/certs/utils.ts b/src/certs/utils.ts index 3d1b1c3..babe6f3 100644 --- a/src/certs/utils.ts +++ b/src/certs/utils.ts @@ -52,6 +52,8 @@ export function getImage(data: ImgConfig): ImageRun { } export async function getQR(data : { + /** ID of the entity in the DB */ + iid: number, dni: string, height: number, width: number, @@ -59,7 +61,7 @@ export async function getQR(data : { verticalOffset: number, behindDocument?: boolean, }): Promise { - const qr = await QR.toDataURL(`https://www.eegsac.com/alumnoscertificados.php?DNI=${data.dni}`, {margin: 1}); + const qr = await QR.toDataURL(`https://www.eegsac.com/alumnoscertificados.php?DNI=${data.dni}&iid=${data.iid}`, {margin: 1}); return new ImageRun({ data: qr, diff --git a/src/controller/certificate/certificate.service.ts b/src/controller/certificate/certificate.service.ts index f85721c..0ffd8f2 100644 --- a/src/controller/certificate/certificate.service.ts +++ b/src/controller/certificate/certificate.service.ts @@ -10,8 +10,9 @@ import { getMatpel } from "./generator/matpel"; import { getMecanicaBasica } from "./generator/mecanicaBasica"; import { getManejoDefensivo } from "./generator/manejoDefensivo"; import { get4x4 } from "./generator/4x4"; +import { getSegOpMaqPesada } from "./generator/segOpMaqPes"; -const generatable = [1, 2, 3, 10, 11, 12]; +const generatable = [1, 2, 3, 10, 11, 12, 72]; @Injectable() export class CertificateService { @@ -136,6 +137,16 @@ export class CertificateService { certDay, ); } + // Seguridad en la Operacion de Maquinaria Pesada + case 72: { + return await getSegOpMaqPesada( + person, + register, + certYear, + certMonth, + certDay, + ); + } default: { throw new BadRequestException("Curso no soportado"); } diff --git a/src/controller/certificate/generator/4x4.ts b/src/controller/certificate/generator/4x4.ts index 0b72b8f..2965571 100644 --- a/src/controller/certificate/generator/4x4.ts +++ b/src/controller/certificate/generator/4x4.ts @@ -20,6 +20,7 @@ export async function get4x4( certYear, certMonth, certDay, + certIId: register.id, }; return [await _4X4Cert(data), `4X4 - ${personFullName.toUpperCase()}.docx`]; diff --git a/src/controller/certificate/generator/manejoDefensivo.ts b/src/controller/certificate/generator/manejoDefensivo.ts index 819ffb0..8254acb 100644 --- a/src/controller/certificate/generator/manejoDefensivo.ts +++ b/src/controller/certificate/generator/manejoDefensivo.ts @@ -20,6 +20,7 @@ export async function getManejoDefensivo( certYear, certMonth, certDay, + certIId: register.id, }; return [await manejoDefensivoCert(data), `MANEJO DEFENSIVO - ${personFullName.toUpperCase()}.docx`]; diff --git a/src/controller/certificate/generator/matpel.ts b/src/controller/certificate/generator/matpel.ts index 0307bdf..beee933 100644 --- a/src/controller/certificate/generator/matpel.ts +++ b/src/controller/certificate/generator/matpel.ts @@ -22,6 +22,7 @@ export async function getMatpel( certYear, certMonth, certDay, + certIId: register.id, }; 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 index eeb0ea3..d977750 100644 --- a/src/controller/certificate/generator/mecanicaBasica.ts +++ b/src/controller/certificate/generator/mecanicaBasica.ts @@ -20,6 +20,7 @@ export async function getMecanicaBasica( certYear, certMonth, certDay, + certIId: register.id, }; return [await mecanicaBasicaCert(data), `MECANICA BASICA - ${personFullName.toUpperCase()}.docx`]; diff --git a/src/controller/certificate/generator/segOpMaqPes.ts b/src/controller/certificate/generator/segOpMaqPes.ts new file mode 100644 index 0000000..d7f25bd --- /dev/null +++ b/src/controller/certificate/generator/segOpMaqPes.ts @@ -0,0 +1,27 @@ +import { CertData } from "src/certs/CertData"; +import { segOpMaqPesCert } from "src/certs/SEG_OP_MAQ_PES"; +import { Persona } from "src/model/Persona/persona.entity"; +import { RegistroGIE } from "src/model/RegistroGIE/registroGIE.entity"; + +export async function getSegOpMaqPesada( + 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, + certIId: register.id, + }; + + return [await segOpMaqPesCert(data), `SEGURIDAD OPERACION MAQUINARIA PESADA - ${personFullName.toUpperCase()}.docx`]; +} diff --git a/src/views/components/Registers.tsx b/src/views/components/Registers.tsx index 04908f8..2600d06 100644 --- a/src/views/components/Registers.tsx +++ b/src/views/components/Registers.tsx @@ -87,6 +87,7 @@ export function Registers(props: { person: Person | null, lastUpdate: number }) function Register(props: { cert: RegisterReturn, onUpdate: () => void }) { const [deleteConfirmation, setDeleteConfirmation] = createSignal(false); const [deleteText, setDeleteText] = createSignal("Eliminar"); + const [downloading, setDownloading] = createSignal(false); const deleteRegister = async() => { if (deleteConfirmation()) { @@ -113,6 +114,7 @@ function Register(props: { cert: RegisterReturn, onUpdate: () => void }) { }; const getCertificate = async() => { + setDownloading(true); const response = await fetch(`/certificate/generate/${props.cert.id}`, { method: "POST", }); @@ -141,13 +143,12 @@ function Register(props: { cert: RegisterReturn, onUpdate: () => void }) { document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); - - return; - } else { const json = await response.json(); alert(json); } + + setDownloading(false); }; return ( diff --git a/src/views/components/Search/RegisterPerson.tsx b/src/views/components/Search/RegisterPerson.tsx index 4ecdb3f..c345055 100644 --- a/src/views/components/Search/RegisterPerson.tsx +++ b/src/views/components/Search/RegisterPerson.tsx @@ -91,43 +91,43 @@ export function RegisterPerson(props: {dni: string, onSuccess: () => void}) {

-
-
- -
- setApellidoP(e.target.value.toUpperCase())} - disabled={loading()} - /> -
-
- -
- setApellidoM(e.target.value.toUpperCase())} - disabled={loading()} - /> -
-
+ +
+ setApellidoP(e.target.value.toUpperCase())} + disabled={loading()} + /> + +
+
+ + +
+ setApellidoM(e.target.value.toUpperCase())} + disabled={loading()} + /> +
+