Automate MATPEL I certificate

master
Fernando 2023-05-12 10:54:19 -05:00
parent 6ff5fddee6
commit 2938dd4dce
7 changed files with 359 additions and 103 deletions

View File

@ -7,8 +7,10 @@ import {
HorizontalPositionAlign, HorizontalPositionAlign,
VerticalPositionAlign, VerticalPositionAlign,
TextRun, TextRun,
AlignmentType,
} from "docx"; } from "docx";
import { join } from "path"; import { join } from "path";
import * as QR from "qrcode";
function cm(centimeters: number) { function cm(centimeters: number) {
@ -19,6 +21,10 @@ function cmToEmu(cm: number) {
return Math.round(cm * 360000); return Math.round(cm * 360000);
} }
function cmText(cm: number) {
return Math.round(cm * 567);
}
type ImgConfig = { type ImgConfig = {
name: string, name: string,
height: number, height: number,
@ -64,6 +70,7 @@ const imgFondoDoc = new ImageRun({
relative: VerticalPositionRelativeFrom.TOP_MARGIN, relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: 0, offset: 0,
}, },
behindDocument: true,
}, },
}); });
@ -261,11 +268,11 @@ const imgEATE = new ImageRun({
const certificateName = new Paragraph({ const certificateName = new Paragraph({
frame: { frame: {
position: { position: {
x: cmToEmu(3.13), x: cmText(3.13),
y: cmToEmu(1.39), y: cmText(1.9),
}, },
width: cm(14.28) * 5, width: cmText(14.28),
height: cm(3.19) * 5, height: cmText(3.19),
anchor: { anchor: {
horizontal: FrameAnchorType.MARGIN, horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN, vertical: FrameAnchorType.MARGIN,
@ -277,15 +284,238 @@ const certificateName = new Paragraph({
}, },
children: [ children: [
new TextRun({ new TextRun({
text: "sample text", text: "CAPACITACIÓN EN MANEJO; MANIPULACIÓN Y TRANSPORTE DE MATERIALES Y RESIDUOS PELIGROSOS - MATPEL I - Advertencia".toUpperCase(),
bold: true, bold: true,
font: "Arial", font: "Arial",
size: 32,
}), }),
], ],
alignment: AlignmentType.CENTER,
});
const certificatePersonName = new Paragraph({
frame: {
position: {
x: cmText(0),
y: cmText(6.5),
},
width: cmText(23),
height: cmText(1.5),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.TOP,
},
},
children: [
new TextRun({
text: "fernando enrique araoz morales".toUpperCase(),
bold: true,
font: "Arial",
allCaps: true,
size: 48,
}),
],
alignment: AlignmentType.CENTER,
});
// "Se expide el presente certificado a:"
const certificateExpedite = new Paragraph({
frame: {
position: {
x: cmText(2.85),
y: cmText(5.15),
},
width: cmText(14.28),
height: cmText(1),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.TOP,
},
},
children: [
new TextRun({
text: "Se expide el presente certificado a:",
font: "Arial",
size: 22,
}),
],
alignment: AlignmentType.CENTER,
});
// N° XXXX-202X-EEG
const certificateCode = new Paragraph({
frame: {
position: {
x: cmText(17.1),
y: cmText(5.35),
},
width: cmText(3.68),
height: cmText(0.7),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.TOP,
},
},
children: [
new TextRun({
text: "N° 0524-2023-EEG",
font: "Arial",
size: 20,
}),
],
alignment: AlignmentType.CENTER,
});
// Identificado con dni ...
const certificateBody = new Paragraph({
frame: {
position: {
x: cmText(1.51),
y: cmText(8.35),
},
width: cmText(20.08),
height: cmText(3.7),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.TOP,
},
},
children: [
new TextRun({
text: "Identificado con DNI N° ",
font: "Arial",
size: 24,
}),
new TextRun({
text: "74059695",
font: "Arial",
size: 24,
bold: true,
}),
new TextRun({
text: ", al haber aprobado el curso de capacitación sobre ",
font: "Arial",
size: 24,
}),
new TextRun({
text: "MANEJO DE MATERIALES Y RESIDUOS PELIGROSOS - MATPEL I - Advertencia",
font: "Arial",
size: 24,
bold: true,
}),
new TextRun({
text: ", según lo estipulado en la Ley N°28256 (ley que regula el Transporte de Materiales y Residuos Peligrosos) y Decreto Supremo N° 021-2008-MTC (Reglamento Nacional de Transporte Terrestre de Materiales y Residuos Peligrosos) con una duración de 12 horas lectivas.\n",
font: "Arial",
size: 24,
}),
],
alignment: AlignmentType.JUSTIFIED,
});
// Se expide certificado...
const certificateFinishLabel = new Paragraph({
frame: {
position: {
x: cmText(1.52),
y: cmText(11.25),
},
width: cmText(20.1),
height: cmText(0.8),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.TOP,
},
},
children: [
new TextRun({
text: "Se expide certificado para los fines que se estime conveniente.",
font: "Arial",
size: 24,
}),
],
alignment: AlignmentType.CENTER,
});
// Fecha de Emision: ...
const certificateDate = new Paragraph({
frame: {
position: {
x: cmText(16),
y: cmText(16.5),
},
width: cmText(9),
height: cmText(1.4),
anchor: {
horizontal: FrameAnchorType.MARGIN,
vertical: FrameAnchorType.MARGIN,
},
alignment: {
x: HorizontalPositionAlign.CENTER,
y: VerticalPositionAlign.TOP,
},
},
children: [
new TextRun({
text: "Fecha de Emisión:\t\t{fecha_emision}",
font: "Arial",
size: 20,
break: 1,
}),
new TextRun({
text: "Fecha de Expiración:\t\t{fecha_expiracion}",
font: "Arial",
size: 20,
break: 1,
}),
],
alignment: AlignmentType.LEFT,
}); });
const doc = new Document({
async function create() {
const qr = await QR.toDataURL(/* join(__dirname, "img", "qr.png"), */ `https://www.eegsac.com/alumnoscertificados.php?DNI=${"74059695"}`, {margin: 1});
const imgQR = new ImageRun({
data: qr, // fs.readFileSync(join(__dirname, "img", "qr.png")),
transformation: {
height: cm(2.47),
width: cm(2.47),
},
floating: {
zIndex: 1,
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.LEFT_MARGIN,
offset: cmToEmu(26.3),
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
offset: cmToEmu(16.48),
},
},
});
const doc = new Document({
sections: [ sections: [
{ {
properties: { properties: {
@ -297,6 +527,12 @@ const doc = new Document({
}, },
children: [ children: [
certificateName, certificateName,
certificatePersonName,
certificateExpedite,
certificateCode,
certificateBody,
certificateFinishLabel,
certificateDate,
new Paragraph({ new Paragraph({
children: [ children: [
imgFondoDoc, imgFondoDoc,
@ -310,14 +546,19 @@ const doc = new Document({
imgMichigan, imgMichigan,
imgCEEM, imgCEEM,
imgEATE, imgEATE,
imgQR,
], ],
}), }),
], ],
}, },
], ],
}); });
// Used to export the file into a .docx file // Used to export the file into a .docx file
Packer.toBuffer(doc).then((buffer) => { Packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer); fs.writeFileSync("My Document.docx", buffer);
}); // fs.rmSync(join(__dirname, "temp", "qr.png"));
});
}
create();

View File

@ -49,6 +49,7 @@
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/jest": "29.5.0", "@types/jest": "29.5.0",
"@types/node": "18.15.11", "@types/node": "18.15.11",
"@types/qrcode": "^1.5.0",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.0.0",

View File

@ -72,6 +72,9 @@ devDependencies:
'@types/node': '@types/node':
specifier: 18.15.11 specifier: 18.15.11
version: 18.15.11 version: 18.15.11
'@types/qrcode':
specifier: ^1.5.0
version: 1.5.0
'@types/supertest': '@types/supertest':
specifier: ^2.0.11 specifier: ^2.0.11
version: 2.0.12 version: 2.0.12
@ -1735,6 +1738,12 @@ packages:
resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==} resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==}
dev: true dev: true
/@types/qrcode@1.5.0:
resolution: {integrity: sha512-x5ilHXRxUPIMfjtM+1vf/GPTRWZ81nqscursm5gMznJeK9M0YnZ1c3bEvRLQ0zSSgedLx1J6MGL231ObQGGhaA==}
dependencies:
'@types/node': 18.15.11
dev: true
/@types/qs@6.9.7: /@types/qs@6.9.7:
resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
dev: true dev: true

