|
|
|
@ -53,8 +53,17 @@
|
|
|
|
// ESTADO
|
|
|
|
// ESTADO
|
|
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
const TIPOLOGIAS = ['A1', 'P1', 'P2'];
|
|
|
|
const TIPOLOGIAS = ['P2D','P3D','P4D','B2D', 'B3D', 'B4D','A4D'];
|
|
|
|
const STEP_TIMEOUT = 1000;
|
|
|
|
const tiposInfo : Record<string,string> = {
|
|
|
|
|
|
|
|
'P2D' :'Vivienda de 2 Dormitorios',
|
|
|
|
|
|
|
|
'P3D' :'Vivienda de 3 Dormitorios',
|
|
|
|
|
|
|
|
'P4D' :'Vivienda de 4 Dormitorios',
|
|
|
|
|
|
|
|
'B2D' :'Planta Baja de 2 Dormitorios',
|
|
|
|
|
|
|
|
'B3D' :'Planta Baja de 3 Dormitorios',
|
|
|
|
|
|
|
|
'B4D' :'Planta Baja de 4 Dormitorios',
|
|
|
|
|
|
|
|
'A4D' :'Ático de 4 Dormitorios',
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let downloadState: DownloadState = $state('idle');
|
|
|
|
let downloadState: DownloadState = $state('idle');
|
|
|
|
let isVisible = $state(false);
|
|
|
|
let isVisible = $state(false);
|
|
|
|
@ -85,8 +94,12 @@
|
|
|
|
const pdfDoc = createPDFDocument();
|
|
|
|
const pdfDoc = createPDFDocument();
|
|
|
|
const config = getPDFConfig(pdfDoc);
|
|
|
|
const config = getPDFConfig(pdfDoc);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ PRECALCULAR TODAS LAS OPCIONES UNA SOLA VEZ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const allViewOptions = precalculateAllOptions(tipologia);
|
|
|
|
|
|
|
|
|
|
|
|
registerFonts(pdfDoc);
|
|
|
|
registerFonts(pdfDoc);
|
|
|
|
await generatePDFContent(pdfDoc, config, tipologia);
|
|
|
|
await generatePDFContent(pdfDoc, config, tipologia, allViewOptions);
|
|
|
|
|
|
|
|
|
|
|
|
downloadState = 'downloading';
|
|
|
|
downloadState = 'downloading';
|
|
|
|
pdfDoc.save(`OPENBOX_${tipologia}_${Date.now()}.pdf`);
|
|
|
|
pdfDoc.save(`OPENBOX_${tipologia}_${Date.now()}.pdf`);
|
|
|
|
@ -104,6 +117,34 @@
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
// PRECÁLCULO DE OPCIONES (una sola vez)
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface AllViewOptions {
|
|
|
|
|
|
|
|
general: Option[];
|
|
|
|
|
|
|
|
views: Map<string, ViewOptions>;
|
|
|
|
|
|
|
|
totalGeneral: number;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function precalculateAllOptions(tipologia: string): AllViewOptions {
|
|
|
|
|
|
|
|
const generalOptions = getGeneralOptions(tiposInfo[tipologia]);
|
|
|
|
|
|
|
|
const viewOptionsMap = new Map<string, ViewOptions>();
|
|
|
|
|
|
|
|
let totalGeneral = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
views.forEach((view) => {
|
|
|
|
|
|
|
|
const viewOptions = getViewOptions(view);
|
|
|
|
|
|
|
|
viewOptionsMap.set(view.id, viewOptions);
|
|
|
|
|
|
|
|
totalGeneral += viewOptions.total;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
general: generalOptions,
|
|
|
|
|
|
|
|
views: viewOptionsMap,
|
|
|
|
|
|
|
|
totalGeneral
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
|
|
// CREACIÓN DE PDF
|
|
|
|
// CREACIÓN DE PDF
|
|
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
|
|
@ -144,11 +185,11 @@
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function generatePDFContent(pdfDoc: jsPDF, config: PDFConfig, tipologia: string) {
|
|
|
|
async function generatePDFContent(pdfDoc: jsPDF, config: PDFConfig, tipologia: string, allOptions: AllViewOptions) {
|
|
|
|
addCoverPage(pdfDoc, config, tipologia);
|
|
|
|
addCoverPage(pdfDoc, config, tipologia);
|
|
|
|
addPresentationPage(pdfDoc, config);
|
|
|
|
addPresentationPage(pdfDoc, config);
|
|
|
|
addViewPages(pdfDoc, config);
|
|
|
|
addViewPages(pdfDoc, config, allOptions);
|
|
|
|
addSummaryPage(pdfDoc, config); // ✅ NUEVA PÁGINA
|
|
|
|
addSummaryPage(pdfDoc, config, allOptions);
|
|
|
|
addFinalPage(pdfDoc, config);
|
|
|
|
addFinalPage(pdfDoc, config);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -204,9 +245,9 @@
|
|
|
|
pdfDoc.addPage();
|
|
|
|
pdfDoc.addPage();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function addViewPages(pdfDoc: jsPDF, config: PDFConfig) {
|
|
|
|
function addViewPages(pdfDoc: jsPDF, config: PDFConfig, allOptions: AllViewOptions) {
|
|
|
|
const imageRatio = 1; // 1500x1500
|
|
|
|
const imageRatio = 1; // 1500x1500
|
|
|
|
const pageRatio = config.pageWidth / config.pageHeight;
|
|
|
|
//const pageRatio = config.pageWidth / config.pageHeight;
|
|
|
|
|
|
|
|
|
|
|
|
views.forEach((view, index) => {
|
|
|
|
views.forEach((view, index) => {
|
|
|
|
const viewImage = images[getViewImage(view.id)];
|
|
|
|
const viewImage = images[getViewImage(view.id)];
|
|
|
|
@ -225,14 +266,17 @@
|
|
|
|
const textStartX = config.margin + renderWidth + 10;
|
|
|
|
const textStartX = config.margin + renderWidth + 10;
|
|
|
|
const textMaxWidth = config.pageWidth - textStartX - config.margin;
|
|
|
|
const textMaxWidth = config.pageWidth - textStartX - config.margin;
|
|
|
|
|
|
|
|
|
|
|
|
addViewContent(pdfDoc, config, view, textStartX, textMaxWidth);
|
|
|
|
const viewOptions = allOptions.views.get(view.id);
|
|
|
|
|
|
|
|
if (viewOptions) {
|
|
|
|
|
|
|
|
addViewContent(pdfDoc, config, view, textStartX, textMaxWidth, viewOptions);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Nueva página (excepto la última)
|
|
|
|
// Nueva página (excepto la última)
|
|
|
|
if (index < views.length - 1) pdfDoc.addPage();
|
|
|
|
if (index < views.length - 1) pdfDoc.addPage();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function addViewContent(pdfDoc: jsPDF, config: PDFConfig, view: View, x: number, maxWidth: number) {
|
|
|
|
function addViewContent(pdfDoc: jsPDF, config: PDFConfig, view: View, x: number, maxWidth: number, viewOptions: ViewOptions) {
|
|
|
|
let cursorY = config.margin + 5;
|
|
|
|
let cursorY = config.margin + 5;
|
|
|
|
|
|
|
|
|
|
|
|
// Título de la vista
|
|
|
|
// Título de la vista
|
|
|
|
@ -247,8 +291,7 @@
|
|
|
|
cursorY = addMultilineText(pdfDoc, view.text, x, cursorY, maxWidth, config.fonts.light, 12, 5);
|
|
|
|
cursorY = addMultilineText(pdfDoc, view.text, x, cursorY, maxWidth, config.fonts.light, 12, 5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Opciones
|
|
|
|
// Opciones (usar las precalculadas)
|
|
|
|
const viewOptions = getViewOptions(view);
|
|
|
|
|
|
|
|
if (viewOptions.options.length > 0) {
|
|
|
|
if (viewOptions.options.length > 0) {
|
|
|
|
cursorY = 100;
|
|
|
|
cursorY = 100;
|
|
|
|
addOptionsSection(pdfDoc, config, viewOptions, x, cursorY, maxWidth);
|
|
|
|
addOptionsSection(pdfDoc, config, viewOptions, x, cursorY, maxWidth);
|
|
|
|
@ -267,7 +310,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
// Estilos de texto
|
|
|
|
// Estilos de texto
|
|
|
|
const styles: TextStyles = {
|
|
|
|
const styles: TextStyles = {
|
|
|
|
group: { font: config.fonts.regular, size: 9 },
|
|
|
|
group: { font: config.fonts.regular, size: 11 },
|
|
|
|
option: { font: config.fonts.bold, size: 11 },
|
|
|
|
option: { font: config.fonts.bold, size: 11 },
|
|
|
|
price: { font: config.fonts.regular, size: 11 }
|
|
|
|
price: { font: config.fonts.regular, size: 11 }
|
|
|
|
};
|
|
|
|
};
|
|
|
|
@ -279,7 +322,8 @@
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function addSummaryPage(pdfDoc: jsPDF, config: PDFConfig) {
|
|
|
|
|
|
|
|
|
|
|
|
function addSummaryPage(pdfDoc: jsPDF, config: PDFConfig, allOptions: AllViewOptions) {
|
|
|
|
pdfDoc.addPage();
|
|
|
|
pdfDoc.addPage();
|
|
|
|
|
|
|
|
|
|
|
|
const COLS = 5;
|
|
|
|
const COLS = 5;
|
|
|
|
@ -287,53 +331,63 @@
|
|
|
|
const COL_WIDTH = (config.pageWidth - 2 * MARGIN) / COLS;
|
|
|
|
const COL_WIDTH = (config.pageWidth - 2 * MARGIN) / COLS;
|
|
|
|
const THUMB_SIZE = COL_WIDTH - 4;
|
|
|
|
const THUMB_SIZE = COL_WIDTH - 4;
|
|
|
|
const ICON_SIZE = 3;
|
|
|
|
const ICON_SIZE = 3;
|
|
|
|
const ROW_SPACING = 10;
|
|
|
|
//const ROW_SPACING = 10;
|
|
|
|
|
|
|
|
|
|
|
|
let startY = MARGIN;
|
|
|
|
let startY = MARGIN+10;
|
|
|
|
|
|
|
|
|
|
|
|
// ===== TÍTULO DE LA PÁGINA =====
|
|
|
|
// ===== TÍTULO DE LA PÁGINA =====
|
|
|
|
pdfDoc.setFontSize(16);
|
|
|
|
pdfDoc.setFontSize(16);
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setTextColor(30, 30, 30);
|
|
|
|
pdfDoc.setTextColor(80, 80, 80);
|
|
|
|
pdfDoc.text('RESUMEN DE ACABADOS', config.pageWidth / 2, startY, { align: 'center' });
|
|
|
|
pdfDoc.text('Resumen de la configuración seleccionada'.toUpperCase(), config.pageWidth / 2, startY, { align: 'center' });
|
|
|
|
startY += 12;
|
|
|
|
startY += 12;
|
|
|
|
|
|
|
|
|
|
|
|
// ===== FILA 1: 5 VISTAS =====
|
|
|
|
// ===== FILA 1: 5 VISTAS =====
|
|
|
|
const row1Y = startY;
|
|
|
|
const row1Y = startY-5;
|
|
|
|
views.slice(0, 5).forEach((view, index) => {
|
|
|
|
views.slice(0, 5).forEach((view, index) => {
|
|
|
|
const x = MARGIN + index * COL_WIDTH;
|
|
|
|
const x = MARGIN + index * COL_WIDTH;
|
|
|
|
renderViewColumnWithHeader(pdfDoc, config, view, x, row1Y, THUMB_SIZE, THUMB_SIZE, ICON_SIZE);
|
|
|
|
const viewOptions = allOptions.views.get(view.id);
|
|
|
|
|
|
|
|
if (viewOptions) {
|
|
|
|
|
|
|
|
renderViewColumnWithHeader(pdfDoc, config, view, viewOptions, x, row1Y, COL_WIDTH, THUMB_SIZE, ICON_SIZE);
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ===== FILA 2: GENERALES (izq) + ESPACIO + TOTAL (der) =====
|
|
|
|
// ===== FILA 2: GENERALES (izq) + ESPACIO + TOTAL (der) =====
|
|
|
|
const row2Y = row1Y + 120; // Ajustar según altura de fila 1
|
|
|
|
const row2Y = row1Y + 120;
|
|
|
|
|
|
|
|
|
|
|
|
// Columna 1: Acabados Generales (izquierda)
|
|
|
|
// Columna 1: Acabados Generales (izquierda)
|
|
|
|
const generalX = MARGIN;
|
|
|
|
//const generalX = MARGIN;
|
|
|
|
renderGeneralColumnWithHeader(pdfDoc, config, generalX, row2Y, COL_WIDTH, THUMB_SIZE, ICON_SIZE);
|
|
|
|
//renderGeneralColumnWithHeader(pdfDoc, config, allOptions.general, generalX, row2Y, COL_WIDTH, THUMB_SIZE, ICON_SIZE);
|
|
|
|
|
|
|
|
renderGeneralColumnWithHeader(pdfDoc, config, allOptions.general, MARGIN, row2Y, COL_WIDTH, THUMB_SIZE, ICON_SIZE);
|
|
|
|
|
|
|
|
|
|
|
|
// Columnas 2-4: Vacías (espacio)
|
|
|
|
// Columnas 2-4: Vacías (espacio)
|
|
|
|
|
|
|
|
renderFirmaColumnWithHeader (pdfDoc, config, 70, row2Y, 130)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Columna 5: Total (derecha)
|
|
|
|
// Columna 5: Total (derecha)
|
|
|
|
const totalX = MARGIN + 4 * COL_WIDTH;
|
|
|
|
const totalX = MARGIN + 4 * COL_WIDTH;
|
|
|
|
renderTotalColumnWithHeader(pdfDoc, config, totalX, row2Y, COL_WIDTH);
|
|
|
|
renderTotalColumnWithHeader(pdfDoc, config, allOptions, totalX-20, row2Y, COL_WIDTH+16);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function renderViewColumnWithHeader(
|
|
|
|
function renderViewColumnWithHeader(
|
|
|
|
pdfDoc: jsPDF,
|
|
|
|
pdfDoc: jsPDF,
|
|
|
|
config: PDFConfig,
|
|
|
|
config: PDFConfig,
|
|
|
|
view: View,
|
|
|
|
view: View,
|
|
|
|
|
|
|
|
viewOptions: ViewOptions,
|
|
|
|
x: number,
|
|
|
|
x: number,
|
|
|
|
y: number,
|
|
|
|
y: number,
|
|
|
|
colWidth: number,
|
|
|
|
colWidth: number,
|
|
|
|
thumbSize: number,
|
|
|
|
thumbSize: number,
|
|
|
|
iconSize: number
|
|
|
|
iconSize: number
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
let cursorY = y;
|
|
|
|
let cursorY = y+3;
|
|
|
|
|
|
|
|
|
|
|
|
// Header de columna
|
|
|
|
// Header de columna
|
|
|
|
pdfDoc.setFillColor(46, 125, 50); // Verde
|
|
|
|
pdfDoc.setFillColor(46, 125, 50);
|
|
|
|
pdfDoc.rect(x+2, cursorY - 5, colWidth, 8, 'F');
|
|
|
|
pdfDoc.rect(x+2, cursorY - 5, thumbSize, 8, 'F');
|
|
|
|
|
|
|
|
|
|
|
|
pdfDoc.setFontSize(8);
|
|
|
|
pdfDoc.setFontSize(8);
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
@ -352,37 +406,61 @@
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cursorY += thumbSize + 5;
|
|
|
|
cursorY += thumbSize + 5;
|
|
|
|
|
|
|
|
|
|
|
|
// Opciones de la vista
|
|
|
|
// ✅ USAR OPCIONES PRECALCULADAS
|
|
|
|
const viewOptions = getViewOptions(view);
|
|
|
|
|
|
|
|
viewOptions.options.forEach((option) => {
|
|
|
|
viewOptions.options.forEach((option) => {
|
|
|
|
cursorY = renderCompactOption(pdfDoc, config, option, x + 2, cursorY, colWidth - 4, iconSize);
|
|
|
|
cursorY = renderCompactOption(pdfDoc, config, option, x + 2, cursorY, colWidth - 4, iconSize);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Total de la vista (si tiene opciones con precio)
|
|
|
|
// Total de la vista
|
|
|
|
if (viewOptions.total > 0) {
|
|
|
|
if (viewOptions.total > 0) {
|
|
|
|
cursorY += 3;
|
|
|
|
cursorY += 3;
|
|
|
|
|
|
|
|
|
|
|
|
// Línea separadora
|
|
|
|
|
|
|
|
pdfDoc.setDrawColor(200, 200, 200);
|
|
|
|
pdfDoc.setDrawColor(200, 200, 200);
|
|
|
|
pdfDoc.setLineWidth(0.3);
|
|
|
|
pdfDoc.setLineWidth(0.3);
|
|
|
|
pdfDoc.line(x + 2, cursorY, x + colWidth - 2, cursorY);
|
|
|
|
pdfDoc.line(x + 2, cursorY, x + colWidth - 2, cursorY);
|
|
|
|
cursorY += 4;
|
|
|
|
cursorY += 4;
|
|
|
|
|
|
|
|
|
|
|
|
// Total
|
|
|
|
|
|
|
|
pdfDoc.setFontSize(9);
|
|
|
|
pdfDoc.setFontSize(9);
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setTextColor(46, 125, 50);
|
|
|
|
pdfDoc.setTextColor(46, 125, 50);
|
|
|
|
pdfDoc.text('TOTAL', x + 2, cursorY);
|
|
|
|
pdfDoc.text('TOTAL', x + 2, cursorY);
|
|
|
|
pdfDoc.text(formatPrice(viewOptions.total), x + colWidth - 4, cursorY, { align: 'right' });
|
|
|
|
pdfDoc.text(formatPrice(viewOptions.total), x + colWidth - 4, cursorY, { align: 'right' });
|
|
|
|
|
|
|
|
|
|
|
|
pdfDoc.setLineWidth(0.2); // Reset
|
|
|
|
pdfDoc.setLineWidth(0.2);
|
|
|
|
pdfDoc.setTextColor(0, 0, 0);
|
|
|
|
pdfDoc.setTextColor(0, 0, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function renderFirmaColumnWithHeader(
|
|
|
|
|
|
|
|
pdfDoc: jsPDF,
|
|
|
|
|
|
|
|
config: PDFConfig,
|
|
|
|
|
|
|
|
x: number,
|
|
|
|
|
|
|
|
y: number,
|
|
|
|
|
|
|
|
colWidth: number,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
let cursorY = y;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Header de columna
|
|
|
|
|
|
|
|
pdfDoc.setFillColor(157, 186, 113);
|
|
|
|
|
|
|
|
pdfDoc.rect(x+2, cursorY - 5, colWidth, 50, 'S');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pdfDoc.setFontSize(12);
|
|
|
|
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
|
|
|
|
pdfDoc.setTextColor(80,80,80);
|
|
|
|
|
|
|
|
const titleLines = pdfDoc.splitTextToSize('FIRMA CLIENTE', colWidth - 4);
|
|
|
|
|
|
|
|
titleLines.forEach((line: string, i: number) => {
|
|
|
|
|
|
|
|
pdfDoc.text(line, x + colWidth / 2, cursorY + i * 3, { align: 'center' });
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function renderGeneralColumnWithHeader(
|
|
|
|
function renderGeneralColumnWithHeader(
|
|
|
|
pdfDoc: jsPDF,
|
|
|
|
pdfDoc: jsPDF,
|
|
|
|
config: PDFConfig,
|
|
|
|
config: PDFConfig,
|
|
|
|
|
|
|
|
generalOptions: Option[],
|
|
|
|
x: number,
|
|
|
|
x: number,
|
|
|
|
y: number,
|
|
|
|
y: number,
|
|
|
|
colWidth: number,
|
|
|
|
colWidth: number,
|
|
|
|
@ -392,40 +470,42 @@
|
|
|
|
let cursorY = y;
|
|
|
|
let cursorY = y;
|
|
|
|
|
|
|
|
|
|
|
|
// Header de columna
|
|
|
|
// Header de columna
|
|
|
|
pdfDoc.setFillColor(139, 92, 246); // Púrpura para generales
|
|
|
|
pdfDoc.setFillColor(157, 186, 113);
|
|
|
|
pdfDoc.rect(x, cursorY - 5, colWidth, 8, 'F');
|
|
|
|
pdfDoc.rect(x+2, cursorY - 5, thumbSize, 8, 'F');
|
|
|
|
|
|
|
|
|
|
|
|
pdfDoc.setFontSize(8);
|
|
|
|
pdfDoc.setFontSize(8);
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setTextColor(255, 255, 255);
|
|
|
|
pdfDoc.setTextColor(80,80,80);
|
|
|
|
const titleLines = pdfDoc.splitTextToSize('ACABADOS GENERALES', colWidth - 4);
|
|
|
|
const titleLines = pdfDoc.splitTextToSize('ACABADOS GENERALES', colWidth - 4);
|
|
|
|
titleLines.forEach((line: string, i: number) => {
|
|
|
|
titleLines.forEach((line: string, i: number) => {
|
|
|
|
pdfDoc.text(line, x + colWidth / 2, cursorY + i * 3, { align: 'center' });
|
|
|
|
pdfDoc.text(line, x + colWidth / 2, cursorY + i * 3, { align: 'center' });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
cursorY += 8;
|
|
|
|
cursorY += 10;
|
|
|
|
|
|
|
|
|
|
|
|
// Icono o placeholder
|
|
|
|
// Icono o placeholder
|
|
|
|
pdfDoc.setFillColor(240, 240, 240);
|
|
|
|
/*
|
|
|
|
|
|
|
|
pdfDoc.setFillColor(157, 186, 113);
|
|
|
|
pdfDoc.rect(x + 2, cursorY, thumbSize, thumbSize, 'F');
|
|
|
|
pdfDoc.rect(x + 2, cursorY, thumbSize, thumbSize, 'F');
|
|
|
|
|
|
|
|
|
|
|
|
// Icono central (opcional)
|
|
|
|
|
|
|
|
pdfDoc.setFontSize(24);
|
|
|
|
pdfDoc.setFontSize(24);
|
|
|
|
pdfDoc.setTextColor(180, 180, 180);
|
|
|
|
pdfDoc.setTextColor(80, 80, 80);
|
|
|
|
pdfDoc.text('🏠', x + colWidth / 2, cursorY + thumbSize / 2 + 4, { align: 'center' });
|
|
|
|
pdfDoc.text('🏠', x + colWidth / 2, cursorY + thumbSize / 2 + 4, { align: 'center' });
|
|
|
|
|
|
|
|
|
|
|
|
cursorY += thumbSize + 5;
|
|
|
|
cursorY += thumbSize + 5;
|
|
|
|
|
|
|
|
|
|
|
|
// Opciones generales
|
|
|
|
*/
|
|
|
|
const generalOptions = getGeneralOptions();
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ USAR OPCIONES PRECALCULADAS
|
|
|
|
generalOptions.forEach((option) => {
|
|
|
|
generalOptions.forEach((option) => {
|
|
|
|
cursorY = renderCompactOption(pdfDoc, config, option, x + 2, cursorY, colWidth - 4, iconSize);
|
|
|
|
cursorY = renderCompactOption(pdfDoc, config, option, x + 2, cursorY, thumbSize, iconSize);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderTotalColumnWithHeader(
|
|
|
|
function renderTotalColumnWithHeader(
|
|
|
|
pdfDoc: jsPDF,
|
|
|
|
pdfDoc: jsPDF,
|
|
|
|
config: PDFConfig,
|
|
|
|
config: PDFConfig,
|
|
|
|
|
|
|
|
allOptions: AllViewOptions,
|
|
|
|
x: number,
|
|
|
|
x: number,
|
|
|
|
y: number,
|
|
|
|
y: number,
|
|
|
|
colWidth: number
|
|
|
|
colWidth: number
|
|
|
|
@ -433,70 +513,68 @@
|
|
|
|
let cursorY = y;
|
|
|
|
let cursorY = y;
|
|
|
|
|
|
|
|
|
|
|
|
// Header de columna
|
|
|
|
// Header de columna
|
|
|
|
pdfDoc.setFillColor(46, 125, 50); // Verde
|
|
|
|
pdfDoc.setFillColor(47,101,53);
|
|
|
|
pdfDoc.rect(x, cursorY - 5, colWidth, 8, 'F');
|
|
|
|
pdfDoc.rect(x, cursorY - 5, colWidth, 8, 'F');
|
|
|
|
|
|
|
|
|
|
|
|
pdfDoc.setFontSize(8);
|
|
|
|
pdfDoc.setFontSize(10);
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setTextColor(255, 255, 255);
|
|
|
|
pdfDoc.setTextColor(255, 255, 255);
|
|
|
|
pdfDoc.text('TOTALES', x + colWidth / 2, cursorY, { align: 'center' });
|
|
|
|
pdfDoc.text('TOTALES ESTANCIAS', x + colWidth / 2, cursorY, { align: 'center' });
|
|
|
|
|
|
|
|
|
|
|
|
cursorY += 15;
|
|
|
|
cursorY += 15;
|
|
|
|
|
|
|
|
|
|
|
|
// Calcular totales
|
|
|
|
// ✅ USAR TOTALES PRECALCULADOS
|
|
|
|
let totalGeneral = 0;
|
|
|
|
|
|
|
|
const viewTotals: { view: string; total: number }[] = [];
|
|
|
|
const viewTotals: { view: string; total: number }[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
views.forEach((view) => {
|
|
|
|
views.forEach((view) => {
|
|
|
|
const vo = getViewOptions(view);
|
|
|
|
const vo = allOptions.views.get(view.id);
|
|
|
|
if (vo.total > 0) {
|
|
|
|
if (vo && vo.total > 0) {
|
|
|
|
totalGeneral += vo.total;
|
|
|
|
|
|
|
|
viewTotals.push({ view: view.name.toUpperCase(), total: vo.total });
|
|
|
|
viewTotals.push({ view: view.name.toUpperCase(), total: vo.total });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Subtotales por vista
|
|
|
|
// Subtotales por vista
|
|
|
|
pdfDoc.setFontSize(9);
|
|
|
|
pdfDoc.setFontSize(10);
|
|
|
|
pdfDoc.setFont(config.fonts.regular, "normal");
|
|
|
|
pdfDoc.setFont(config.fonts.regular, "normal");
|
|
|
|
pdfDoc.setTextColor(80, 80, 80);
|
|
|
|
pdfDoc.setTextColor(80, 80, 80);
|
|
|
|
|
|
|
|
|
|
|
|
viewTotals.forEach((item) => {
|
|
|
|
viewTotals.forEach((item) => {
|
|
|
|
// Nombre de vista
|
|
|
|
|
|
|
|
const nameLines = pdfDoc.splitTextToSize(item.view, colWidth * 0.6);
|
|
|
|
const nameLines = pdfDoc.splitTextToSize(item.view, colWidth * 0.6);
|
|
|
|
nameLines.forEach((line: string, i: number) => {
|
|
|
|
nameLines.forEach((line: string, i: number) => {
|
|
|
|
pdfDoc.text(line, x + 2, cursorY + i * 3);
|
|
|
|
pdfDoc.text(line, x + 2, cursorY + i * 3);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Precio alineado a la derecha
|
|
|
|
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.text(formatPrice(item.total), x + colWidth - 4, cursorY, { align: 'right' });
|
|
|
|
pdfDoc.text(formatPrice(item.total), x + colWidth - 4, cursorY, { align: 'right' });
|
|
|
|
pdfDoc.setFont(config.fonts.regular, "normal");
|
|
|
|
pdfDoc.setFont(config.fonts.regular, "normal");
|
|
|
|
|
|
|
|
|
|
|
|
cursorY += Math.max(nameLines.length * 3, 4) + 1;
|
|
|
|
cursorY += Math.max(nameLines.length * 3, 6) + 1;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Línea separadora
|
|
|
|
// Línea separadora
|
|
|
|
cursorY += 3;
|
|
|
|
cursorY += 5;
|
|
|
|
|
|
|
|
/*
|
|
|
|
pdfDoc.setDrawColor(46, 125, 50);
|
|
|
|
pdfDoc.setDrawColor(46, 125, 50);
|
|
|
|
pdfDoc.setLineWidth(0.5);
|
|
|
|
pdfDoc.setLineWidth(0.5);
|
|
|
|
pdfDoc.line(x + 2, cursorY, x + colWidth - 2, cursorY);
|
|
|
|
pdfDoc.line(x + 2, cursorY, x + colWidth - 2, cursorY);
|
|
|
|
cursorY += 6;
|
|
|
|
cursorY += 6;
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
// Total general
|
|
|
|
// Total general
|
|
|
|
pdfDoc.setFillColor(46, 125, 50);
|
|
|
|
pdfDoc.setFillColor(240, 240, 240);
|
|
|
|
pdfDoc.rect(x + 1, cursorY - 4, colWidth - 2, 10, 'F');
|
|
|
|
pdfDoc.rect(x + 1, cursorY - 4, colWidth - 2, 10, 'F');
|
|
|
|
|
|
|
|
|
|
|
|
pdfDoc.setFontSize(10);
|
|
|
|
pdfDoc.setFontSize(10);
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setFont(config.fonts.bold, "normal");
|
|
|
|
pdfDoc.setTextColor(255, 255, 255);
|
|
|
|
pdfDoc.setTextColor(25, 25, 25);
|
|
|
|
pdfDoc.text('TOTAL', x + 4, cursorY + 2);
|
|
|
|
pdfDoc.text('TOTAL', x + 4, cursorY + 2);
|
|
|
|
pdfDoc.setFontSize(12);
|
|
|
|
pdfDoc.setFontSize(12);
|
|
|
|
pdfDoc.text(formatPrice(totalGeneral), x + colWidth - 4, cursorY + 2, { align: 'right' });
|
|
|
|
pdfDoc.text(formatPrice(allOptions.totalGeneral), x + colWidth - 4, cursorY + 2, { align: 'right' });
|
|
|
|
|
|
|
|
|
|
|
|
pdfDoc.setLineWidth(0.2); // Reset
|
|
|
|
pdfDoc.setLineWidth(0.2);
|
|
|
|
pdfDoc.setTextColor(0, 0, 0);
|
|
|
|
pdfDoc.setTextColor(0, 0, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function renderCompactOption(
|
|
|
|
function renderCompactOption(
|
|
|
|
pdfDoc: jsPDF,
|
|
|
|
pdfDoc: jsPDF,
|
|
|
|
config: PDFConfig,
|
|
|
|
config: PDFConfig,
|
|
|
|
@ -577,9 +655,19 @@
|
|
|
|
return cursorY;
|
|
|
|
return cursorY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getGeneralOptions(): Option[] {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getGeneralOptions(tipologia: string): Option[] {
|
|
|
|
const options: Option[] = [];
|
|
|
|
const options: Option[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
options.push({
|
|
|
|
|
|
|
|
nombreGrupo: 'TIPO DE VIVIENDA',
|
|
|
|
|
|
|
|
nombre: tipologia,
|
|
|
|
|
|
|
|
consultar: false,
|
|
|
|
|
|
|
|
precio: null,
|
|
|
|
|
|
|
|
warning: false,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
Object.entries(dataConfig.options.general).forEach(([groupId, group]) => {
|
|
|
|
Object.entries(dataConfig.options.general).forEach(([groupId, group]) => {
|
|
|
|
const currentOptionId = appState.getCurrentOption('general', groupId);
|
|
|
|
const currentOptionId = appState.getCurrentOption('general', groupId);
|
|
|
|
if (!currentOptionId) return;
|
|
|
|
if (!currentOptionId) return;
|
|
|
|
@ -672,15 +760,14 @@
|
|
|
|
let cursorY = y;
|
|
|
|
let cursorY = y;
|
|
|
|
|
|
|
|
|
|
|
|
// Línea 1: Nombre del grupo
|
|
|
|
// Línea 1: Nombre del grupo
|
|
|
|
|
|
|
|
const cY = cursorY;
|
|
|
|
cursorY = renderGroupName(pdfDoc, option.nombreGrupo, x, cursorY, styles.group);
|
|
|
|
cursorY = renderGroupName(pdfDoc, option.nombreGrupo, x, cursorY, styles.group);
|
|
|
|
|
|
|
|
renderPrice(pdfDoc, option, x, cY, maxWidth, styles.price);
|
|
|
|
|
|
|
|
|
|
|
|
// Línea 2: Nombre de la opción + icono
|
|
|
|
// Línea 2: Nombre de la opción + icono
|
|
|
|
cursorY = renderOptionName(pdfDoc, option.nombre, x, cursorY, maxWidth, styles.option, icon);
|
|
|
|
cursorY = renderOptionName(pdfDoc, option.nombre, x, cursorY, maxWidth, styles.option, icon);
|
|
|
|
|
|
|
|
|
|
|
|
// Línea 3: Precio
|
|
|
|
// Línea 3: Warning (si existe)
|
|
|
|
cursorY = renderPrice(pdfDoc, option, x, cursorY, maxWidth, styles.price);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Línea 4: Warning (si existe)
|
|
|
|
|
|
|
|
if (option.warningMessage) {
|
|
|
|
if (option.warningMessage) {
|
|
|
|
cursorY = renderWarning(pdfDoc, option.warningMessage, x, cursorY, maxWidth, styles.group.font);
|
|
|
|
cursorY = renderWarning(pdfDoc, option.warningMessage, x, cursorY, maxWidth, styles.group.font);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -784,13 +871,13 @@
|
|
|
|
let cursorY = y + 1;
|
|
|
|
let cursorY = y + 1;
|
|
|
|
|
|
|
|
|
|
|
|
pdfDoc.setFont(font, "normal");
|
|
|
|
pdfDoc.setFont(font, "normal");
|
|
|
|
pdfDoc.setFontSize(8);
|
|
|
|
pdfDoc.setFontSize(10);
|
|
|
|
pdfDoc.setTextColor(200, 100, 0);
|
|
|
|
pdfDoc.setTextColor(200, 100, 0);
|
|
|
|
|
|
|
|
|
|
|
|
const lines = pdfDoc.splitTextToSize(message, maxWidth - 5);
|
|
|
|
const lines = pdfDoc.splitTextToSize(message, maxWidth - 5);
|
|
|
|
lines.forEach((line: string) => {
|
|
|
|
lines.forEach((line: string) => {
|
|
|
|
pdfDoc.text('⚠️ ' + line, x, cursorY);
|
|
|
|
pdfDoc.text(line, x, cursorY);
|
|
|
|
cursorY += 3;
|
|
|
|
cursorY += 4;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
pdfDoc.setTextColor(0, 0, 0); // Reset
|
|
|
|
pdfDoc.setTextColor(0, 0, 0); // Reset
|
|
|
|
@ -853,6 +940,7 @@
|
|
|
|
{message}
|
|
|
|
{message}
|
|
|
|
{downloadState}
|
|
|
|
{downloadState}
|
|
|
|
tipologias={TIPOLOGIAS}
|
|
|
|
tipologias={TIPOLOGIAS}
|
|
|
|
|
|
|
|
tiposTextos={Object.values(tiposInfo)}
|
|
|
|
{logoCompany}
|
|
|
|
{logoCompany}
|
|
|
|
{logoProject}
|
|
|
|
{logoProject}
|
|
|
|
text="Descarga tu Selección"
|
|
|
|
text="Descarga tu Selección"
|
|
|
|
|