All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m16s
- SubTotal ahora termina exactamente en 130mm (borde del formulario) - Otras Notas expandida de 10mm a 20mm para llenar el espacio - Agregadas líneas adicionales para escribir en Otras Notas
546 lines
16 KiB
TypeScript
546 lines
16 KiB
TypeScript
/**
|
|
* Renderizado del formulario EVC-IH01 en PDF
|
|
* Replica el formato exacto del formulario físico de catación de café
|
|
*/
|
|
|
|
import type { jsPDF } from 'jspdf'
|
|
import type { Muestra, Intensidades } from '~/types/catacion'
|
|
import { calcularSumatoriaAfectiva, calcularSCAA } from '~/types/catacion'
|
|
import {
|
|
PDF_CONFIG,
|
|
calcularYInicio,
|
|
PARAMETROS_INTENSIDAD,
|
|
CATEGORIAS_JERARQUICAS,
|
|
SENSACIONES_PDF,
|
|
GUSTOS_PDF,
|
|
DEFECTOS_PDF,
|
|
} from './pdfLayout'
|
|
import {
|
|
dibujarCheckbox,
|
|
dibujarCheckboxConLabel,
|
|
dibujarCasillasTazas,
|
|
dibujarRectangulo,
|
|
dibujarLineaHorizontal,
|
|
dibujarLineaVertical,
|
|
truncarTexto,
|
|
} from './pdfHelpers'
|
|
|
|
/**
|
|
* Renderiza un formulario completo de catación
|
|
*/
|
|
export function renderizarFormulario(
|
|
doc: jsPDF,
|
|
muestra: Muestra,
|
|
posicion: 0 | 1 | 2
|
|
): void {
|
|
const yBase = calcularYInicio(posicion)
|
|
const xBase = PDF_CONFIG.marginLeft
|
|
|
|
// Borde exterior del formulario
|
|
dibujarRectangulo(
|
|
doc,
|
|
xBase,
|
|
yBase,
|
|
PDF_CONFIG.formWidth,
|
|
PDF_CONFIG.formHeight,
|
|
PDF_CONFIG.lineWidth.thick
|
|
)
|
|
|
|
// Renderizar cada sección con posiciones ajustadas para layout compacto
|
|
// Fragancia-Aroma y Sabores: 28mm cada una (layout horizontal)
|
|
// SubTotal debe terminar exactamente en formHeight (130mm)
|
|
renderizarEncabezado(doc, xBase, yBase, muestra)
|
|
renderizarTablaIntensidades(doc, xBase, yBase + 14, 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, altura 20mm
|
|
renderizarSeccionSubTotal(doc, xBase, yBase + 122, muestra) // 122+8=130 (exacto al borde)
|
|
}
|
|
|
|
/**
|
|
* Renderiza el encabezado del formulario
|
|
*/
|
|
function renderizarEncabezado(
|
|
doc: jsPDF,
|
|
x: number,
|
|
y: number,
|
|
muestra: Muestra
|
|
): void {
|
|
const width = PDF_CONFIG.formWidth
|
|
|
|
// Línea separadora del encabezado
|
|
dibujarLineaHorizontal(doc, x, y + 14, x + width, PDF_CONFIG.lineWidth.normal)
|
|
|
|
// Título principal
|
|
doc.setFontSize(PDF_CONFIG.fontSize.title)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('EVALUACION DEL VALOR DEL CAFE / DESCRIPTIVA-AFECTIVA', x + 3, y + 5)
|
|
|
|
// Código EVC-IH01
|
|
doc.setFontSize(PDF_CONFIG.fontSize.header)
|
|
doc.text('EVC-IH01', x + width - 22, y + 5)
|
|
|
|
// Línea separadora después del título
|
|
dibujarLineaHorizontal(doc, x, y + 7, x + width, PDF_CONFIG.lineWidth.thin)
|
|
|
|
// MUESTRA
|
|
doc.setFontSize(PDF_CONFIG.fontSize.body)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('MUESTRA:', x + 3, y + 11)
|
|
|
|
doc.setFont('helvetica', 'normal')
|
|
const nombreTruncado = truncarTexto(doc, muestra.nombre, 60, PDF_CONFIG.fontSize.body)
|
|
doc.text(nombreTruncado, x + 25, y + 11)
|
|
}
|
|
|
|
/**
|
|
* Renderiza la tabla de intensidades (lado izquierdo)
|
|
*/
|
|
function renderizarTablaIntensidades(
|
|
doc: jsPDF,
|
|
x: number,
|
|
y: number,
|
|
muestra: Muestra
|
|
): void {
|
|
const colWidths = {
|
|
parametro: 28,
|
|
descriptiva: 13,
|
|
afectiva: 13,
|
|
}
|
|
const rowHeight = 5
|
|
const tableWidth = colWidths.parametro + colWidths.descriptiva + colWidths.afectiva
|
|
const tableHeight = rowHeight * 10
|
|
|
|
// Borde de la tabla
|
|
dibujarRectangulo(doc, x, y, tableWidth, tableHeight, PDF_CONFIG.lineWidth.normal)
|
|
|
|
// Headers
|
|
doc.setFontSize(PDF_CONFIG.fontSize.small)
|
|
doc.setFont('helvetica', 'bold')
|
|
|
|
// Header: Intensidad/Parámetro
|
|
doc.text('Intensidad', x + 2, y + 4)
|
|
doc.setFontSize(PDF_CONFIG.fontSize.tiny)
|
|
doc.text('Parámetro', x + 2, y + rowHeight + 4)
|
|
|
|
// Header: Descriptiva
|
|
dibujarLineaVertical(doc, x + colWidths.parametro, y, y + tableHeight)
|
|
doc.setFontSize(PDF_CONFIG.fontSize.tiny)
|
|
doc.text('Descriptiva', x + colWidths.parametro + 1, y + 4)
|
|
doc.text('(1 al 15)', x + colWidths.parametro + 2, y + 8)
|
|
|
|
// Header: Afectiva
|
|
dibujarLineaVertical(doc, x + colWidths.parametro + colWidths.descriptiva, y, y + tableHeight)
|
|
doc.text('Afectiva', x + colWidths.parametro + colWidths.descriptiva + 2, y + 4)
|
|
doc.text('(1 al 9)', x + colWidths.parametro + colWidths.descriptiva + 3, y + 8)
|
|
|
|
// Línea horizontal después de headers
|
|
dibujarLineaHorizontal(doc, x, y + rowHeight * 2, x + tableWidth)
|
|
|
|
// Filas de datos
|
|
const intensidades = muestra.intensidades
|
|
let currentY = y + rowHeight * 2
|
|
|
|
PARAMETROS_INTENSIDAD.forEach((param) => {
|
|
const intensidad = intensidades[param.key as keyof Intensidades]
|
|
|
|
// Parámetro
|
|
doc.setFontSize(PDF_CONFIG.fontSize.tiny)
|
|
doc.setFont('helvetica', 'normal')
|
|
doc.text(param.label, x + 2, currentY + 3.5)
|
|
|
|
// Valor descriptiva
|
|
if (intensidad.descriptiva !== null) {
|
|
doc.text(
|
|
String(intensidad.descriptiva),
|
|
x + colWidths.parametro + colWidths.descriptiva / 2 - 1,
|
|
currentY + 3.5
|
|
)
|
|
}
|
|
|
|
// Valor afectiva
|
|
if (intensidad.afectiva !== null) {
|
|
doc.text(
|
|
String(intensidad.afectiva),
|
|
x + colWidths.parametro + colWidths.descriptiva + colWidths.afectiva / 2 - 1,
|
|
currentY + 3.5
|
|
)
|
|
}
|
|
|
|
// Línea separadora
|
|
dibujarLineaHorizontal(doc, x, currentY + rowHeight, x + tableWidth, PDF_CONFIG.lineWidth.thin)
|
|
|
|
currentY += rowHeight
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Renderiza la sección de Fragancia-Aroma con layout horizontal para hijos
|
|
*/
|
|
function renderizarSeccionFraganciaAroma(
|
|
doc: jsPDF,
|
|
x: number,
|
|
y: number,
|
|
muestra: Muestra
|
|
): void {
|
|
const width = 144.9 // Ancho total de la sección derecha
|
|
const sectionHeight = 28 // Más compacto con layout horizontal
|
|
|
|
// Borde de la sección
|
|
dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal)
|
|
|
|
// Header
|
|
doc.setFontSize(PDF_CONFIG.fontSize.small)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('Fragancia - Aroma', x + 3, y + 4)
|
|
|
|
// Línea después del header
|
|
dibujarLineaHorizontal(doc, x, y + 5.5, x + width, PDF_CONFIG.lineWidth.thin)
|
|
|
|
// Determinar qué está seleccionado
|
|
const categoriasSeleccionadas = muestra.fraganciaAromaNotas.categorias
|
|
const subcategoriasSeleccionadas = muestra.fraganciaAromaNotas.subcategorias
|
|
|
|
// 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)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('Notas:', x + 3, y + sectionHeight - 2)
|
|
|
|
if (muestra.fraganciaAromaNotas.notaEspecifica) {
|
|
doc.setFont('helvetica', 'normal')
|
|
const notaTruncada = truncarTexto(
|
|
doc,
|
|
muestra.fraganciaAromaNotas.notaEspecifica,
|
|
width - 22,
|
|
PDF_CONFIG.fontSize.tiny
|
|
)
|
|
doc.text(notaTruncada, x + 15, y + sectionHeight - 2)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Renderiza categorías con hijos en layout horizontal
|
|
* Las familias completas nunca se truncan entre líneas
|
|
*/
|
|
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 familySpacing = 8 // Espacio entre familias
|
|
const itemSpacing = 1 // Espacio entre items dentro de familia
|
|
|
|
let currentX = x
|
|
let currentY = y
|
|
|
|
doc.setFontSize(PDF_CONFIG.fontSize.tiny)
|
|
|
|
CATEGORIAS_JERARQUICAS.forEach((cat) => {
|
|
// Calcular ancho total de la familia (padre + todos los hijos)
|
|
const parentWidth = checkboxSize + 1 + doc.getTextWidth(cat.label)
|
|
let familyWidth = parentWidth
|
|
|
|
if (cat.hijos && cat.hijos.length > 0) {
|
|
cat.hijos.forEach((hijo) => {
|
|
familyWidth += itemSpacing + checkboxSize + 1 + doc.getTextWidth(hijo.label)
|
|
})
|
|
}
|
|
|
|
// Si la familia completa no cabe, ir a la siguiente línea
|
|
if (currentX + familyWidth > x + maxWidth && currentX > x) {
|
|
currentX = x
|
|
currentY += rowHeight
|
|
}
|
|
|
|
// Renderizar el padre
|
|
const isParentChecked = categoriasSeleccionadas.includes(cat.key as any)
|
|
dibujarCheckbox(doc, currentX, currentY, isParentChecked, checkboxSize)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text(cat.label, currentX + checkboxSize + 1, currentY + 2)
|
|
currentX += parentWidth + itemSpacing
|
|
|
|
// Renderizar hijos (si los tiene)
|
|
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)
|
|
|
|
dibujarCheckbox(doc, currentX, currentY, isChildChecked, checkboxSize)
|
|
doc.text(hijo.label, currentX + checkboxSize + 1, currentY + 2)
|
|
currentX += childWidth + itemSpacing
|
|
})
|
|
}
|
|
|
|
// Añadir espacio entre familias
|
|
currentX += familySpacing
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Renderiza la sección de Sabores (mismo layout horizontal que Fragancia-Aroma)
|
|
*/
|
|
function renderizarSeccionSabores(
|
|
doc: jsPDF,
|
|
x: number,
|
|
y: number,
|
|
muestra: Muestra
|
|
): void {
|
|
const width = 144.9
|
|
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)
|
|
|
|
// Header
|
|
doc.setFontSize(PDF_CONFIG.fontSize.small)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('Sabores', x + 3, y + 4)
|
|
|
|
// Línea después del header
|
|
dibujarLineaHorizontal(doc, x, y + 5.5, x + width, PDF_CONFIG.lineWidth.thin)
|
|
|
|
// Determinar qué está seleccionado
|
|
const categoriasSeleccionadas = muestra.saborNotas.categorias
|
|
const subcategoriasSeleccionadas = muestra.saborNotas.subcategorias
|
|
|
|
// 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)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('Notas:', x + 3, y + sectionHeight - 2)
|
|
|
|
if (muestra.saborNotas.notaEspecifica) {
|
|
doc.setFont('helvetica', 'normal')
|
|
const notaTruncada = truncarTexto(
|
|
doc,
|
|
muestra.saborNotas.notaEspecifica,
|
|
width - 22,
|
|
PDF_CONFIG.fontSize.tiny
|
|
)
|
|
doc.text(notaTruncada, x + 15, y + sectionHeight - 2)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Renderiza la sección de Sensación en boca y Gustos predominantes
|
|
* Layout horizontal en 2 filas para cada sección
|
|
*/
|
|
function renderizarSeccionSensacionGustos(
|
|
doc: jsPDF,
|
|
x: number,
|
|
y: number,
|
|
muestra: Muestra
|
|
): void {
|
|
const width = PDF_CONFIG.formWidth
|
|
const sectionHeight = 16 // Más compacto con layout horizontal
|
|
|
|
// Borde de la sección
|
|
dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal)
|
|
|
|
// Sección Sensación en boca
|
|
const sensacionWidth = width * 0.6
|
|
|
|
doc.setFontSize(PDF_CONFIG.fontSize.small)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('Sensación en boca', x + 3, y + 4)
|
|
|
|
// Línea divisoria vertical
|
|
dibujarLineaVertical(doc, x + sensacionWidth, y, y + sectionHeight)
|
|
|
|
// Sensaciones - Layout horizontal en 2 filas
|
|
// Fila 1: Áspero, Aceitoso, Metálico
|
|
// Fila 2: Suave, Deja seca la boca
|
|
const sensacionesLabels = [
|
|
{ key: 'Áspero', label: 'Áspero' },
|
|
{ key: 'Aceitoso', label: 'Aceitoso' },
|
|
{ key: 'Metálico', label: 'Metálico' },
|
|
{ key: 'Suave', label: 'Suave' },
|
|
{ key: 'Deja seca la boca', label: 'Astringente' },
|
|
]
|
|
|
|
const checkSpacingX = 40 // Espacio horizontal entre checkboxes
|
|
const row1Y = y + 7
|
|
const row2Y = y + 12
|
|
|
|
// Fila 1: primeros 3
|
|
sensacionesLabels.slice(0, 3).forEach((sens, idx) => {
|
|
const isChecked = muestra.sensacionEnBoca === sens.key
|
|
dibujarCheckboxConLabel(doc, x + 3 + idx * checkSpacingX, row1Y, sens.label, isChecked)
|
|
})
|
|
|
|
// Fila 2: últimos 2
|
|
sensacionesLabels.slice(3).forEach((sens, idx) => {
|
|
const isChecked = muestra.sensacionEnBoca === sens.key
|
|
dibujarCheckboxConLabel(doc, x + 3 + idx * checkSpacingX, row2Y, sens.label, isChecked)
|
|
})
|
|
|
|
// Sección Gustos predominantes
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('Gustos predominantes (2)', x + sensacionWidth + 3, y + 4)
|
|
|
|
// Gustos - Layout horizontal en 2 filas
|
|
// Fila 1: Salado, Amargo, Ácido
|
|
// Fila 2: Dulce, Umami
|
|
const gustosSpacingX = 26
|
|
|
|
// Fila 1: primeros 3
|
|
GUSTOS_PDF.slice(0, 3).forEach((gusto, idx) => {
|
|
const isChecked = muestra.gustosPredominantes.includes(gusto.key as any)
|
|
dibujarCheckboxConLabel(doc, x + sensacionWidth + 3 + idx * gustosSpacingX, row1Y, gusto.label, isChecked)
|
|
})
|
|
|
|
// Fila 2: últimos 2
|
|
GUSTOS_PDF.slice(3).forEach((gusto, idx) => {
|
|
const isChecked = muestra.gustosPredominantes.includes(gusto.key as any)
|
|
dibujarCheckboxConLabel(doc, x + sensacionWidth + 3 + idx * gustosSpacingX, row2Y, gusto.label, isChecked)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Renderiza la sección de Tazas y Defectos
|
|
*/
|
|
function renderizarSeccionTazasDefectos(
|
|
doc: jsPDF,
|
|
x: number,
|
|
y: number,
|
|
muestra: Muestra
|
|
): void {
|
|
const width = PDF_CONFIG.formWidth
|
|
const sectionHeight = 16
|
|
|
|
// Borde de la sección
|
|
dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal)
|
|
|
|
// Tazas NO Uniformes
|
|
dibujarCasillasTazas(doc, x + 3, y + 3, muestra.tazasNoUniformes, 'Tazas NO Uniformes:')
|
|
|
|
// Tazas Defectuosas
|
|
dibujarCasillasTazas(doc, x + 70, y + 3, muestra.tazasDefectuosas, 'Tazas defectuosas:')
|
|
|
|
// Defectos - Header alineado con checkboxes
|
|
doc.setFontSize(PDF_CONFIG.fontSize.small)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('Defecto (de haberse):', x + 140, y + 5)
|
|
|
|
// Checkboxes alineados bajo el header
|
|
let checkX = x + 140
|
|
const checkY = y + 10
|
|
DEFECTOS_PDF.forEach((defecto) => {
|
|
const isChecked = muestra.defecto === defecto.key
|
|
dibujarCheckboxConLabel(doc, checkX, checkY, defecto.label, isChecked)
|
|
checkX += 20
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Renderiza la sección de Otras Notas
|
|
*/
|
|
function renderizarSeccionOtrasNotas(
|
|
doc: jsPDF,
|
|
x: number,
|
|
y: number,
|
|
muestra: Muestra
|
|
): void {
|
|
const width = PDF_CONFIG.formWidth
|
|
const sectionHeight = 20 // Expandida para llenar espacio hasta SubTotal
|
|
|
|
// Borde de la sección
|
|
dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal)
|
|
|
|
// Label
|
|
doc.setFontSize(PDF_CONFIG.fontSize.body)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('OTRAS NOTAS:', x + 3, y + 6)
|
|
|
|
// Valor
|
|
if (muestra.otrasNotas) {
|
|
doc.setFont('helvetica', 'normal')
|
|
const notaTruncada = truncarTexto(
|
|
doc,
|
|
muestra.otrasNotas,
|
|
width - 45,
|
|
PDF_CONFIG.fontSize.body
|
|
)
|
|
doc.text(notaTruncada, x + 38, y + 6)
|
|
}
|
|
|
|
// Líneas para escribir (múltiples líneas para el espacio expandido)
|
|
dibujarLineaHorizontal(doc, x + 38, y + 7, x + width - 3, PDF_CONFIG.lineWidth.thin)
|
|
dibujarLineaHorizontal(doc, x + 3, y + 12, x + width - 3, PDF_CONFIG.lineWidth.thin)
|
|
dibujarLineaHorizontal(doc, x + 3, y + 17, x + width - 3, PDF_CONFIG.lineWidth.thin)
|
|
}
|
|
|
|
/**
|
|
* Renderiza la sección de Sub Total
|
|
*/
|
|
function renderizarSeccionSubTotal(
|
|
doc: jsPDF,
|
|
x: number,
|
|
y: number,
|
|
muestra: Muestra
|
|
): void {
|
|
const width = PDF_CONFIG.formWidth
|
|
const sectionHeight = 8
|
|
|
|
// Borde de la sección
|
|
dibujarRectangulo(doc, x, y, width, sectionHeight, PDF_CONFIG.lineWidth.normal)
|
|
|
|
// Cálculos
|
|
const sumatoriaAfectiva = calcularSumatoriaAfectiva(muestra)
|
|
const scaa = calcularSCAA(muestra)
|
|
|
|
// Daño: 6pts por cada taza defectuosa (por definición también es no uniforme)
|
|
const dano = muestra.tazasDefectuosas.length * 6
|
|
|
|
doc.setFontSize(PDF_CONFIG.fontSize.small)
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text('SUB TOTAL:', x + 3, y + 5)
|
|
|
|
// Valores
|
|
const valores = [
|
|
{ label: 'Sumatoria Afectiva:', valor: sumatoriaAfectiva.toString() },
|
|
{ label: 'Total (100%):', valor: scaa.toFixed(2) },
|
|
{ label: 'Daño (6pto Taza):', valor: dano.toString() },
|
|
{ label: 'Total (100%) - Daño:', valor: (scaa - dano).toFixed(2) },
|
|
]
|
|
|
|
let currentX = x + 32
|
|
valores.forEach((item) => {
|
|
doc.setFont('helvetica', 'normal')
|
|
doc.text(item.label, currentX, y + 5)
|
|
currentX += doc.getTextWidth(item.label) + 1
|
|
|
|
doc.setFont('helvetica', 'bold')
|
|
doc.text(item.valor, currentX, y + 5)
|
|
currentX += doc.getTextWidth(item.valor) + 6
|
|
})
|
|
}
|