View File

@ -12,7 +12,7 @@ import { SubjectService } from "./controller/subject/subject.service";
import * as dotenv from "dotenv"; import * as dotenv from "dotenv";
// Must be done before initializing DB. // Must be done before initializing DB.
dotenv.config(); console.log(dotenv.config());
@Module({ @Module({
imports: [ imports: [

View File

@ -8,7 +8,7 @@ export function template(ssr: string): string {
<title>Registrar certificados - EEGSAC</title> <title>Registrar certificados - EEGSAC</title>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/static/styles.css" /> <link rel="stylesheet" href="/static/styles.css?t=${Date.now()}" />
${generateHydrationScript()} ${generateHydrationScript()}
</head> </head>
<body> <body>
@ -16,7 +16,7 @@ export function template(ssr: string): string {
${ssr} ${ssr}
</div> </div>
</body> </body>
<script src="/static/hydration.js"></script> <script src="/static/hydration.js?t=${Date.now()}"></script>
</html> </html>
`; `;

View File

@ -1,9 +1,8 @@
import { createEffect, createSignal, onMount, Show } from "solid-js"; import { createSignal, onMount, Show } from "solid-js";
import type { CursoGIE } from "../../model/CursoGIE/cursoGIE.entity"; import type { CursoGIE } from "../../model/CursoGIE/cursoGIE.entity";
import { SearchableSelect } from "./NewRegister/SearchableSelect"; import { SearchableSelect } from "./NewRegister/SearchableSelect";
import { JSX } from "solid-js/jsx-runtime"; import { JSX } from "solid-js/jsx-runtime";
import { Person } from "src/types/Person"; import { Person } from "src/types/Person";
import QR from "qrcode";
type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & { type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
@ -19,21 +18,9 @@ export function NewRegister(props: {person: Person | null, onSuccess: () => void
const [selectedSubject, setSelectedSubject] = createSignal<number | null>(null); const [selectedSubject, setSelectedSubject] = createSignal<number | null>(null);
const [qrBase64, setQrBase64] = createSignal<string | null>(null);
// Update QR
createEffect(() => {
if (props.person !== null) {
QR.toDataURL(`https://www.eegsac.com/alumnosceertificados.php?DNI=${props.person.dni}`, (err, res) => {
if (err) {
console.error("Error creating QR code");
return;
}
setQrBase64(res);
});
}
});
const datePicker = ( const datePicker = (
<input <input
@ -147,9 +134,6 @@ export function NewRegister(props: {person: Person | null, onSuccess: () => void
disabled={loading()} disabled={loading()}
/> />
</div> </div>
<div>
<img src={qrBase64() ?? ""} height="225" width="225" />
</div>
</form> </form>
</Show> </Show>
</div> </div>

View File

@ -1,7 +1,8 @@
import { createSignal, Show } from "solid-js"; import { createEffect, createSignal, Show } from "solid-js";
import { JSX } from "solid-js/jsx-runtime"; import { JSX } from "solid-js/jsx-runtime";
import { Person } from "../../types/Person"; import { Person } from "../../types/Person";
import { RegisterPerson } from "./Search/RegisterPerson"; import { RegisterPerson } from "./Search/RegisterPerson";
import QR from "qrcode";
type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & { type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
submitter: HTMLElement; submitter: HTMLElement;
@ -16,6 +17,21 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
const [loading, setLoading] = createSignal(false); const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(""); const [error, setError] = createSignal("");
const [warning, setWarning] = createSignal(""); const [warning, setWarning] = createSignal("");
const [qrBase64, setQrBase64] = createSignal<string | null>(null);
// Update QR
createEffect(() => {
if (dni() !== "") {
QR.toDataURL(`https://www.eegsac.com/alumnoscertificados.php?DNI=${dni()}`, {margin: 1}, (err, res) => {
if (err) {
console.error("Error creating QR code");
return;
}
setQrBase64(res);
});
}
});
/* /*
Get the user data from the DB Get the user data from the DB
@ -51,7 +67,8 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
}; };
return ( return (
<div class="p-4"> <div class="p-4 grid" style={{"grid-template-columns": "40rem auto"}}>
<div>
<h2 class="my-2 font-bold text-xl">1. Buscar persona</h2> <h2 class="my-2 font-bold text-xl">1. Buscar persona</h2>
<form onSubmit={searchDNI} class="px-4"> <form onSubmit={searchDNI} class="px-4">
<label for="search-dni">DNI</label> <label for="search-dni">DNI</label>
@ -105,5 +122,9 @@ export function Search(props: {setPerson: (p: Person | null) => void}) {
<RegisterPerson dni={dni()} onSuccess={search} /> <RegisterPerson dni={dni()} onSuccess={search} />
</Show> </Show>
</div> </div>
<div>
<img src={qrBase64() ?? ""} height="225" width="225" />
</div>
</div>
); );
} }