diff --git a/src/main/java/com/imprimelibros/erp/ErpApplication.java b/src/main/java/com/imprimelibros/erp/ErpApplication.java index 441eb0c..0e589a8 100644 --- a/src/main/java/com/imprimelibros/erp/ErpApplication.java +++ b/src/main/java/com/imprimelibros/erp/ErpApplication.java @@ -2,8 +2,10 @@ package com.imprimelibros.erp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @SpringBootApplication +@ConfigurationPropertiesScan(basePackages = "com.imprimelibros.erp") public class ErpApplication { public static void main(String[] args) { diff --git a/src/main/java/com/imprimelibros/erp/pdf/PdfModuleConfig.java b/src/main/java/com/imprimelibros/erp/pdf/PdfModuleConfig.java index 244f915..7ae5481 100644 --- a/src/main/java/com/imprimelibros/erp/pdf/PdfModuleConfig.java +++ b/src/main/java/com/imprimelibros/erp/pdf/PdfModuleConfig.java @@ -1,17 +1,14 @@ +// com.imprimelibros.erp.pdf.PdfModuleConfig.java package com.imprimelibros.erp.pdf; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; import java.util.Map; -@Configuration @ConfigurationProperties(prefix = "imprimelibros.pdf") public class PdfModuleConfig { - /** - * Mapa: "TYPE:templateId" -> "ruta thymeleaf" (sin extensión). - * Ej: "PRESUPUESTO:presupuesto-a4" -> "pdf/presupuesto-a4" - */ - private Map templates; + private Map templates = new HashMap<>(); public Map getTemplates() { return templates; } public void setTemplates(Map templates) { this.templates = templates; } diff --git a/src/main/java/com/imprimelibros/erp/pdf/PdfRenderer.java b/src/main/java/com/imprimelibros/erp/pdf/PdfRenderer.java index 3502808..79c2db5 100644 --- a/src/main/java/com/imprimelibros/erp/pdf/PdfRenderer.java +++ b/src/main/java/com/imprimelibros/erp/pdf/PdfRenderer.java @@ -1,5 +1,6 @@ package com.imprimelibros.erp.pdf; +import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder; import com.openhtmltopdf.pdfboxout.PdfRendererBuilder; import org.springframework.core.io.Resource; import org.springframework.beans.factory.annotation.Value; @@ -11,34 +12,33 @@ import java.io.ByteArrayOutputStream; public class PdfRenderer { @Value("classpath:/static/") - private Resource staticRoot; + private org.springframework.core.io.Resource staticRoot; public byte[] renderHtmlToPdf(String html) { try (var baos = new ByteArrayOutputStream()) { - var builder = new PdfRendererBuilder(); - - builder.useFont(() -> getClass().getResourceAsStream("/static/assets/fonts/OpenSans-Regular.ttf"), "Open Sans", - 400, com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.FontStyle.NORMAL, true); - builder.useFont(() -> getClass().getResourceAsStream("/static/assets/fonts/OpenSans-SemiBold.ttf"), "Open Sans", - 600, com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.FontStyle.NORMAL, true); - builder.useFont(() -> getClass().getResourceAsStream("/static/assets/fonts/OpenSans-Bold.ttf"), "Open Sans", 700, - com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder.FontStyle.NORMAL, true); - + var builder = new com.openhtmltopdf.pdfboxout.PdfRendererBuilder(); builder.useFastMode(); - builder.withHtmlContent(html, baseUrl()); + + // 👇 Base URL para que pueda resolver /assets/css/ y /img/ + builder.withHtmlContent(html, staticRoot.getURL().toString()); // .../target/classes/static/ + + + // (Opcional) Registrar fuentes TTF + builder.useFont(() -> getClass().getResourceAsStream("/static/assets/fonts/OpenSans-Regular.ttf"), + "Open Sans", 400, BaseRendererBuilder.FontStyle.NORMAL, true); + builder.useFont(() -> getClass().getResourceAsStream("/static/assets/fonts/OpenSans-SemiBold.ttf"), + "Open Sans", 600, BaseRendererBuilder.FontStyle.NORMAL, true); + builder.useFont(() -> getClass().getResourceAsStream("/static/assets/fonts/OpenSans-Bold.ttf"), + "Open Sans", 700, BaseRendererBuilder.FontStyle.NORMAL, true); + builder.toStream(baos); builder.run(); + return baos.toByteArray(); } catch (Exception e) { throw new IllegalStateException("Error generando PDF", e); } } - - private String baseUrl() { - try { - return staticRoot.getURL().toString(); - } catch (Exception e) { - return null; - } - } } + + diff --git a/src/main/java/com/imprimelibros/erp/pdf/TemplateRegistry.java b/src/main/java/com/imprimelibros/erp/pdf/TemplateRegistry.java index 153d88f..71f19c1 100644 --- a/src/main/java/com/imprimelibros/erp/pdf/TemplateRegistry.java +++ b/src/main/java/com/imprimelibros/erp/pdf/TemplateRegistry.java @@ -1,4 +1,3 @@ -// com.imprimelibros.erp.pdf.TemplateRegistry.java package com.imprimelibros.erp.pdf; import org.springframework.stereotype.Service; @@ -9,10 +8,15 @@ public class TemplateRegistry { public TemplateRegistry(PdfModuleConfig config) { this.config = config; + System.out.println("PDF templates registrados => " + config.getTemplates()); } public String resolve(DocumentType type, String templateId) { + String key = type.name() + ":" + templateId; + String keyAlt = type.name() + "_" + templateId; // compatibilidad con properties if (config.getTemplates() == null) return null; - return config.getTemplates().get(type.name() + ":" + templateId); + String value = config.getTemplates().get(key); + if (value == null) value = config.getTemplates().get(keyAlt); + return value; } } diff --git a/src/main/java/com/imprimelibros/erp/presupuesto.zip b/src/main/java/com/imprimelibros/erp/presupuesto.zip deleted file mode 100644 index f40202e..0000000 Binary files a/src/main/java/com/imprimelibros/erp/presupuesto.zip and /dev/null differ diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a020a4d..66f623c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -89,5 +89,6 @@ spring.jpa.properties.hibernate.jdbc.time_zone=UTC # # PDF Templates # -imprimelibros.pdf.templates.PRESUPUESTO\:presupuesto-a4=imprimelibros/pdf/presupuesto-a4 -imprimelibros.pdf.templates.FACTURA\:factura-a4=imprimelibros/pdf/factura-a4 \ No newline at end of file +# PDF Templates +imprimelibros.pdf.templates.PRESUPUESTO_presupuesto-a4=imprimelibros/pdf/presupuesto-a4 +imprimelibros.pdf.templates.FACTURA_factura-a4=imprimelibros/pdf/factura-a4 diff --git a/src/main/resources/static/assets/css/presupuestopdf.css b/src/main/resources/static/assets/css/presupuestopdf.css index 6ad90ee..1b1063a 100644 --- a/src/main/resources/static/assets/css/presupuestopdf.css +++ b/src/main/resources/static/assets/css/presupuestopdf.css @@ -1,225 +1,144 @@ -/* === Fuentes === - * Para HTML→PDF con OpenHTMLtoPDF, es preferible registrar las TTF en el renderer. - * Aun así, incluimos @font-face por si renderizas en navegador. - * Coloca los TTF en /static/fonts/ y ajusta los nombres si procede. - */ +/* Open Sans (rutas relativas desde css → fonts) */ @font-face { font-family: "Open Sans"; - src: url("/assets/fonts/OpenSans-Regular.ttf") format("truetype"); + src: url("../fonts/OpenSans-Regular.ttf") format("truetype"); font-weight: 400; - font-style: normal; } @font-face { font-family: "Open Sans"; - src: url("/assets/fonts/OpenSans-SemiBold.ttf") format("truetype"); + src: url("../fonts/OpenSans-SemiBold.ttf") format("truetype"); font-weight: 600; - font-style: normal; } @font-face { font-family: "Open Sans"; - src: url("/assets/fonts/OpenSans-Bold.ttf") format("truetype"); + src: url("../fonts/OpenSans-Bold.ttf") format("truetype"); font-weight: 700; - font-style: normal; } :root { - --text: #1f2937; - --muted: #6b7280; - --border: #e5e7eb; - --brand: #0ea5e9; /* azul suave */ - --bg-light: #f8fafc; /* gris muy claro */ + --ink: #1b1e28; + --muted: #5b6472; + --accent: #0ea5e9; /* azul tira a cyan */ + --line: #e6e8ef; + --bg-tag: #f4f7fb; } @page { size: A4; - margin: 18mm 15mm 22mm 15mm; - @bottom-right { - content: "Página " counter(page) " / " counter(pages); - } + margin: 15mm 14mm 18mm 14mm; + @bottom-right { content: "Página " counter(page) " / " counter(pages); } } + html, body { font-family: "Open Sans", Arial, sans-serif; - color: var(--text); + color: var(--ink); font-size: 11pt; line-height: 1.35; } -* { box-sizing: border-box; } -.doc-header { +/* Top band */ +.topbar { display: table; width: 100%; - margin-bottom: 10mm; - border-bottom: 2px solid var(--brand); + border-bottom: 2px solid var(--accent); padding-bottom: 6mm; + margin-bottom: 6mm; } -.brand { - display: table-cell; - vertical-align: top; - width: 60%; -} -.logo { - height: 34px; - margin-bottom: 6px; -} -.brand-meta { margin-top: 4px; } -.company-name { - font-weight: 700; - font-size: 12pt; -} -.company-meta { - color: var(--muted); - font-size: 9.5pt; -} +.brand { display: table-cell; width: 60%; vertical-align: top; } +.logo { height: 30px; display: block; margin-bottom: 4px; } +.brand-name { font-weight: 700; } +.brand-meta { color: var(--muted); font-size: 9.5pt; } +.doc-title { display: table-cell; width: 40%; text-align: right; font-weight: 700; letter-spacing: 3px; } -.doc-title { - display: table-cell; - vertical-align: top; - width: 40%; - text-align: right; -} -.title-main { - font-size: 18pt; - font-weight: 700; - letter-spacing: 0.5px; -} -.doc-info { - margin-top: 6px; - margin-left: auto; - font-size: 10pt; - border-collapse: collapse; -} -.doc-info td { - padding: 3px 0 3px 12px; - border-bottom: 1px solid var(--border); -} -.doc-info td:first-child { - color: var(--muted); - padding-left: 0; -} -.right { text-align: right; } - -.blocks { - display: table; +/* ficha superior */ +.sheet-info { width: 100%; - table-layout: fixed; - margin-bottom: 8mm; + border-collapse: collapse; + margin: 4mm 0 6mm 0; + font-size: 10.5pt; } -.block { - display: table-cell; - width: 50%; - padding-right: 6mm; - vertical-align: top; +.sheet-info td { + border: 1px solid var(--line); + padding: 4px 6px; } -.block:last-child { padding-right: 0; } +.sheet-info .lbl { color: var(--muted); margin-right: 4px; } +.sheet-info .val { font-weight: 700; } + +/* Línea título libro */ +.line-title { + margin: 3mm 0 5mm 0; + padding: 4px 6px; + background: var(--bg-tag); + border-left: 3px solid var(--accent); + font-size: 10.5pt; +} +.line-title .lbl { color: var(--muted); margin-right: 6px; font-weight: 600; } + +/* Specs 2 columnas */ +.specs { display: table; width: 100%; table-layout: fixed; margin-bottom: 6mm; } +.specs .col { display: table-cell; width: 50%; padding-right: 6mm; vertical-align: top; } +.specs .col:last-child { padding-right: 0; } .block-title { - font-weight: 600; text-transform: uppercase; + font-weight: 700; + color: var(--accent); font-size: 10pt; - color: var(--brand); - margin-bottom: 2mm; -} -.block-body { - border: 1px solid var(--border); - padding: 4mm; - background: var(--bg-light); -} -.row { - margin-bottom: 2mm; - display: block; -} -.row .label { - display: inline-block; - width: 27%; - color: var(--muted); -} -.row .value { - display: inline-block; - width: 70%; - vertical-align: top; + margin: 2mm 0 1mm 0; } +.kv { margin: 1mm 0; } +.kv span { color: var(--muted); display: inline-block; min-width: 55%; } +.kv b { font-weight: 600; } +.subblock { margin-top: 3mm; } -.table-section .section-title, -.notes .section-title { - font-weight: 600; - font-size: 10.5pt; - margin: 6mm 0 2mm 0; -} +.services { margin: 0; padding-left: 14px; } +.services li { margin: 1mm 0; } -.items { - width: 100%; - border-collapse: collapse; - font-size: 10.5pt; -} -.items thead th { - text-align: left; - border-bottom: 2px solid var(--brand); - padding: 6px 6px; - background: #eef8fe; - font-weight: 600; -} -.items tbody td { - border-bottom: 1px solid var(--border); - padding: 6px 6px; - vertical-align: top; -} -.items .col-right { text-align: right; } -.items .col-center { text-align: center; } -.items .col-desc .desc { font-weight: 600; } -.items .col-desc .meta { color: var(--muted); font-size: 9.5pt; margin-top: 2px; } -.items .group-header td { - background: var(--bg-light); - color: var(--brand); - font-weight: 600; -} - -.totals { - margin-top: 6mm; - display: block; - width: 100%; -} -.totals-table { - margin-left: auto; - border-collapse: collapse; - min-width: 70mm; -} -.totals-table td { - padding: 4px 0 4px 14px; - border-bottom: 1px solid var(--border); -} -.totals-table td:first-child { - color: var(--muted); - padding-left: 0; -} -.totals-table .total-row td { - border-bottom: 2px solid var(--brand); - padding-top: 6px; - padding-bottom: 6px; -} -.totals-table .right { text-align: right; } - -.notes .note-text { - border: 1px solid var(--border); - padding: 4mm; +/* Bloque marcapáginas */ +.bookmark { + margin-top: 4mm; + border: 1px dashed var(--line); + padding: 3mm; background: #fff; - font-size: 10pt; - white-space: pre-wrap; +} +.bookmark .bk-title { + font-weight: 700; margin-bottom: 2mm; } -.doc-footer { +/* Tabla de precios (tiradas) */ +.prices { + width: 100%; + border-collapse: collapse; + margin-top: 6mm; + font-size: 10.5pt; +} +.prices thead th { + text-align: left; + padding: 6px; + border-bottom: 2px solid var(--accent); + background: #eef8fe; + font-weight: 700; +} +.prices tbody td { + border-bottom: 1px solid var(--line); + padding: 6px; +} +.prices .col-tirada { width: 22%; font-weight: 700; } + +/* Footer */ +.footer { margin-top: 8mm; + border-top: 1px solid var(--line); padding-top: 4mm; - border-top: 1px solid var(--border); display: table; width: 100%; + font-size: 9.5pt; color: var(--muted); - font-size: 9pt; -} -.footer-left { display: table-cell; } -.footer-right { - display: table-cell; - text-align: right; } +.footer .address { display: table-cell; width: 45%; } +.footer .privacy { display: table-cell; width: 55%; } +.pv-title { font-weight: 700; margin-bottom: 1mm; color: var(--ink); } +.pv-text { line-height: 1.25; } -/* Compatibilidad PDF (OpenHTMLtoPDF) */ -.page-number::after { content: counter(page); } -.page-count::after { content: counter(pages); } +.page-count { margin-top: 2mm; text-align: right; font-size: 9pt; color: var(--muted); } +.page::after { content: counter(page); } +.pages::after { content: counter(pages); } diff --git a/src/main/resources/templates/imprimelibros/pdf/presupuesto-a4.html b/src/main/resources/templates/imprimelibros/pdf/presupuesto-a4.html index 23141d3..6e58aeb 100644 --- a/src/main/resources/templates/imprimelibros/pdf/presupuesto-a4.html +++ b/src/main/resources/templates/imprimelibros/pdf/presupuesto-a4.html @@ -3,193 +3,140 @@ Presupuesto - + - -
+ +
- -
-
ImprimeLibros ERP
-
- C/ Dirección 123, 28000 Madrid
- +34 600 000 000 · - info@imprimelibros.com
- B-12345678 + +
+
ImprimeLibros ERP
+
+ C. José Picón, 28 local A · 28028 Madrid + · 910052574 + · info@imprimelibros.com + · B04998886
-
-
PRESUPUESTO
- - - - - - - - - - - - - -
2025-00123
Fecha12/10/2025
Validez30 días
-
-
+
P R E S U P U E S T O
+ - -
-
-
Cliente
-
-
- Nombre: - Editorial Ejemplo S.L. -
-
- CIF/NIF: - B-00000000 -
-
- Dirección: - Av. de los Libros, 45 -
-
- Localidad: - - - - - -
-
- Email: - comercial@editorial.com -
+ + + + + + + +
PRESUPUESTO Nº: 153153CLIENTE: JUAN JOSÉ MÉNDEZFECHA: 10/10/2025
+ + +
+ Título: + Libro de prueba +
+ + +
+
+
Encuadernación
+
Encuadernación:Fresado
+
Formato: + 148x210 mm +
+
Páginas:132
+
Páginas Negro:100
+
Páginas Color:32
+ +
+
Interior
+
Tipo de impresión:Color Premium
+
Papel interior:Estucado Mate
+
Gramaje interior:115
-
-
Proyecto
-
-
- Título: - Libro de Ejemplo +
+
+
Cubierta
+
Tipo de cubierta:Tapa blanda
+
Solapas:
+
Tamaño solapas:80 mm
+
Impresión:Una cara
+
Papel cubierta:Estucado mate
+
Gramaje cubierta:250
+
Acabado:Plastificado Brillo 1/C
+
+ +
+
Servicios Extras
+ +
+
    +
  • + Ferro Digital + 0,00 € +
  • +
-
- Autor: - Autor/a -
-
- ISBN: - 978-1-2345-6789-0 -
-
- Formato: - - 150 × 210 mm - (personalizado) - -
-
- Páginas: - - - - -
-
- Tiradas: - 300, 500, 1000 + + +
+
Marcapáginas
+
Tamaño:50x210
+
Papel:Estucado mate 300 g
+
Impresión:Una cara
+
Plastificado:Brillo 1/C
-
+ - -
-
Detalle del presupuesto
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
DescripciónUdsPrecio unit.Dto.Importe
-
Impresión interior B/N 80 g
-
- 300 páginas · offset 80 g · tinta negra -
-
10002,1500-2.150,00
Servicios adicionales
-
Transporte
-
190,00-90,00
-
- - -
- + +
+ - - + + + + + - - - + + + + + + + + + - - - - - - - - -
Base imponible2.180,00TIRADAIMPRESIÓNIVATOTALUNIDAD
IVA (21%)457,80
100 uds.152,15€7,68€159,99€1,52€
Total2.637,80
Peso estimado120,00 kg
-
+ + - -
-
Observaciones
-
Presupuesto válido 30 días.
- -
Condiciones
-
- Entrega estimada 7-10 días laborables tras confirmación de artes finales. + +
+
+
Política de privacidad
+
+ Responsable: Impresión Imprime Libros - + CIF: B04998886 - + Email: info@imprimelibros.com · + Dirección: Calle José Picón, Nº 28 Local A, 28028, Madrid. + Sus datos se tratarán para la adecuada gestión fiscal y contable… +
+
+ - -
- - -
+
Página /