diff --git a/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-template-resolver.ts b/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-template-resolver.ts index eaf84023..b57efb54 100644 --- a/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-template-resolver.ts +++ b/modules/core/src/api/infrastructure/documents/renderers/fastreport/fastreport-template-resolver.ts @@ -31,8 +31,8 @@ export class FastReportTemplateResolver { return this.resolveJoin([module, "templates", companySlug, languageCode]); } - // /templates/// - return this.resolveJoin([companySlug, module, languageCode]); + // /templates// + return this.resolveJoin([module, languageCode]); } /** Resuelve una ruta de recurso relativa al directorio de plantilla */ diff --git a/modules/core/src/api/infrastructure/documents/renderers/handlebars/handlebars-template-resolver.ts b/modules/core/src/api/infrastructure/documents/renderers/handlebars/handlebars-template-resolver.ts new file mode 100644 index 00000000..011c820a --- /dev/null +++ b/modules/core/src/api/infrastructure/documents/renderers/handlebars/handlebars-template-resolver.ts @@ -0,0 +1,97 @@ +import { existsSync, readFileSync } from "node:fs"; + +import Handlebars from "handlebars"; +import { lookup } from "mime-types"; + +import { RendererTemplateResolver } from "./renderer-template-resolver-SOBRA"; + +export class HandlebarsTemplateResolver extends RendererTemplateResolver { + protected readonly hbs = Handlebars.create(); + protected registered = false; + protected readonly assetCache = new Map(); + + /** + * Registra el helper "asset". + * + * - 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) { + // 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 = `${assetPath}`; + + // 1) Caché en memoria + const cached = this.assetCache.get(cacheKey); + if (cached) { + return cached; + } + + if (!existsSync(assetPath)) { + throw new Error(`Asset not found: ${assetPath}`); + } + + // 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 + base64 = readFileSync(assetPath, "utf8").trim(); + + // 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 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}`; + } + } + + this.assetCache.set(cacheKey, value); + return value; + }); + + this.registered = true; + } + + /** Compilación directa desde string (sin resolución de rutas) */ + public compile(templateSource: string) { + return this.hbs.compile(templateSource); + } + + /** Localiza → lee → registra helpers → compila */ + public compileTemplate( + module: string, + companySlug: string, + templateName: string + ): Handlebars.TemplateDelegate { + // 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); + + // 5) Compilar + return this.compile(source); + } +} diff --git a/modules/core/src/api/infrastructure/documents/renderers/handlebars/renderer-template-resolver-SOBRA.ts b/modules/core/src/api/infrastructure/documents/renderers/handlebars/renderer-template-resolver-SOBRA.ts new file mode 100644 index 00000000..f8e1b4d6 --- /dev/null +++ b/modules/core/src/api/infrastructure/documents/renderers/handlebars/renderer-template-resolver-SOBRA.ts @@ -0,0 +1,61 @@ +import { existsSync, readFileSync } from "node:fs"; +import { join } from "node:path"; + +import type { IRendererTemplateResolver } from "../../../application"; + +import { FastReportTemplateNotFoundError } from "./fastreport"; + +/** + * Resuelve rutas de plantillas para desarrollo y producción. + */ +export abstract class RendererTemplateResolver implements IRendererTemplateResolver { + constructor(protected readonly rootPath: string) {} + + /** Une partes de ruta relativas al rootPath */ + protected resolveJoin(parts: string[]): string { + return join(this.rootPath, ...parts); + } + + /** + * Devuelve el directorio donde residen las plantillas de un módulo/empresa + * según el entorno (dev/prod). + */ + protected resolveTemplateDirectory(module: string, companySlug: string): string { + const isDev = process.env.NODE_ENV === "development"; + + if (isDev) { + // //templates// + return this.resolveJoin([module, "templates", companySlug]); + } + + // /templates/// + //return this.resolveJoin(["templates", module, companySlug]); + return this.resolveJoin([module]); + } + + /** Resuelve una ruta de recurso relativa al directorio de plantilla */ + protected resolveAssetPath(templateDir: string, relative: string): string { + return join(templateDir, relative); + } + + /** + * Devuelve la ruta absoluta del fichero de plantilla. + */ + public resolveTemplatePath(module: string, companySlug: string, templateName: string): string { + const dir = this.resolveTemplateDirectory(module, companySlug); + const filePath = this.resolveAssetPath(dir, templateName); + + if (!existsSync(filePath)) { + throw new FastReportTemplateNotFoundError( + `Template not found: module=${module} company=${companySlug} name=${templateName}` + ); + } + + return filePath; + } + + /** Lee el contenido de un fichero plantilla */ + protected readTemplateFile(templatePath: string): string { + return readFileSync(templatePath, "utf8"); + } +} diff --git a/scripts/build-factuges.sh b/scripts/build-factuges.sh index a6906873..64bf78f8 100755 --- a/scripts/build-factuges.sh +++ b/scripts/build-factuges.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -SCRIPT_VERSION="1.2.8" +SCRIPT_VERSION="1.2.9" # ===================================================== # FACTUGES Build Script @@ -194,6 +194,7 @@ if [[ "$MODE" == "api" || "$MODE" == "all" ]]; then # Recopilar plantillas ${SCRIPT_DIR}/build-templates.sh ${COMPANY} TEMPLATES_DIR="${PROJECT_DIR}/out/${COMPANY}/templates" + echo "📑 Plantillas ${TEMPLATES_DIR}" cd "${PROJECT_DIR}" echo "🐳 Construyendo imagen Docker..." diff --git a/scripts/build-templates.sh b/scripts/build-templates.sh index 53c4eac7..b36f4bbd 100755 --- a/scripts/build-templates.sh +++ b/scripts/build-templates.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -SCRIPT_VERSION="0.0.2" +SCRIPT_VERSION="0.0.3" # ===================================================== # TEMPLATES Build Script @@ -54,7 +54,7 @@ rm -rf "${TARGET_DIR:?}/"* for module in "$SOURCE_DIR"/*; do if [ -d "$module/templates/$COMPANY" ]; then module_name=$(basename "$module") - echo "→ Copying templates for module: $module_name" + echo "📑 → Copying templates for module: $module_name" mkdir -p "$TARGET_DIR/$module_name" cp -Rv "$module/templates/$COMPANY/." "$TARGET_DIR/$module_name/" diff --git a/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/linux/FastReportCliGenerator b/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/linux/FastReportCliGenerator index c79ce77f..aeb45cd2 100755 Binary files a/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/linux/FastReportCliGenerator and b/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/linux/FastReportCliGenerator differ diff --git a/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/windows/FastReportCliGenerator.exe b/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/windows/FastReportCliGenerator.exe index 2cbf7f63..80d57821 100755 Binary files a/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/windows/FastReportCliGenerator.exe and b/tools/fastreportcli-net-core-skia/FastReportCliGenerator/publish/windows/FastReportCliGenerator.exe differ