generación de factura pdf terminada

This commit is contained in:
2026-01-04 12:11:45 +01:00
parent 6bea279066
commit 400251ac3d
3 changed files with 150 additions and 5267 deletions

File diff suppressed because it is too large Load Diff

View File

@ -29,18 +29,16 @@
font-weight: 700; font-weight: 700;
} }
@page { @page {
size: A4; size: A4;
margin: 15mm 14mm 47mm 14mm;
/* Estos márgenes sustituyen a tu padding grande en .page-content */
margin: 15mm 14mm 50mm 14mm; /* bottom grande para el footer */
@bottom-center { @bottom-center {
content: element(pdfFooter); content: element(footer);
/* llamamos al elemento “footer” */
} }
}
}
html, html,
body { body {
@ -52,11 +50,12 @@ body {
.page-content { .page-content {
padding: 0; padding: 0;
/* ↑ deja 10mm extra para no pisar el footer */
box-sizing: border-box; box-sizing: border-box;
/* para que el padding no desborde */
} }
body.has-watermark { body.has-watermark {
background-image: none !important; background-image: none !important;
} }
@ -156,19 +155,22 @@ body.has-watermark {
/* Nueva banda verde PRESUPUESTO */ /* Nueva banda verde PRESUPUESTO */
.doc-banner { .doc-banner {
width: 100%; width: 100%;
background-color: #92b2a7 !important; /* ← tu verde corporativo */ background-color: #92b2a7 !important;
/* ← tu verde corporativo */
color: white; color: white;
text-align: center; text-align: center;
padding: 2mm 0; padding: 2mm 0;
margin-bottom: 4mm; margin-bottom: 4mm;
display: block; /* evita conflictos */ display: block;
/* evita conflictos */
} }
.banner-text { .banner-text {
font-family: "Open Sans", Arial, sans-serif !important; font-family: "Open Sans", Arial, sans-serif !important;
font-weight: 400; font-weight: 400;
font-size: 20pt; font-size: 20pt;
letter-spacing: 8px; /* ← configurable */ letter-spacing: 8px;
/* ← configurable */
} }
@ -212,8 +214,10 @@ body.has-watermark {
/* Specs 2 columnas */ /* Specs 2 columnas */
.specs-wrapper { .specs-wrapper {
width: 180mm; width: 180mm;
margin-left: 15mm; /* ← margen izquierdo real del A4 */ margin-left: 15mm;
margin-right: auto; /* opcional */ /* ← margen izquierdo real del A4 */
margin-right: auto;
/* opcional */
color: #5c5c5c; color: #5c5c5c;
font-size: 9pt; font-size: 9pt;
} }
@ -230,32 +234,44 @@ body.has-watermark {
table-layout: fixed; table-layout: fixed;
margin-bottom: 6mm; margin-bottom: 6mm;
} }
.specs .col { .specs .col {
display: table-cell; display: table-cell;
width: 50%; width: 50%;
padding-right: 6mm; padding-right: 6mm;
vertical-align: top; vertical-align: top;
} }
.specs .col:last-child { .specs .col:last-child {
padding-right: 0; padding-right: 0;
} }
/* Listas sin margen superior por defecto */ /* Listas sin margen superior por defecto */
ul, ol { ul,
margin-top: 0; ol {
margin-bottom: 0rem; /* si quieres algo abajo */ margin-top: 0;
padding-left: 1.25rem; /* sangría */ margin-bottom: 0rem;
/* si quieres algo abajo */
padding-left: 1.25rem;
/* sangría */
} }
/* Párrafos con menos margen inferior */ /* Párrafos con menos margen inferior */
p { p {
margin: 0 0 .5rem; margin: 0 0 .5rem;
} }
/* Si una lista va justo después de un texto o título, que no tenga hueco arriba */ /* Si una lista va justo después de un texto o título, que no tenga hueco arriba */
p + ul, p + ol, p+ul,
h1 + ul, h2 + ul, h3 + ul, h4 + ul, h5 + ul, h6 + ul, p+ol,
div + ul, div + ol { h1+ul,
h2+ul,
h3+ul,
h4+ul,
h5+ul,
h6+ul,
div+ul,
div+ol {
margin-top: 0; margin-top: 0;
} }
@ -336,6 +352,20 @@ div + ul, div + ol {
/* Footer */ /* Footer */
.footer {
position: fixed;
left: 14mm;
right: 14mm;
bottom: 18mm;
border-top: 1px solid var(--line);
padding-top: 4mm;
font-size: 7.5pt;
color: var(--muted);
z-index: 10;
/* sobre la marca */
background: transparent;
}
.footer .address { .footer .address {
display: table-cell; display: table-cell;
@ -357,70 +387,90 @@ div + ul, div + ol {
line-height: 1.25; line-height: 1.25;
} }
.page-count {
/* Caja a página completa SIN vw/vh y SIN z-index negativo */ margin-top: 2mm;
.watermark { text-align: right;
position: fixed; font-size: 9pt;
top: 0; left: 0; right: 0; bottom: 0; /* ocupa toda la HOJA */ color: var(--muted);
pointer-events: none;
z-index: 0; /* debajo del contenido */
} }
.watermark img { .page::after {
position: absolute; content: counter(page);
top: 245mm; /* baja/sube (7085%) */
left: 155mm; /* desplaza a la derecha si quieres */
transform: translate(-50%, -50%) rotate(-15deg);
width: 60%; /* tamaño grande, ya no hay recorte por márgenes */
max-width: none;
} }
.pages::after {
content: counter(pages);
}
.items-table { .items-table {
width: 100%; width: 100%;
border-color: #92b2a7 ; border-color: #92b2a7;
border-collapse: collapse; border-collapse: collapse;
} }
.items-table thead th { .items-table thead th {
background-color: #f3f6f9; background-color: #f3f6f9;
font-size: small; font-size: small;
} }
.items-table tbody td { .items-table tbody td {
font-size: small; font-size: small;
color: #000
}
.items-table td.desc{
font-family: "Open Sans" !important;
color: #5c5c5c !important;
font-size: 9pt !important;
line-height: 1.25 !important;
}
/* TODO lo que esté dentro (p, span, ul, li, b, i, etc. del HTML manual) */
.items-table td.desc *{
font-family: "Open Sans" !important;
color: #5c5c5c !important;
font-size: 9pt !important;
line-height: 1.25 !important;
}
.items-table thead {
display: table-header-group;
}
.items-table tfoot {
display: table-footer-group;
}
.items-table tr,
.items-table td,
.items-table th {
break-inside: avoid;
page-break-inside: avoid;
}
.page-number {
position: absolute;
bottom: -3mm;
right: 0;
font-size: small;
} }
.page-number .pn:before {
/* Saca el footer fuente fuera del papel (pero sigue existiendo para capturarlo) */ content: counter(page) " / " counter(pages);
.pdf-footer-source {
position: absolute;
left: 0;
top: -200mm; /* cualquier valor grande negativo */
width: 100%;
} }
/* El footer que se captura */ .pdf-footer-running {
#pdf-footer { position: running(footer);
position: running(pdfFooter); /* lo registra como elemento repetible */
}
/* Estilo del footer ya dentro del margin-box */
.footer {
border-top: 1px solid var(--line);
padding-top: 4mm;
padding-bottom: 6mm; /* aire para que no quede pegado abajo */
font-size: 7.5pt; font-size: 7.5pt;
color: var(--muted); color: var(--muted);
background: transparent; width: 100%;
} border-top: 1px solid var(--line);
padding-top: 4mm;
/* Numeración */ /* el resto de tus estilos internos (address, privacy, etc.) */
#pdf-footer .page-number {
margin-top: 2mm;
text-align: right;
font-size: 9pt;
}
#pdf-footer .page-number .pn::before {
content: " " counter(page) "/" counter(pages);
} }

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="es"> <html xmlns:th="http://www.thymeleaf.org " lang="es">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@ -10,14 +10,13 @@
<body class="has-watermark"> <body class="has-watermark">
<div class="watermark">
<img src="assets/images/logo-watermark.png" alt="Marca de agua" />
</div>
<!-- PIE --> <!-- PIE -->
<div class="pdf-footer-source"> <div class="pdf-footer-running">
<div class="footer" id="pdf-footer"> <div class="footer" id="pdf-footer">
<div class="privacy"> <div class="privacy">
<div class="pv-title" th:text="#{pdf.politica-privacidad}">Política de privacidad</div> <div class="pv-title" th:text="#{pdf.politica-privacidad}">Política de privacidad</div>
<div class="pv-text" th:text="#{pdf.politica-privacidad.responsable}">Responsable: Impresión Imprime Libros - <div class="pv-text" th:text="#{pdf.politica-privacidad.responsable}">Responsable: Impresión Imprime Libros -
@ -39,10 +38,10 @@
</div> </div>
<div class="page-number"> <div class="page-number">
<span th:text="#{pdf.page} ?: 'Página'">Página</span> <span th:text="#{pdf.page} ?: 'Página'">Página</span><span> </span><span class="pn"></span>
<span class="pn"></span>
</div> </div>
</div> </div>
</div> </div>
@ -121,15 +120,15 @@
<thead> <thead>
<tr> <tr>
<th class="w-75" th:text="#{pdf.factura.lineas.descripcion}">Descripción</th> <th class="w-75" th:text="#{pdf.factura.lineas.descripcion}">Descripción</th>
<th th:text="#{pdf.factura.lineas.base}">Base</th> <th class="num" th:text="#{pdf.factura.lineas.base}">Base</th>
<th th:text="#{pdf.factura.lineas.iva_4}">I.V.A. 4%</th> <th class="num" th:text="#{pdf.factura.lineas.iva_4}">I.V.A. 4%</th>
<th th:text="#{pdf.factura.lineas.iva_21}">I.V.A. 21%</th> <th class="num" th:text="#{pdf.factura.lineas.iva_21}">I.V.A. 21%</th>
<th th:text="#{pdf.factura.lineas.total}">Total</th> <th class="num" th:text="#{pdf.factura.lineas.total}">Total</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr th:each="lineaFactura : ${factura.lineas}"> <tr th:each="lineaFactura : ${factura.lineas}">
<td th:utext="${lineaFactura.descripcion}">Descripción de la línea</td> <td class="desc" th:utext="${lineaFactura.descripcion}">Descripción de la línea</td>
<td class="text-end" th:text="${#numbers.formatCurrency(lineaFactura.baseLinea)}">0.00</td> <td class="text-end" th:text="${#numbers.formatCurrency(lineaFactura.baseLinea)}">0.00</td>
<td class="text-end" th:text="${#numbers.formatCurrency(lineaFactura.iva4Linea)}">0.00</td> <td class="text-end" th:text="${#numbers.formatCurrency(lineaFactura.iva4Linea)}">0.00</td>
<td class="text-end" th:text="${#numbers.formatCurrency(lineaFactura.iva21Linea)}">0.00</td> <td class="text-end" th:text="${#numbers.formatCurrency(lineaFactura.iva21Linea)}">0.00</td>