Documentos + Homepage rediseñada
Build & Push Docker Images / test-backend (push) Successful in 24s
Build & Push Docker Images / test-frontend (push) Successful in 23s
Build & Push Docker Images / build-backend (push) Successful in 2m22s
Build & Push Docker Images / build-frontend (push) Successful in 57s
Build & Push Docker Images / test-backend (push) Successful in 24s
Build & Push Docker Images / test-frontend (push) Successful in 23s
Build & Push Docker Images / build-backend (push) Successful in 2m22s
Build & Push Docker Images / build-frontend (push) Successful in 57s
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const OUT_DIR = process.cwd();
|
||||
|
||||
const packageDocs = [
|
||||
{
|
||||
outPath: path.resolve(OUT_DIR, 'SOLICITUD_Y_DIBUJOS_OEPM_FARMAFINDER.pdf'),
|
||||
pages: [
|
||||
{ kind: 'cover', title: 'ANEXO DE DIBUJOS E INSTANCIA', subtitle: 'Expediente de Patente para FarmaFinder' },
|
||||
{ kind: 'instance' },
|
||||
{ kind: 'diagram1' },
|
||||
{ kind: 'diagram2' },
|
||||
{ kind: 'diagram3' },
|
||||
{ kind: 'diagram4' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
function esc(s) {
|
||||
return String(s).replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
|
||||
}
|
||||
|
||||
function makePdf({ outPath, pages }) {
|
||||
const PAGE_W = 595.28;
|
||||
const PAGE_H = 841.89;
|
||||
const MARGIN_L = 48;
|
||||
const MARGIN_R = 48;
|
||||
const CONTENT_W = PAGE_W - MARGIN_L - MARGIN_R;
|
||||
|
||||
const objects = [''];
|
||||
const contentNums = [];
|
||||
const pageNums = [];
|
||||
const add = (x) => (objects.push(x), objects.length - 1);
|
||||
const f1 = add('<< /Type /Font /Subtype /Type1 /BaseFont /Times-Roman /Encoding /WinAnsiEncoding >>');
|
||||
const f2 = add('<< /Type /Font /Subtype /Type1 /BaseFont /Times-Bold /Encoding /WinAnsiEncoding >>');
|
||||
const f3 = add('<< /Type /Font /Subtype /Type1 /BaseFont /Times-Italic /Encoding /WinAnsiEncoding >>');
|
||||
|
||||
const wrap = (text, max) => {
|
||||
const words = String(text).split(/\s+/);
|
||||
const lines = [];
|
||||
let line = '';
|
||||
for (const word of words) {
|
||||
const cand = line ? `${line} ${word}` : word;
|
||||
if (cand.length > max && line) {
|
||||
lines.push(line);
|
||||
line = word;
|
||||
} else {
|
||||
line = cand;
|
||||
}
|
||||
}
|
||||
if (line) lines.push(line);
|
||||
return lines;
|
||||
};
|
||||
|
||||
function pageOps(page) {
|
||||
const ops = [];
|
||||
const drawText = (txt, x, y, size = 11, font = 'F1') => {
|
||||
ops.push(`BT /${font} ${size} Tf ${x} ${y} Td (${esc(txt)}) Tj ET`);
|
||||
};
|
||||
const line = (x1, y1, x2, y2, w = 1) => {
|
||||
ops.push(`${w} w ${x1} ${y1} m ${x2} ${y2} l S`);
|
||||
};
|
||||
const rect = (x, y, w, h, lw = 1) => {
|
||||
ops.push(`${lw} w ${x} ${y} ${w} ${h} re S`);
|
||||
};
|
||||
const arrow = (x1, y1, x2, y2) => {
|
||||
line(x1, y1, x2, y2, 1);
|
||||
const dx = x2 - x1;
|
||||
const dy = y2 - y1;
|
||||
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||
const ux = dx / len;
|
||||
const uy = dy / len;
|
||||
const ax = x2 - ux * 10;
|
||||
const ay = y2 - uy * 10;
|
||||
const px = -uy;
|
||||
const py = ux;
|
||||
line(x2, y2, ax + px * 4, ay + py * 4, 1);
|
||||
line(x2, y2, ax - px * 4, ay - py * 4, 1);
|
||||
};
|
||||
const box = (x, y, w, h, title, body) => {
|
||||
rect(x, y, w, h, 1);
|
||||
drawText(title, x + 8, y + h - 18, 11, 'F2');
|
||||
const lines = wrap(body, Math.max(18, Math.floor(w / 5.5)));
|
||||
let ty = y + h - 34;
|
||||
for (const l of lines) {
|
||||
drawText(l, x + 8, ty, 9.5, 'F1');
|
||||
ty -= 12;
|
||||
}
|
||||
};
|
||||
|
||||
if (page.kind === 'cover') {
|
||||
drawText(page.title, 160, 770, 18, 'F2');
|
||||
drawText(page.subtitle, 170, 748, 12, 'F3');
|
||||
rect(70, 560, 455, 120, 1);
|
||||
drawText('Solicitante: Antoni Nuñez Romeu', 90, 640, 11, 'F1');
|
||||
drawText('DNI/NIF: 45858029P', 90, 622, 11, 'F1');
|
||||
drawText('Domicilio: C/ Font Vella 12, 1, Terrassa, Barcelona, 08221', 90, 604, 11, 'F1');
|
||||
drawText('Documentos incluidos: instancia, memoria, reivindicaciones, resumen, dibujos, tasa reducida', 90, 586, 10, 'F1');
|
||||
drawText('Expediente preparado para presentacion ante OEPM', 145, 500, 10, 'F3');
|
||||
drawText('Pagina 1', 470, 24, 9, 'F1');
|
||||
return ops.join('\n');
|
||||
}
|
||||
|
||||
if (page.kind === 'instance') {
|
||||
drawText('INSTANCIA DE SOLICITUD', 175, 780, 16, 'F2');
|
||||
drawText('Oficina Espanola de Patentes y Marcas (OEPM)', 140, 760, 11, 'F1');
|
||||
line(48, 748, 547, 748, 0.8);
|
||||
drawText('Datos del solicitante', 48, 725, 12, 'F2');
|
||||
drawText('Nombre: Antoni Nuñez Romeu', 56, 705, 11, 'F1');
|
||||
drawText('DNI/NIF: 45858029P', 56, 688, 11, 'F1');
|
||||
drawText('Domicilio: C/ Font Vella 12, 1, Terrassa, Barcelona, 08221', 56, 671, 11, 'F1');
|
||||
drawText('Tipo de presentacion: Patente nacional', 56, 654, 11, 'F1');
|
||||
drawText('Titulo: Sistema y procedimiento implementados por ordenador para la identificacion de necesidades terapeuticas mediante escaneo de tarjeta sanitaria individual, contraste con stock farmacutico geolocalizado y generacion de acciones de suministro.', 56, 628, 10.3, 'F1');
|
||||
drawText('Documentacion anexa', 48, 590, 12, 'F2');
|
||||
drawText('1. Memoria descriptiva', 60, 570, 10.8, 'F1');
|
||||
drawText('2. Reivindicaciones', 60, 554, 10.8, 'F1');
|
||||
drawText('3. Resumen', 60, 538, 10.8, 'F1');
|
||||
drawText('4. Dibujos / laminas tecnicas', 60, 522, 10.8, 'F1');
|
||||
drawText('5. Solicitud de reduccion de tasas (persona fisica)', 60, 506, 10.8, 'F1');
|
||||
drawText('Declaracion: el solicitante afirma ser el inventor y unico titular de la solicitud presentada.', 48, 470, 10.5, 'F1');
|
||||
drawText('Firma: Antoni Nuñez Romeu', 48, 442, 11, 'F1');
|
||||
drawText('Fecha: 22 de junio de 2026', 48, 424, 11, 'F1');
|
||||
drawText('Pagina 2', 470, 24, 9, 'F1');
|
||||
return ops.join('\n');
|
||||
}
|
||||
|
||||
if (page.kind === 'diagram1') {
|
||||
drawText('Figura 1. Arquitectura general del sistema', 160, 790, 14, 'F2');
|
||||
box(50, 620, 120, 70, '1 Usuario', 'Inicia busqueda y notificaciones');
|
||||
box(220, 620, 120, 70, '2 Escaneo TSI', 'Captura de tarjeta y validacion');
|
||||
box(390, 620, 120, 70, '3 Perfil', 'Autenticacion y ubicacion');
|
||||
box(105, 470, 120, 70, '4 Stock', 'Farmacias y medicamentos');
|
||||
box(275, 470, 120, 70, '5 Motor', 'Contraste, ordenacion y reglas');
|
||||
box(445, 470, 90, 70, '6 Accion', 'Reserva / aviso / pedido');
|
||||
arrow(170, 655, 220, 655);
|
||||
arrow(340, 655, 390, 655);
|
||||
arrow(280, 620, 165, 540);
|
||||
arrow(340, 620, 335, 540);
|
||||
arrow(445, 655, 490, 540);
|
||||
arrow(225, 470, 275, 505);
|
||||
arrow(395, 470, 445, 505);
|
||||
drawText('Flujo: usuario -> captura -> verificacion -> consulta de stock -> accion', 110, 350, 10.5, 'F1');
|
||||
drawText('Pagina 1 de dibujos', 450, 24, 9, 'F1');
|
||||
return ops.join('\n');
|
||||
}
|
||||
|
||||
if (page.kind === 'diagram2') {
|
||||
drawText('Figura 2. Captura y validacion de la TSI', 175, 790, 14, 'F2');
|
||||
box(70, 600, 140, 70, '1 Camara / lector', 'Entrada de imagen o codigo');
|
||||
box(250, 600, 140, 70, '2 Extraccion', 'OCR / parseo / identificacion');
|
||||
box(430, 600, 100, 70, '3 Validacion', 'Usuario autenticado');
|
||||
box(160, 430, 160, 70, '4 Vinculo seguro', 'Asociacion a perfil y consentimiento');
|
||||
box(360, 430, 160, 70, '5 Resultado', 'Necesidad terapeutica o referencia');
|
||||
arrow(210, 635, 250, 635);
|
||||
arrow(390, 635, 430, 635);
|
||||
arrow(480, 600, 240, 500);
|
||||
arrow(320, 600, 440, 500);
|
||||
drawText('Pagina 2 de dibujos', 450, 24, 9, 'F1');
|
||||
return ops.join('\n');
|
||||
}
|
||||
|
||||
if (page.kind === 'diagram3') {
|
||||
drawText('Figura 3. Contraste con stock farmaceutico', 160, 790, 14, 'F2');
|
||||
box(50, 620, 130, 70, '1 Medicamento', 'Referencia obtenida del sistema');
|
||||
box(220, 620, 130, 70, '2 Geolocalizacion', 'Distancia y prioridad');
|
||||
box(390, 620, 130, 70, '3 Inventario', 'Stock, horario y precio');
|
||||
box(135, 460, 140, 70, '4 Motor de ranking', 'Ordena por stock y proximidad');
|
||||
box(330, 460, 140, 70, '5 Resultado', 'Lista de farmacias priorizada');
|
||||
arrow(180, 655, 220, 655);
|
||||
arrow(350, 655, 390, 655);
|
||||
arrow(285, 620, 205, 530);
|
||||
arrow(455, 620, 400, 530);
|
||||
drawText('Pagina 3 de dibujos', 450, 24, 9, 'F1');
|
||||
return ops.join('\n');
|
||||
}
|
||||
|
||||
if (page.kind === 'diagram4') {
|
||||
drawText('Figura 4. Flujo cuando no hay stock', 185, 790, 14, 'F2');
|
||||
box(60, 620, 120, 70, '1 Sin stock', 'No existe disponibilidad inmediata');
|
||||
box(230, 620, 120, 70, '2 Alerta', 'Aviso a farmacia o usuario');
|
||||
box(400, 620, 120, 70, '3 Reserva', 'Lista de espera o reserva');
|
||||
box(145, 450, 120, 70, '4 Pedido', 'Solicitud a proveedor o distribucion');
|
||||
box(325, 450, 120, 70, '5 Alternativa', 'Otra farmacia con stock');
|
||||
arrow(180, 655, 230, 655);
|
||||
arrow(350, 655, 400, 655);
|
||||
arrow(290, 620, 205, 520);
|
||||
arrow(460, 620, 385, 520);
|
||||
drawText('Pagina 4 de dibujos', 450, 24, 9, 'F1');
|
||||
return ops.join('\n');
|
||||
}
|
||||
|
||||
return ops.join('\n');
|
||||
}
|
||||
|
||||
const pageContents = pages.map((p) => pageOps(p));
|
||||
for (const content of pageContents) {
|
||||
contentNums.push(add(`<< /Length ${Buffer.byteLength(content, 'latin1')} >>\nstream\n${content}\nendstream`));
|
||||
}
|
||||
const pagesTree = add('');
|
||||
for (let i = 0; i < contentNums.length; i++) {
|
||||
pageNums.push(add(`<< /Type /Page /Parent ${pagesTree} 0 R /MediaBox [0 0 ${PAGE_W} ${PAGE_H}] /Resources << /Font << /F1 ${f1} 0 R /F2 ${f2} 0 R /F3 ${f3} 0 R >> >> /Contents ${contentNums[i]} 0 R >>`));
|
||||
}
|
||||
objects[pagesTree] = `<< /Type /Pages /Kids [${pageNums.map(n => `${n} 0 R`).join(' ')}] /Count ${pageNums.length} >>`;
|
||||
const catalog = add(`<< /Type /Catalog /Pages ${pagesTree} 0 R >>`);
|
||||
|
||||
let pdf = '%PDF-1.4\n';
|
||||
const offsets = [0];
|
||||
for (let i = 1; i < objects.length; i++) {
|
||||
offsets[i] = Buffer.byteLength(pdf, 'latin1');
|
||||
pdf += `${i} 0 obj\n${objects[i]}\nendobj\n`;
|
||||
}
|
||||
const xref = Buffer.byteLength(pdf, 'latin1');
|
||||
pdf += `xref\n0 ${objects.length}\n`;
|
||||
pdf += '0000000000 65535 f \n';
|
||||
for (let i = 1; i < objects.length; i++) pdf += `${String(offsets[i]).padStart(10, '0')} 00000 n \n`;
|
||||
pdf += `trailer << /Size ${objects.length} /Root ${catalog} 0 R >>\nstartxref\n${xref}\n%%EOF\n`;
|
||||
fs.writeFileSync(outPath, Buffer.from(pdf, 'latin1'));
|
||||
}
|
||||
|
||||
// Generate Drawings and Instance PDF
|
||||
for (const doc of packageDocs) {
|
||||
makePdf(doc);
|
||||
console.log(`Wrote ${doc.outPath}`);
|
||||
}
|
||||
|
||||
// Zip final package and source archive
|
||||
try {
|
||||
console.log('Packaging all generated PDFs into a ZIP archive...');
|
||||
// Force removal of existing zip first if any to avoid duplication issues
|
||||
if (fs.existsSync('oepm_patent_package_pdf.zip')) fs.unlinkSync('oepm_patent_package_pdf.zip');
|
||||
execSync('zip -j oepm_patent_package_pdf.zip SOLICITUD_PATENTE_OEPM_FARMAFINDER.pdf PRECONVERSION_OEPM_FARMAFINDER.pdf REDUCCION_TASAS_OEPM_PERSONA_FISICA.pdf SOLICITUD_Y_DIBUJOS_OEPM_FARMAFINDER.pdf', { stdio: 'inherit' });
|
||||
console.log('Created oepm_patent_package_pdf.zip successfully.');
|
||||
|
||||
console.log('Packaging pre-conversion source files into a ZIP archive...');
|
||||
if (fs.existsSync('oepm_preconversion_archive.zip')) fs.unlinkSync('oepm_preconversion_archive.zip');
|
||||
execSync('zip -r oepm_preconversion_archive.zip SOLICITUD_PATENTE_OEPM_FARMAFINDER.html SOLICITUD_PATENTE_OEPM_FARMAFINDER.md MEMORIA_TECNICA.md PLAN.md dibujos/', { stdio: 'inherit' });
|
||||
console.log('Created oepm_preconversion_archive.zip successfully.');
|
||||
} catch (err) {
|
||||
console.error('Error creating zip archives:', err.message);
|
||||
}
|
||||
Reference in New Issue
Block a user