Informes de facturas de cliente

This commit is contained in:
David Arranz 2025-11-20 13:03:54 +01:00
parent 156dc9db0f
commit 747d11a956
23 changed files with 167 additions and 161 deletions

View File

@ -52,7 +52,7 @@
// other vscode settings
"[handlebars]": {
"editor.defaultFormatter": "vscode.html-language-features"
"editor.defaultFormatter": "mfeckies.handlebars-formatter"
},
"[sql]": {
"editor.defaultFormatter": "cweijan.vscode-mysql-client2"

View File

@ -292,6 +292,14 @@
"enabled": true
}
},
"html": {
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
}
},
"overrides": [
{
"includes": ["**/*.test.{js,ts,tsx}", "**/*.spec.{js,ts,tsx}", "**/__tests__/**"],

View File

@ -1,4 +1,4 @@
import { IPresenter } from "./presenter.interface";
import type { IPresenter } from "./presenter.interface";
/**
* 🔑 Claves de proyección comunes para seleccionar presenters

View File

@ -1,10 +1,11 @@
import { Criteria, CriteriaFromUrlConverter } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd";
import { NextFunction, Request, Response } from "express";
import { type Criteria, CriteriaFromUrlConverter } from "@repo/rdx-criteria/server";
import type { UniqueID } from "@repo/rdx-ddd";
import type { NextFunction, Request, Response } from "express";
import httpStatus from "http-status";
import { ApiErrorContext, ApiErrorMapper, toProblemJson } from "./api-error-mapper";
import { type ApiErrorContext, ApiErrorMapper, toProblemJson } from "./api-error-mapper";
import {
ApiError,
type ApiError,
ConflictApiError,
ForbiddenApiError,
InternalApiError,
@ -13,7 +14,7 @@ import {
UnavailableApiError,
ValidationApiError,
} from "./errors";
import { GuardFn } from "./express-guards";
import type { GuardFn } from "./express-guards";
export abstract class ExpressController {
protected req!: Request;
@ -125,6 +126,15 @@ export abstract class ExpressController {
return this.res.send(pdfBuffer);
}
public downloadHTML(htmlString: string) {
this.res.set({
"Content-Type": "text/html; charset=utf-8",
"Content-Length": Buffer.byteLength(htmlString, "utf-8"),
});
return this.res.send(htmlString);
}
protected clientError(message: string, errors?: any[] | any) {
return this.handleApiError(
new ValidationApiError(message, Array.isArray(errors) ? errors : [errors])

View File

@ -5,11 +5,6 @@ import { lookup } from "mime-types";
import { TemplateResolver } from "./template-resolver";
interface AssetHelperOptions {
baseDir: string;
mode: "local_file" | "base64";
}
export class HandlebarsTemplateResolver extends TemplateResolver {
protected readonly hbs = Handlebars.create();
protected registered = false;
@ -18,18 +13,16 @@ export class HandlebarsTemplateResolver extends TemplateResolver {
/**
* Registra el helper "asset".
*
* - Si `mode === "local_file"` devuelve file://...
* - Si `mode === "base64"`:
* - Si el fichero termina en .b64 se asume que el contenido ya es base64
* - Si no se lee binario y se convierte a base64
*/
protected registerAssetHelper(templateDir: string, mode: "local_file" | "base64") {
protected registerAssetHelper(templateDir: string) {
// Si ya está registrado, no hacer nada
if (this.registered) return;
this.hbs.registerHelper("asset", (resource: string) => {
const assetPath = this.resolveAssetPath(templateDir, resource);
const cacheKey = `${mode}:${assetPath}`;
const cacheKey = `${assetPath}`;
// 1) Caché en memoria
const cached = this.assetCache.get(cacheKey);
@ -41,18 +34,12 @@ export class HandlebarsTemplateResolver extends TemplateResolver {
throw new Error(`Asset not found: ${assetPath}`);
}
// 2) Modo "local_file": solo devolver la ruta de fichero
if (mode === "local_file") {
const value = `file://${assetPath.replace(/\\/g, "/")}`;
this.assetCache.set(cacheKey, value);
return value;
}
// 3) Modo "base64"
const isPreencoded = assetPath.endsWith(".b64");
let base64: string;
let mimeType: string;
let value: string;
if (isPreencoded) {
// Fichero ya contiene el base64 en texto plano
@ -61,14 +48,24 @@ export class HandlebarsTemplateResolver extends TemplateResolver {
// Para el MIME usamos el nombre "original" sin .b64
const mimeLookupPath = assetPath.replace(/\.b64$/, "");
mimeType = (lookup(mimeLookupPath) || "application/octet-stream") as string;
value = `data:${mimeType};base64,${base64}`;
} else {
// Fichero binario normal → convertimos a base64
const buffer = readFileSync(assetPath);
mimeType = (lookup(assetPath) || "application/octet-stream") as string;
base64 = buffer.toString("base64");
// Fichero normal
// Si es un CSS no se convierte y se incrusta
const isCSS = assetPath.endsWith(".css");
if (isCSS) {
const buffer = readFileSync(assetPath);
value = buffer.toString();
} else {
// En otro caso, se transforma a Base64
const buffer = readFileSync(assetPath);
mimeType = (lookup(assetPath) || "application/octet-stream") as string;
base64 = buffer.toString("base64");
value = `data:${mimeType};base64,${base64}`;
}
}
const value = `data:${mimeType};base64,${base64}`;
this.assetCache.set(cacheKey, value);
return value;
});
@ -87,14 +84,12 @@ export class HandlebarsTemplateResolver extends TemplateResolver {
companySlug: string,
templateName: string
): Handlebars.TemplateDelegate {
const isDev = process.env.NODE_ENV === "development";
// 1) Directorio de plantillas
const templateDir = this.resolveTemplateDirectory(module, companySlug);
const templatePath = this.resolveTemplatePath(module, companySlug, templateName); // 2) Path completo del template
const source = this.readTemplateFile(templatePath); // Contenido
this.registerAssetHelper(templateDir, isDev ? "local_file" : "base64");
this.registerAssetHelper(templateDir);
// 5) Compilar
return this.compile(source);

View File

@ -55,8 +55,7 @@
"libphonenumber-js": "^1.12.7",
"lucide-react": "^0.503.0",
"pg-hstore": "^2.3.4",
"puppeteer": "^24.20.0",
"puppeteer-report": "^3.2.0",
"puppeteer": "^24.30.0",
"react-hook-form": "^7.58.1",
"react-i18next": "^15.5.1",
"react-router-dom": "^6.26.0",

View File

@ -21,7 +21,7 @@ export class IssuedInvoiceVerifactuFullPresenter extends Presenter {
}),
() => ({
id: "",
status: "",
status: "Pendiente",
url: "",
qr_code: "",
})

View File

@ -10,6 +10,7 @@ type ReportIssuedInvoiceUseCaseInput = {
companyId: UniqueID;
companySlug: string;
invoice_id: string;
format: "pdf" | "html";
};
export class ReportIssuedInvoiceUseCase {
@ -20,7 +21,7 @@ export class ReportIssuedInvoiceUseCase {
) {}
public async execute(params: ReportIssuedInvoiceUseCaseInput) {
const { invoice_id, companyId, companySlug } = params;
const { invoice_id, companyId, companySlug, format } = params;
const idOrError = UniqueID.create(invoice_id);
@ -32,7 +33,7 @@ export class ReportIssuedInvoiceUseCase {
const pdfPresenter = this.presenterRegistry.getPresenter({
resource: "issued-invoice",
projection: "REPORT",
format: "PDF",
format,
}) as IssuedInvoiceReportPDFPresenter;
return this.transactionManager.complete(async (transaction) => {
@ -48,10 +49,18 @@ export class ReportIssuedInvoiceUseCase {
}
const invoice = invoiceOrError.data;
const pdfData = await pdfPresenter.toOutput(invoice, { companySlug });
const reportData = await pdfPresenter.toOutput(invoice, { companySlug });
if (format === "html") {
return Result.ok({
data: String(reportData),
filename: undefined,
});
}
return Result.ok({
data: pdfData,
filename: `invoice-${invoice.invoiceNumber}.pdf`,
data: reportData as Buffer<ArrayBuffer>,
filename: `proforma-${invoice.invoiceNumber}.pdf`,
});
} catch (error: unknown) {
return Result.fail(error as Error);

View File

@ -1,12 +1,12 @@
import { Presenter } from "@erp/core/api";
import puppeteer from "puppeteer";
import report from "puppeteer-report";
import type { CustomerInvoice } from "../../../../../domain";
import type { IssuedInvoiceReportHTMLPresenter } from "./issued-invoice.report.html";
// https://plnkr.co/edit/lWk6Yd?preview
// https://latenode.com/es/blog/web-automation-scraping/puppeteer-fundamentals-setup/complete-guide-to-pdf-generation-with-puppeteer-from-simple-documents-to-complex-reports
export class IssuedInvoiceReportPDFPresenter extends Presenter<
CustomerInvoice,
@ -27,35 +27,35 @@ export class IssuedInvoiceReportPDFPresenter extends Presenter<
// Generar el PDF con Puppeteer
const browser = await puppeteer.launch({
headless: "new",
//headless: "new",
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
args: ["--font-render-hinting=medium"],
});
const page = await browser.newPage();
page.setDefaultNavigationTimeout(60000);
page.setDefaultTimeout(60000);
//page.setDefaultNavigationTimeout(60000);
//page.setDefaultTimeout(60000);
await page.setContent(htmlData, {
waitUntil: "networkidle0",
waitUntil: "networkidle2",
});
// Espera extra opcional si hay imágenes base64 muy grandes
await page.waitForNetworkIdle({ idleTime: 200, timeout: 5000 });
const reportPDF = await report.pdfPage(page, {
const reportPDF = await page.pdf({
format: "A4",
margin: {
bottom: "10mm",
left: "10mm",
right: "10mm",
top: "10mm",
top: 0,
left: 0,
right: 0,
bottom: 0,
},
landscape: false,
preferCSSPageSize: true,
omitBackground: false,
printBackground: true,
displayHeaderFooter: false,
displayHeaderFooter: true,
headerTemplate: "<div />",
footerTemplate:
'<div style="text-align: center;width: 297mm;font-size: 10px;">Página <span style="margin-right: 1cm"><span class="pageNumber"></span> de <span class="totalPages"></span></span></span></div>',

View File

@ -6,7 +6,6 @@ import {
IssueCustomerInvoiceDomainService,
ProformaCustomerInvoiceDomainService,
} from "../../../domain";
import type { ProformaFullPresenter } from "../../presenters";
import type { CustomerInvoiceApplicationService } from "../../services";
type IssueProformaUseCaseInput = {
@ -42,10 +41,6 @@ export class IssueProformaUseCase {
if (idOrError.isFailure) return Result.fail(idOrError.error);
const proformaId = idOrError.data;
const presenter = this.presenterRegistry.getPresenter({
resource: "issued-invoice",
projection: "FULL",
}) as ProformaFullPresenter;
return this.transactionManager.complete(async (transaction) => {
try {
@ -97,7 +92,12 @@ export class IssueProformaUseCase {
transaction
);
const dto = presenter.toOutput(saveInvoiceResult.data);
const invoice = saveInvoiceResult.data;
const dto = {
proforma_id: proforma.id.toString(),
invoice_id: invoice.id.toString(),
customer_id: invoice.customerId.toString(),
};
return Result.ok(dto);
} catch (error: unknown) {
return Result.fail(error as Error);

View File

@ -4,12 +4,11 @@ import { Result } from "@repo/rdx-utils";
import type { CustomerInvoiceApplicationService } from "../../../services/customer-invoice-application.service";
import type { ProformaReportPDFPresenter } from "./reporter";
type ReportProformaUseCaseInput = {
companyId: UniqueID;
companySlug: string;
proforma_id: string;
format: "pdf" | "html";
};
export class ReportProformaUseCase {
@ -20,7 +19,7 @@ export class ReportProformaUseCase {
) {}
public async execute(params: ReportProformaUseCaseInput) {
const { proforma_id, companySlug, companyId } = params;
const { proforma_id, companySlug, companyId, format } = params;
const idOrError = UniqueID.create(proforma_id);
@ -29,11 +28,11 @@ export class ReportProformaUseCase {
}
const proformaId = idOrError.data;
const pdfPresenter = this.presenterRegistry.getPresenter({
const reportPresenter = this.presenterRegistry.getPresenter({
resource: "proforma",
projection: "REPORT",
format: "PDF",
}) as ProformaReportPDFPresenter;
format,
});
return this.transactionManager.complete(async (transaction) => {
try {
@ -47,9 +46,17 @@ export class ReportProformaUseCase {
}
const proforma = proformaOrError.data;
const pdfData = await pdfPresenter.toOutput(proforma, { companySlug });
const reportData = await reportPresenter.toOutput(proforma, { companySlug });
if (format === "html") {
return Result.ok({
data: String(reportData),
filename: undefined,
});
}
return Result.ok({
data: pdfData,
data: reportData as Buffer<ArrayBuffer>,
filename: `proforma-${proforma.invoiceNumber}.pdf`,
});
} catch (error: unknown) {

View File

@ -1,12 +1,12 @@
import { Presenter } from "@erp/core/api";
import puppeteer from "puppeteer";
import report from "puppeteer-report";
import type { CustomerInvoice } from "../../../../../domain";
import type { ProformaReportHTMLPresenter } from "./proforma.report.html";
// https://plnkr.co/edit/lWk6Yd?preview
// https://latenode.com/es/blog/web-automation-scraping/puppeteer-fundamentals-setup/complete-guide-to-pdf-generation-with-puppeteer-from-simple-documents-to-complex-reports
export class ProformaReportPDFPresenter extends Presenter<
CustomerInvoice,
@ -25,38 +25,37 @@ export class ProformaReportPDFPresenter extends Presenter<
const htmlData = htmlPresenter.toOutput(proforma, params);
// Generar el PDF con Puppeteer
// Generar el PDF con Puppeteer
const browser = await puppeteer.launch({
headless: "new",
//headless: "new",
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
args: ["--font-render-hinting=medium"],
});
const page = await browser.newPage();
page.setDefaultNavigationTimeout(60000);
page.setDefaultTimeout(60000);
//page.setDefaultNavigationTimeout(60000);
//page.setDefaultTimeout(60000);
await page.setContent(htmlData, {
waitUntil: "networkidle0",
waitUntil: "networkidle2",
});
// Espera extra opcional si hay imágenes base64 muy grandes
await page.waitForNetworkIdle({ idleTime: 200, timeout: 5000 });
const reportPDF = await report.pdfPage(page, {
const reportPDF = await page.pdf({
format: "A4",
margin: {
bottom: "10mm",
left: "10mm",
right: "10mm",
top: "10mm",
bottom: 0,
left: 0,
right: 0,
top: 0,
},
landscape: false,
preferCSSPageSize: true,
omitBackground: false,
printBackground: true,
displayHeaderFooter: false,
displayHeaderFooter: true,
headerTemplate: "<div />",
footerTemplate:
'<div style="text-align: center;width: 297mm;font-size: 10px;">Página <span style="margin-right: 1cm"><span class="pageNumber"></span> de <span class="totalPages"></span></span></span></div>',

View File

@ -20,11 +20,13 @@ export class ReportIssuedInvoiceController extends ExpressController {
const { companySlug } = this.getUser();
const { invoice_id } = this.req.params;
const { format } = this.req.query as { format: "pdf" | "html" };
const result = await this.useCase.execute({ invoice_id, companyId, companySlug });
const result = await this.useCase.execute({ invoice_id, companyId, companySlug, format });
return result.match(
({ data, filename }) => this.downloadPDF(data, filename),
({ data, filename }) =>
filename ? this.downloadPDF(data, filename) : this.downloadHTML(data as string),
(err) => this.handleError(err)
);
}

View File

@ -20,11 +20,13 @@ export class ReportProformaController extends ExpressController {
const { companySlug } = this.getUser();
const { proforma_id } = this.req.params;
const { format } = this.req.query as { format: "pdf" | "html" };
const result = await this.useCase.execute({ proforma_id, companyId, companySlug });
const result = await this.useCase.execute({ proforma_id, companyId, companySlug, format });
return result.match(
({ data, filename }) => this.downloadPDF(data, filename),
({ data, filename }) =>
filename ? this.downloadPDF(data, filename) : this.downloadHTML(data as string),
(err) => this.handleError(err)
);
}

View File

@ -7,7 +7,8 @@ import type { Sequelize } from "sequelize";
import {
GetIssueInvoiceByIdRequestSchema,
ListIssuedInvoicesRequestSchema,
ReportIssueInvoiceByIdRequestSchema,
ReportIssueInvoiceByIdParamsRequestSchema,
ReportIssueInvoiceByIdQueryRequestSchema,
} from "../../../common/dto";
import { buildIssuedInvoicesDependencies } from "../issued-invoices-dependencies";
@ -71,7 +72,8 @@ export const issuedInvoicesRouter = (params: ModuleParams) => {
router.get(
"/:invoice_id/report",
//checkTabContext,
validateRequest(ReportIssueInvoiceByIdRequestSchema, "params"),
validateRequest(ReportIssueInvoiceByIdParamsRequestSchema, "params"),
validateRequest(ReportIssueInvoiceByIdQueryRequestSchema, "query"),
(req: Request, res: Response, next: NextFunction) => {
const useCase = deps.useCases.report_issued_invoice();
const controller = new ReportIssuedInvoiceController(useCase);

View File

@ -12,7 +12,8 @@ import {
GetProformaByIdRequestSchema,
IssueProformaByIdParamsRequestSchema,
ListProformasRequestSchema,
ReportProformaByIdRequestSchema,
ReportProformaByIdParamsRequestSchema,
ReportProformaByIdQueryRequestSchema,
UpdateProformaByIdParamsRequestSchema,
UpdateProformaByIdRequestSchema,
} from "../../../common";
@ -121,7 +122,8 @@ export const proformasRouter = (params: ModuleParams) => {
router.get(
"/:proforma_id/report",
//checkTabContext,
validateRequest(ReportProformaByIdRequestSchema, "params"),
validateRequest(ReportProformaByIdParamsRequestSchema, "params"),
validateRequest(ReportProformaByIdQueryRequestSchema, "query"),
(req: Request, res: Response, next: NextFunction) => {
const useCase = deps.useCases.report_proforma();
const controller = new ReportProformaController(useCase);

View File

@ -78,8 +78,6 @@ export class CustomerInvoiceRepository
});
const dtoResult = mapper.mapToPersistence(invoice);
console.log("DTO to persist:", dtoResult);
if (dtoResult.isFailure) {
return Result.fail(dtoResult.error);
}
@ -358,6 +356,12 @@ export class CustomerInvoiceRepository
],
include: [
...normalizedInclude,
{
model: VerifactuRecordModel,
as: "verifactu",
required: false,
attributes: ["id", "estado", "url", "uuid"],
},
{
model: CustomerModel,
as: "current_customer",

View File

@ -1,7 +1,17 @@
import { z } from "zod/v4";
export const ReportIssueInvoiceByIdRequestSchema = z.object({
export const ReportIssueInvoiceByIdParamsRequestSchema = z.object({
invoice_id: z.string(),
});
export type ReportIssueInvoiceByIdRequestDTO = z.infer<typeof ReportIssueInvoiceByIdRequestSchema>;
export type ReportIssueInvoiceByIdParamsRequestDTO = z.infer<
typeof ReportIssueInvoiceByIdParamsRequestSchema
>;
export const ReportIssueInvoiceByIdQueryRequestSchema = z.object({
format: z.enum(["pdf", "html"]).default("pdf"),
});
export type ReportIssueInvoiceByIdQueryRequestDTO = z.infer<
typeof ReportIssueInvoiceByIdQueryRequestSchema
>;

View File

@ -1,7 +1,16 @@
import { z } from "zod/v4";
export const ReportProformaByIdRequestSchema = z.object({
export const ReportProformaByIdParamsRequestSchema = z.object({
proforma_id: z.string(),
});
export type ReportProformaByIdRequestDTO = z.infer<typeof ReportProformaByIdRequestSchema>;
export type ReportProformaByIdParamsRequestDTO = z.infer<
typeof ReportProformaByIdParamsRequestSchema
>;
export const ReportProformaByIdQueryRequestSchema = z.object({
format: z.enum(["pdf", "html"]).default("pdf"),
});
export type ReportProformaByIdQueryRequestDTO = z.infer<
typeof ReportProformaByIdQueryRequestSchema
>;

View File

@ -3,13 +3,7 @@
<head>
<meta charset="UTF-8">
<style type="text/css">
{
{
asset 'tailwind.css.b64'
}
}
</style>
<style>{{ asset 'tailwind.css' }}</style>
<title>Factura</title>
<style>
/* ---------------------------- */

View File

@ -3,15 +3,9 @@
<head>
<meta charset="UTF-8">
<style type="text/css">
{
{
asset 'tailwind.css.b64'
}
}
</style>
<style>{{ asset 'tailwind.css' }}</style>
<title>Factura proforma</title>
<style>
<style type="text/css">
/* ---------------------------- */
/* ESTRUCTURA CABECERA */
/* ---------------------------- */
@ -43,7 +37,7 @@
.company-text {
font-size: 7pt;
line-height: 1.2;
line-height: 1;
padding-left: 10px;
}

File diff suppressed because one or more lines are too long

View File

@ -511,11 +511,8 @@ importers:
specifier: ^2.3.4
version: 2.3.4
puppeteer:
specifier: ^24.20.0
version: 24.28.0(typescript@5.9.3)
puppeteer-report:
specifier: ^3.2.0
version: 3.2.0
specifier: ^24.30.0
version: 24.30.0(typescript@5.9.3)
react-hook-form:
specifier: ^7.58.1
version: 7.66.0(react@19.2.0)
@ -1768,12 +1765,6 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@pdf-lib/standard-fonts@1.0.0':
resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==}
'@pdf-lib/upng@1.0.1':
resolution: {integrity: sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@ -3167,8 +3158,8 @@ packages:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
chromium-bidi@10.5.1:
resolution: {integrity: sha512-rlj6OyhKhVTnk4aENcUme3Jl9h+cq4oXu4AzBcvr8RMmT6BR4a3zSNT9dbIfXr9/BS6ibzRyDhowuw4n2GgzsQ==}
chromium-bidi@11.0.0:
resolution: {integrity: sha512-cM3DI+OOb89T3wO8cpPSro80Q9eKYJ7hGVXoGS3GkDPxnYSqiv+6xwpIf6XERyJ9Tdsl09hmNmY94BkgZdVekw==}
peerDependencies:
devtools-protocol: '*'
@ -4810,9 +4801,6 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
param-case@2.1.1:
resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
@ -4910,9 +4898,6 @@ packages:
pause@0.0.1:
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
pdf-lib@1.17.1:
resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
@ -5060,15 +5045,12 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
puppeteer-core@24.28.0:
resolution: {integrity: sha512-QpAqaYgeZHF5/xAZ4jAOzsU+l0Ed4EJoWkRdfw8rNqmSN7itcdYeCJaSPQ0s5Pyn/eGNC4xNevxbgY+5bzNllw==}
puppeteer-core@24.30.0:
resolution: {integrity: sha512-2S3Smy0t0W4wJnNvDe7W0bE7wDmZjfZ3ljfMgJd6hn2Hq/f0jgN+x9PULZo2U3fu5UUIJ+JP8cNUGllu8P91Pg==}
engines: {node: '>=18'}
puppeteer-report@3.2.0:
resolution: {integrity: sha512-c2JNsAeLa4Ik4FVPdTRyWYfXO61uSI0ZJ9BNPuf84sImTmwBT4CY/Vn88iQ7q7LQstJStkPuNWAzJgNEmBONmQ==}
puppeteer@24.28.0:
resolution: {integrity: sha512-KLRGFNCGmXJpocEBbEIoHJB0vNRZLQNBjl5ExXEv0z7MIU+qqVEQcfWTyat+qxPDk/wZvSf+b30cQqAfWxX0zg==}
puppeteer@24.30.0:
resolution: {integrity: sha512-A5OtCi9WpiXBQgJ2vQiZHSyrAzQmO/WDsvghqlN4kgw21PhxA5knHUaUQq/N3EMt8CcvSS0RM+kmYLJmedR3TQ==}
engines: {node: '>=18'}
hasBin: true
@ -6815,14 +6797,6 @@ snapshots:
'@parcel/watcher-win32-ia32': 2.5.1
'@parcel/watcher-win32-x64': 2.5.1
'@pdf-lib/standard-fonts@1.0.0':
dependencies:
pako: 1.0.11
'@pdf-lib/upng@1.0.1':
dependencies:
pako: 1.0.11
'@pkgjs/parseargs@0.11.0':
optional: true
@ -8284,7 +8258,7 @@ snapshots:
chownr@2.0.0: {}
chromium-bidi@10.5.1(devtools-protocol@0.0.1521046):
chromium-bidi@11.0.0(devtools-protocol@0.0.1521046):
dependencies:
devtools-protocol: 0.0.1521046
mitt: 3.0.1
@ -9963,8 +9937,6 @@ snapshots:
package-json-from-dist@1.0.1: {}
pako@1.0.11: {}
param-case@2.1.1:
dependencies:
no-case: 2.3.2
@ -10060,13 +10032,6 @@ snapshots:
pause@0.0.1: {}
pdf-lib@1.17.1:
dependencies:
'@pdf-lib/standard-fonts': 1.0.0
'@pdf-lib/upng': 1.0.1
pako: 1.0.11
tslib: 1.14.1
pend@1.2.0: {}
pg-connection-string@2.9.1: {}
@ -10215,10 +10180,10 @@ snapshots:
punycode@2.3.1: {}
puppeteer-core@24.28.0:
puppeteer-core@24.30.0:
dependencies:
'@puppeteer/browsers': 2.10.13
chromium-bidi: 10.5.1(devtools-protocol@0.0.1521046)
chromium-bidi: 11.0.0(devtools-protocol@0.0.1521046)
debug: 4.4.3
devtools-protocol: 0.0.1521046
typed-query-selector: 2.12.0
@ -10232,17 +10197,13 @@ snapshots:
- supports-color
- utf-8-validate
puppeteer-report@3.2.0:
dependencies:
pdf-lib: 1.17.1
puppeteer@24.28.0(typescript@5.9.3):
puppeteer@24.30.0(typescript@5.9.3):
dependencies:
'@puppeteer/browsers': 2.10.13
chromium-bidi: 10.5.1(devtools-protocol@0.0.1521046)
chromium-bidi: 11.0.0(devtools-protocol@0.0.1521046)
cosmiconfig: 9.0.0(typescript@5.9.3)
devtools-protocol: 0.0.1521046
puppeteer-core: 24.28.0
puppeteer-core: 24.30.0
typed-query-selector: 2.12.0
transitivePeerDependencies:
- bare-abort-controller