From 9fc184279106fe879a6a39957beab0f6aaf97f28 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Mon, 24 Nov 2025 17:56:41 -0600 Subject: [PATCH] =?UTF-8?q?PDF:=20Layout=20horizontal=20compacto=20para=20?= =?UTF-8?q?categor=C3=ADas=20padre-hijo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Categorías reestructuradas con jerarquía explícita (padres con hijos) - Hijos se muestran horizontalmente después del padre con wrap automático - Fragancia-Aroma y Sabores reducidos a 28mm cada una (más compacto) - Labels abreviados para subcategorías (F.Deshidr., Az.Morena, etc.) - Ajustadas posiciones Y de todas las secciones --- nuxt4/app/utils/pdf/pdfFormulario.ts | 156 ++++++++++++++++----------- nuxt4/app/utils/pdf/pdfLayout.ts | 94 ++++++++++------ 2 files changed, 154 insertions(+), 96 deletions(-) diff --git a/nuxt4/app/utils/pdf/pdfFormulario.ts b/nuxt4/app/utils/pdf/pdfFormulario.ts index a832cec..3ec7f75 100644 --- a/nuxt4/app/utils/pdf/pdfFormulario.ts +++ b/nuxt4/app/utils/pdf/pdfFormulario.ts @@ -10,7 +10,7 @@ import { PDF_CONFIG, calcularYInicio, PARAMETROS_INTENSIDAD, - CATEGORIAS_PDF, + CATEGORIAS_JERARQUICAS, SENSACIONES_PDF, GUSTOS_PDF, DEFECTOS_PDF, @@ -46,15 +46,16 @@ export function renderizarFormulario( PDF_CONFIG.lineWidth.thick ) - // Renderizar cada sección con posiciones ajustadas para formulario de 130mm + // Renderizar cada sección con posiciones ajustadas para layout compacto + // Fragancia-Aroma y Sabores: 28mm cada una (layout horizontal) renderizarEncabezado(doc, xBase, yBase, muestra) renderizarTablaIntensidades(doc, xBase, yBase + 14, muestra) - renderizarSeccionFraganciaAroma(doc, xBase + 55, yBase + 14, muestra) - renderizarSeccionSabores(doc, xBase + 55, yBase + 49, muestra) // Sabores ahora tiene 34mm igual que Fragancia-Aroma - renderizarSeccionSensacionGustos(doc, xBase, yBase + 84, muestra) - renderizarSeccionTazasDefectos(doc, xBase, yBase + 100, muestra) - renderizarSeccionOtrasNotas(doc, xBase, yBase + 116, muestra) - renderizarSeccionSubTotal(doc, xBase, yBase + 124, muestra) + renderizarSeccionFraganciaAroma(doc, xBase + 55, yBase + 14, muestra) // 28mm + renderizarSeccionSabores(doc, xBase + 55, yBase + 42, muestra) // 28mm (14+28=42) + renderizarSeccionSensacionGustos(doc, xBase, yBase + 70, muestra) // Después de Sabores (42+28=70) + renderizarSeccionTazasDefectos(doc, xBase, yBase + 86, muestra) // 70+16=86 + renderizarSeccionOtrasNotas(doc, xBase, yBase + 102, muestra) // 86+16=102 + renderizarSeccionSubTotal(doc, xBase, yBase + 112, muestra) // 102+10=112 } /** @@ -175,7 +176,7 @@ function renderizarTablaIntensidades( } /** - * Renderiza la sección de Fragancia-Aroma + * Renderiza la sección de Fragancia-Aroma con layout horizontal para hijos */ function renderizarSeccionFraganciaAroma( doc: jsPDF, @@ -184,7 +185,7 @@ function renderizarSeccionFraganciaAroma( muestra: Muestra ): void { const width = 144.9 // Ancho total de la sección derecha - const sectionHeight = 34 + const sectionHeight = 28 // Más compacto con layout horizontal // Borde de la sección dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal) @@ -201,31 +202,15 @@ function renderizarSeccionFraganciaAroma( const categoriasSeleccionadas = muestra.fraganciaAromaNotas.categorias const subcategoriasSeleccionadas = muestra.fraganciaAromaNotas.subcategorias - // Espaciado entre checkboxes (compacto) - const checkSpacing = 2.6 - const startY = y + 8 - - // Columna izquierda de checkboxes - let checkY = startY - CATEGORIAS_PDF.columnaIzquierda.forEach((cat) => { - const isChecked = - categoriasSeleccionadas.includes(cat.key as any) || - subcategoriasSeleccionadas.includes(cat.key) - - dibujarCheckboxConLabel(doc, x + 3, checkY, cat.label, isChecked, cat.indent) - checkY += checkSpacing - }) - - // Columna derecha de checkboxes - checkY = startY - CATEGORIAS_PDF.columnaDerecha.forEach((cat) => { - const isChecked = - categoriasSeleccionadas.includes(cat.key as any) || - subcategoriasSeleccionadas.includes(cat.key) - - dibujarCheckboxConLabel(doc, x + 75, checkY, cat.label, isChecked, cat.indent) - checkY += checkSpacing - }) + // Renderizar categorías con layout horizontal + renderizarCategoriasHorizontal( + doc, + x + 2, + y + 7.5, + width - 4, + categoriasSeleccionadas, + subcategoriasSeleccionadas + ) // Notas específica (sin recuadro, solo texto) doc.setFontSize(PDF_CONFIG.fontSize.tiny) @@ -245,7 +230,70 @@ function renderizarSeccionFraganciaAroma( } /** - * Renderiza la sección de Sabores (exactamente igual a Fragancia-Aroma) + * Renderiza categorías con hijos en layout horizontal con wrap + */ +function renderizarCategoriasHorizontal( + doc: jsPDF, + x: number, + y: number, + maxWidth: number, + categoriasSeleccionadas: string[], + subcategoriasSeleccionadas: string[] +): void { + const checkboxSize = 2.5 // Checkbox más pequeño + const rowHeight = 3.8 // Altura de fila + const parentSpacing = 2 // Espacio después de cada grupo padre+hijos + const childIndent = 3 // Indentación para hijos en wrap + + let currentX = x + let currentY = y + + CATEGORIAS_JERARQUICAS.forEach((cat) => { + const isParentChecked = categoriasSeleccionadas.includes(cat.key as any) + + // Calcular ancho necesario para el padre + doc.setFontSize(PDF_CONFIG.fontSize.tiny) + const parentWidth = checkboxSize + 1 + doc.getTextWidth(cat.label) + + // Si no cabe en esta línea, ir a la siguiente + if (currentX + parentWidth > x + maxWidth && currentX > x) { + currentX = x + currentY += rowHeight + } + + // Dibujar checkbox del padre + dibujarCheckbox(doc, currentX, currentY, isParentChecked, checkboxSize) + doc.setFont('helvetica', 'bold') + doc.text(cat.label, currentX + checkboxSize + 1, currentY + 2) + currentX += parentWidth + 1 + + // Si tiene hijos, renderizarlos horizontalmente + if (cat.hijos && cat.hijos.length > 0) { + doc.setFont('helvetica', 'normal') + + cat.hijos.forEach((hijo) => { + const isChildChecked = subcategoriasSeleccionadas.includes(hijo.key) + const childWidth = checkboxSize + 1 + doc.getTextWidth(hijo.label) + + // Si no cabe, wrap a siguiente línea con indentación + if (currentX + childWidth > x + maxWidth) { + currentX = x + childIndent + currentY += rowHeight + } + + dibujarCheckbox(doc, currentX, currentY, isChildChecked, checkboxSize) + doc.text(hijo.label, currentX + checkboxSize + 1, currentY + 2) + currentX += childWidth + 1 + }) + } + + // Añadir espacio después del grupo + currentX += parentSpacing + }) +} + +/** + * Renderiza la sección de Sabores (mismo layout horizontal que Fragancia-Aroma) */ function renderizarSeccionSabores( doc: jsPDF, @@ -254,7 +302,7 @@ function renderizarSeccionSabores( muestra: Muestra ): void { const width = 144.9 - const sectionHeight = 34 // Mismo tamaño que Fragancia-Aroma + const sectionHeight = 28 // Mismo tamaño compacto que Fragancia-Aroma // Borde de la sección dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal) @@ -271,31 +319,15 @@ function renderizarSeccionSabores( const categoriasSeleccionadas = muestra.saborNotas.categorias const subcategoriasSeleccionadas = muestra.saborNotas.subcategorias - // Espaciado entre checkboxes (compacto, igual que Fragancia-Aroma) - const checkSpacing = 2.6 - const startY = y + 8 - - // Columna izquierda de checkboxes (mismas categorías que Fragancia-Aroma) - let checkY = startY - CATEGORIAS_PDF.columnaIzquierda.forEach((cat) => { - const isChecked = - categoriasSeleccionadas.includes(cat.key as any) || - subcategoriasSeleccionadas.includes(cat.key) - - dibujarCheckboxConLabel(doc, x + 3, checkY, cat.label, isChecked, cat.indent) - checkY += checkSpacing - }) - - // Columna derecha de checkboxes - checkY = startY - CATEGORIAS_PDF.columnaDerecha.forEach((cat) => { - const isChecked = - categoriasSeleccionadas.includes(cat.key as any) || - subcategoriasSeleccionadas.includes(cat.key) - - dibujarCheckboxConLabel(doc, x + 75, checkY, cat.label, isChecked, cat.indent) - checkY += checkSpacing - }) + // Renderizar categorías con layout horizontal (igual que Fragancia-Aroma) + renderizarCategoriasHorizontal( + doc, + x + 2, + y + 7.5, + width - 4, + categoriasSeleccionadas, + subcategoriasSeleccionadas + ) // Notas específica (sin recuadro, solo texto) doc.setFontSize(PDF_CONFIG.fontSize.tiny) diff --git a/nuxt4/app/utils/pdf/pdfLayout.ts b/nuxt4/app/utils/pdf/pdfLayout.ts index c250387..7324aca 100644 --- a/nuxt4/app/utils/pdf/pdfLayout.ts +++ b/nuxt4/app/utils/pdf/pdfLayout.ts @@ -147,48 +147,74 @@ export const PARAMETROS_INTENSIDAD = [ ] as const /** - * Tipo para categoría de nota en PDF + * Tipo para subcategoría (hijo) */ -export interface CategoriaPdf { +export interface SubcategoriaPdf { key: string label: string - indent?: boolean +} + +/** + * Tipo para categoría padre con hijos opcionales + */ +export interface CategoriaConHijosPdf { + key: string + label: string + hijos?: SubcategoriaPdf[] } /** * Categorías de notas para el formulario PDF - * Distribución correcta según formulario físico EVC-IH01 + * Estructura jerárquica: padres con hijos que se renderizan horizontalmente */ -export const CATEGORIAS_PDF: { - columnaIzquierda: CategoriaPdf[] - columnaDerecha: CategoriaPdf[] -} = { - columnaIzquierda: [ - { key: 'Floral', label: 'Floral' }, - { key: 'Afrutado', label: 'Afrutado' }, - { key: 'Bayas', label: 'Bayas', indent: true }, - { key: 'Frutas Deshidratadas', label: 'Frutas Deshidratadas', indent: true }, - { key: 'Cítricos', label: 'Cítricos', indent: true }, - { key: 'Verde Vegetal', label: 'Verde/Vegetal' }, - { key: 'Otro', label: 'Otra' }, - { key: 'Químico', label: 'Químico', indent: true }, - { key: 'Humedad/Tierra', label: 'Humedad/Tierra', indent: true }, - { key: 'Madera', label: 'Madera', indent: true }, - ], - columnaDerecha: [ - { key: 'Tostado', label: 'Tostado' }, - { key: 'Cereal', label: 'Cereal', indent: true }, - { key: 'Quemado', label: 'Quemado', indent: true }, - { key: 'Tabaco', label: 'Tabaco', indent: true }, - { key: 'Nueces/Cacao', label: 'Nueces/Cacao' }, - { key: 'Nueces', label: 'Nueces', indent: true }, - { key: 'Cacao', label: 'Cacao', indent: true }, - { key: 'Especias', label: 'Especias' }, - { key: 'Dulce', label: 'Dulce' }, - { key: 'Vainilla', label: 'Vainilla', indent: true }, - { key: 'Azúcar Morena', label: 'Azúcar Morena', indent: true }, - ], -} +export const CATEGORIAS_JERARQUICAS: CategoriaConHijosPdf[] = [ + { key: 'Floral', label: 'Floral' }, + { + key: 'Afrutado', + label: 'Afrutado', + hijos: [ + { key: 'Bayas', label: 'Bayas' }, + { key: 'Frutas Deshidratadas', label: 'F.Deshidr.' }, + { key: 'Cítricos', label: 'Cítricos' }, + ], + }, + { key: 'Verde Vegetal', label: 'Verde/Vegetal' }, + { + key: 'Otro', + label: 'Otra', + hijos: [ + { key: 'Químico', label: 'Químico' }, + { key: 'Humedad/Tierra', label: 'Humedad' }, + { key: 'Madera', label: 'Madera' }, + ], + }, + { + key: 'Tostado', + label: 'Tostado', + hijos: [ + { key: 'Cereal', label: 'Cereal' }, + { key: 'Quemado', label: 'Quemado' }, + { key: 'Tabaco', label: 'Tabaco' }, + ], + }, + { + key: 'Nueces/Cacao', + label: 'Nueces/Cacao', + hijos: [ + { key: 'Nueces', label: 'Nueces' }, + { key: 'Cacao', label: 'Cacao' }, + ], + }, + { key: 'Especias', label: 'Especias' }, + { + key: 'Dulce', + label: 'Dulce', + hijos: [ + { key: 'Vainilla', label: 'Vainilla' }, + { key: 'Azúcar Morena', label: 'Az.Morena' }, + ], + }, +] /** * Sensaciones en boca para el formulario PDF