Introducción
JasperReports es una poderosa herramienta de generación de reportes que permite crear documentos en formatos como PDF, HTML, XLS, y más. En este tutorial, aprenderemos a generar una factura a partir de un JSON usando la biblioteca JasperReports en Scala.
Requisitos previos
Antes de comenzar, asegúrese de contar con:
- Jaspersoft Studio instalado para diseñar el reporte. En este tutorial se usara la version 6.21.3.
- Un archivo JSON que sirva como fuente de datos.
Sección 1: Diseñar el reporte en Jaspersoft Studio
Paso 1. Configurar el Origen de Datos JSON
-
Acceder al Administrador de Data Adapters
- En la barra lateral izquierda, localiza Repository Explorer
- Haz clic derecho en Data Adapters > Create Data Adapter
-
Seleccionar Tipo de Fuente de Datos
- En el wizard de creación:
- Tipo: Selecciona JSON File
- Nombre:
JSON_Adapter_iNVOICE
(usar nombre descriptivo)
- En el wizard de creación:
-
Configurar Ruta del JSON 🗂️
- File Name/URL:
- Usa el ícono de carpeta para seleccionar tu archivo JSON local
- Marca esta opcion:
- ✅ Use the report JSON expression when filling the report
- File Name/URL:
-
Validación de Conexión 🔍
- Haz clic en Test → Debes ver:
Successful
- Si falla:
- Verifica la sintaxis del JSON (usa JSONLint)
- Haz clic en Test → Debes ver:
Paso 2. Crear un Nuevo Reporte con Datos JSON
Sigue estos pasos para configurar la estructura base del reporte:
Paso a Paso:
-
Iniciar nuevo reporte
- Menú superior: File > New > Jasper Report
- Selecciona un template básico (ej:
Invoice
)
-
Configurar conexión a JSON
- En la sección Data Adapter:
- Selecciona tu adaptador JSON previamente creado
- Verifica la ruta del archivo JSON de prueba
- En la sección Data Adapter:
-
Mapear estructura del JSON
- En el panel Dataset and Query:
- JSON Path: Haz doble clic en el nodo padre (
invoice
en este caso)// Ejemplo de estructura esperada: { "invoice": { "id": "INV-2023", "items": [...], "client": {...} } }
- Click en Read Fields para cargar los sub-campos
- JSON Path: Haz doble clic en el nodo padre (
- En el panel Dataset and Query:
-
Agregar campos al diseño
- Desde el panel Fields:
- Arrastra los elementos clave al área de diseño (ej:
invoice.id
,invoice.total
) - Usa elementos estáticos (Text) para encabezados
- Arrastra los elementos clave al área de diseño (ej:
- Desde el panel Fields:
-
Configuración final del dataset
- En la pestaña Group By:
- No modifiques valores (presiona Next directamente)
- Click en Finish para generar la plantilla base
- En la pestaña Group By:
📸 Vista Preliminar:
Ejemplo de reporte con campos principales mapeados
💡 Trabajando con Datos Anidados (Nuevo!):
Para incluir tablas con estructuras complejas (ej: lista de ítems dentro de invoice.items
):
➡️ Guía completa: Tablas con propiedades anidadas en JSON
Paso 3. Diseñar el reporte:
-
Organización básica de elementos
- Arrastre y suelte los campos del panel Fields al área de diseño
- Personalice con:
- Títulos: Use Static Text con fuentes grandes (18-22pt)
- Encabezados: Fondo coloreado + texto blanco
- Formatos: Alineación numérica derecha, fechas con
dd/MM/yyyy
-
Creación de variables para operaciones
- Click derecho en Variables > Create Variable:
// Ejemplo: Cálculo de subtotal $F{precio} * $F{cantidad}
- Click derecho en Variables > Create Variable:
Tipos comunes:
Variable | Expresión | Cálculo |
---|---|---|
TotalItems | $F{quantity} | Sum |
SubTotal | $F{quantity * $F{unit_price} | Sum |
GranTotal | $F{quantity} * $F{unit_price} * (1 + $F{tax_rate}) | Sum |
- Integración de variables en diseño
// En cuadro de texto dinámico: "$ " + $V{GranTotal} // Con formato monetario: (new DecimalFormat("$#,##0.00")).format($V{SubTotal})
📸 Ejemplo de Implementación:
*Reporte funcional
Paso 4. Compilar y guardar el reporte (.jrxml).
¿Por qué necesitamos dos formatos (.jrxml y .jasper)?
-
Archivo
.jrxml
- Función: Es el diseño editable del reporte en formato XML.
- Características:
- Contiene toda la estructura visual (tablas, campos, estilos).
- Es legible para humanos (puedes abrirlo con cualquier editor de texto).
- Solo se usa en fase de diseño con JasperSoft Studio.
-
Archivo
.jasper
- Función: Versión compilada y optimizada del reporte..
- Características:
- Formato binario específico de JasperReports.
- No es editable directamente.
- Requerido para la generación de reportes en tiempo de ejecución.
Sección 2: Configuración del Proyecto Scala
2.1 Agregar Dependencias
Incluye JasperReports en tu build.sbt
(usa siempre la última versión estable):
val jasperReportsVersion = "6.21.4" // Versión con soporte para JSON y Java 11+
libraryDependencies ++= Seq(
"net.sf.jasperreports" % "jasperreports" % jasperReportsVersion
)
2.2 Cargar Datos JSON desde Recursos
import scala.io.Source
def readJsonFile(fileName: String): String = {
// Carga archivo desde src/main/resources
val source = Source.fromResource(fileName)
try {
source.mkString // Convierte a String
} finally {
source.close() // Importante: Cierra el recurso
}
}
// Uso: carga tu archivo JSON de ejemplo
val jsonData = readJsonFile("invoice1.json")
2.3 Crear DataSource para JasperReports
import java.io.ByteArrayInputStream
import net.sf.jasperreports.engine.data.JsonDataSource
// Convierte el String JSON a InputStream
val jsonStream = new ByteArrayInputStream(jsonData.getBytes("UTF-8"))
// Crea DataSource usando JSONPath (aquí "invoice" es el nodo raíz)
val dataSource = new JsonDataSource(jsonStream, "invoice")
2.4 Configurar Entradas/Salidas
import java.io.{FileInputStream, FileOutputStream}
// Plantilla compilada (.jasper)
val template = new FileInputStream("src/main/resources/invoice.jasper")
// Archivo PDF de salida
val outputPdf = new FileOutputStream("src/main/resources/invoice1.pdf")
2.5 Definir Parámetros del Reporte
import scala.collection.mutable.Map
val parameters = Map[String, AnyRef]()
// Parámetros que coinciden con los definidos en el diseño .jrxml
parameters.put("Company_Name", "Mi Empresa S.A.") // Ejemplo de parámetro estático
2.6 Clases Esenciales para la Generación
Modelo de Parámetros
import net.sf.jasperreports.engine.JRDataSource
import java.io.{InputStream, OutputStream}
// Trait base para diferentes tipos de plantillas
sealed trait JasperTemplateParameters
// Implementación específica para JSON
case class JsonTemplateParameters(
datasource: JRDataSource,
// Fuente de datos JSON
template: InputStream, // Plantilla .jasper
outputStream: OutputStream, // Destino del PDF
reportParameters: Map[String, AnyRef] = Map.empty // Parámetros adicionales
) extends JasperTemplateParameters
Generador de PDFs
import com.yourproject.reports.models.JsonTemplateParameters
import net.sf.jasperreports.engine.{JRException, JasperExportManager, JasperFillManager, JasperPrint}
import java.io.OutputStream
import scala.jdk.CollectionConverters.*
import scala.util.{Failure, Success, Try}
class JasperPdfCreator {
override def writePdf(templateParameters: JasperTemplateParameters): Either[JRException, Unit] = {
templateParameters match
case templateParameters: JsonTemplateParameters =>
for {
jasperPrint <- fillReport(templateParameters)
_ <- generatePdf(jasperPrint, templateParameters.outputStream)
} yield()
}
private def fillReport(jsonTemplateParameters: JsonTemplateParameters): Either[JRException, JasperPrint] = {
val datasource = jsonTemplateParameters.datasource
Try {
JasperFillManager.fillReport(jsonTemplateParameters.template, jsonTemplateParameters.reportParameters.asJava, datasource)
} match {
case Success(jasperPrint) => Right(jasperPrint)
case Failure(exception: JRException) => Left(exception)
case Failure(other) => Left(new JRException("Unexpected error", other))
}
}
private def generatePdf(jasperPrint: JasperPrint, pdfOutputStream: OutputStream) : Either[JRException, Unit] = {
try {
Right(JasperExportManager.exportReportToPdfStream(jasperPrint, pdfOutputStream))
} catch
case e : JRException => Left(e)
}
}
2.7 Ejecutar Generación de Reporte
val pdfGenerator = new JasperPdfCreator()
pdfGenerator.generateReport(
JsonTemplateParameters(
datasource = dataSource,
template = template,
outputStream = outputPdf,
reportParameters = parameters
)
) match {
case Right(_) => println("✅ Reporte generado exitosamente")
case Left(error) => println(s"❌ Error crítico: $error")
}