PDF: Layout horizontal compacto para categorías padre-hijo
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m14s

- 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
This commit is contained in:
2025-11-24 17:56:41 -06:00
parent 7c3b9a34b7
commit 9fc1842791
2 changed files with 154 additions and 96 deletions

View File

@@ -10,7 +10,7 @@ import {
PDF_CONFIG, PDF_CONFIG,
calcularYInicio, calcularYInicio,
PARAMETROS_INTENSIDAD, PARAMETROS_INTENSIDAD,
CATEGORIAS_PDF, CATEGORIAS_JERARQUICAS,
SENSACIONES_PDF, SENSACIONES_PDF,
GUSTOS_PDF, GUSTOS_PDF,
DEFECTOS_PDF, DEFECTOS_PDF,
@@ -46,15 +46,16 @@ export function renderizarFormulario(
PDF_CONFIG.lineWidth.thick 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) renderizarEncabezado(doc, xBase, yBase, muestra)
renderizarTablaIntensidades(doc, xBase, yBase + 14, muestra) renderizarTablaIntensidades(doc, xBase, yBase + 14, muestra)
renderizarSeccionFraganciaAroma(doc, xBase + 55, yBase + 14, muestra) renderizarSeccionFraganciaAroma(doc, xBase + 55, yBase + 14, muestra) // 28mm
renderizarSeccionSabores(doc, xBase + 55, yBase + 49, muestra) // Sabores ahora tiene 34mm igual que Fragancia-Aroma renderizarSeccionSabores(doc, xBase + 55, yBase + 42, muestra) // 28mm (14+28=42)
renderizarSeccionSensacionGustos(doc, xBase, yBase + 84, muestra) renderizarSeccionSensacionGustos(doc, xBase, yBase + 70, muestra) // Después de Sabores (42+28=70)
renderizarSeccionTazasDefectos(doc, xBase, yBase + 100, muestra) renderizarSeccionTazasDefectos(doc, xBase, yBase + 86, muestra) // 70+16=86
renderizarSeccionOtrasNotas(doc, xBase, yBase + 116, muestra) renderizarSeccionOtrasNotas(doc, xBase, yBase + 102, muestra) // 86+16=102
renderizarSeccionSubTotal(doc, xBase, yBase + 124, muestra) 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( function renderizarSeccionFraganciaAroma(
doc: jsPDF, doc: jsPDF,
@@ -184,7 +185,7 @@ function renderizarSeccionFraganciaAroma(
muestra: Muestra muestra: Muestra
): void { ): void {
const width = 144.9 // Ancho total de la sección derecha 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 // Borde de la sección
dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal) dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal)
@@ -201,31 +202,15 @@ function renderizarSeccionFraganciaAroma(
const categoriasSeleccionadas = muestra.fraganciaAromaNotas.categorias const categoriasSeleccionadas = muestra.fraganciaAromaNotas.categorias
const subcategoriasSeleccionadas = muestra.fraganciaAromaNotas.subcategorias const subcategoriasSeleccionadas = muestra.fraganciaAromaNotas.subcategorias
// Espaciado entre checkboxes (compacto) // Renderizar categorías con layout horizontal
const checkSpacing = 2.6 renderizarCategoriasHorizontal(
const startY = y + 8 doc,
x + 2,
// Columna izquierda de checkboxes y + 7.5,
let checkY = startY width - 4,
CATEGORIAS_PDF.columnaIzquierda.forEach((cat) => { categoriasSeleccionadas,
const isChecked = subcategoriasSeleccionadas
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
})
// Notas específica (sin recuadro, solo texto) // Notas específica (sin recuadro, solo texto)
doc.setFontSize(PDF_CONFIG.fontSize.tiny) 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( function renderizarSeccionSabores(
doc: jsPDF, doc: jsPDF,
@@ -254,7 +302,7 @@ function renderizarSeccionSabores(
muestra: Muestra muestra: Muestra
): void { ): void {
const width = 144.9 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 // Borde de la sección
dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal) dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal)
@@ -271,31 +319,15 @@ function renderizarSeccionSabores(
const categoriasSeleccionadas = muestra.saborNotas.categorias const categoriasSeleccionadas = muestra.saborNotas.categorias
const subcategoriasSeleccionadas = muestra.saborNotas.subcategorias const subcategoriasSeleccionadas = muestra.saborNotas.subcategorias
// Espaciado entre checkboxes (compacto, igual que Fragancia-Aroma) // Renderizar categorías con layout horizontal (igual que Fragancia-Aroma)
const checkSpacing = 2.6 renderizarCategoriasHorizontal(
const startY = y + 8 doc,
x + 2,
// Columna izquierda de checkboxes (mismas categorías que Fragancia-Aroma) y + 7.5,
let checkY = startY width - 4,
CATEGORIAS_PDF.columnaIzquierda.forEach((cat) => { categoriasSeleccionadas,
const isChecked = subcategoriasSeleccionadas
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
})
// Notas específica (sin recuadro, solo texto) // Notas específica (sin recuadro, solo texto)
doc.setFontSize(PDF_CONFIG.fontSize.tiny) doc.setFontSize(PDF_CONFIG.fontSize.tiny)

View File

@@ -147,48 +147,74 @@ export const PARAMETROS_INTENSIDAD = [
] as const ] as const
/** /**
* Tipo para categoría de nota en PDF * Tipo para subcategoría (hijo)
*/ */
export interface CategoriaPdf { export interface SubcategoriaPdf {
key: string key: string
label: 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 * 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: { export const CATEGORIAS_JERARQUICAS: CategoriaConHijosPdf[] = [
columnaIzquierda: CategoriaPdf[] { key: 'Floral', label: 'Floral' },
columnaDerecha: CategoriaPdf[] {
} = { key: 'Afrutado',
columnaIzquierda: [ label: 'Afrutado',
{ key: 'Floral', label: 'Floral' }, hijos: [
{ key: 'Afrutado', label: 'Afrutado' }, { key: 'Bayas', label: 'Bayas' },
{ key: 'Bayas', label: 'Bayas', indent: true }, { key: 'Frutas Deshidratadas', label: 'F.Deshidr.' },
{ key: 'Frutas Deshidratadas', label: 'Frutas Deshidratadas', indent: true }, { key: 'Cítricos', label: 'Cítricos' },
{ key: 'Cítricos', label: 'Cítricos', indent: true }, ],
{ key: 'Verde Vegetal', label: 'Verde/Vegetal' }, },
{ key: 'Otro', label: 'Otra' }, { key: 'Verde Vegetal', label: 'Verde/Vegetal' },
{ key: 'Químico', label: 'Químico', indent: true }, {
{ key: 'Humedad/Tierra', label: 'Humedad/Tierra', indent: true }, key: 'Otro',
{ key: 'Madera', label: 'Madera', indent: true }, label: 'Otra',
], hijos: [
columnaDerecha: [ { key: 'Químico', label: 'Químico' },
{ key: 'Tostado', label: 'Tostado' }, { key: 'Humedad/Tierra', label: 'Humedad' },
{ key: 'Cereal', label: 'Cereal', indent: true }, { key: 'Madera', label: 'Madera' },
{ 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: 'Tostado',
{ key: 'Cacao', label: 'Cacao', indent: true }, label: 'Tostado',
{ key: 'Especias', label: 'Especias' }, hijos: [
{ key: 'Dulce', label: 'Dulce' }, { key: 'Cereal', label: 'Cereal' },
{ key: 'Vainilla', label: 'Vainilla', indent: true }, { key: 'Quemado', label: 'Quemado' },
{ key: 'Azúcar Morena', label: 'Azúcar Morena', indent: true }, { 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 * Sensaciones en boca para el formulario PDF