9. Espacio de nombres data:: — datos externos
← fix:: · Índice · Siguiente: Biblioteca estándar →
8 funciones para cruzar el contenido del PDF con listas y tablas tuyas. Todo en local: ningún dato sale de la máquina.
9.1 Dónde están los archivos
Los glosarios y los conjuntos de datos aceptan una ruta relativa al directorio de ejecución:
data::load_glossary("terminos/juridicos.txt")
data::load_dataset("datos/lotes.csv")
Las bases de consulta (query_gtin, query_medicamento, query_postal_code)
tienen nombre fijo y se buscan en este orden:
$PDFL_DATA_DIR(variable de entorno)./dados/./- Perfiles instalados con
pdfl add(pdfl_profiles/*/dados/) - Junto al PDF analizado
# Apuntando explícitamente a la carpeta de las bases
PDFL_DATA_DIR=/opt/bases pdfl run perfil.pdfl documento.pdf
Si no se encuentra la base, el mensaje de error dice dónde ponerla.
Para distribuir las bases junto con los perfiles, usa pdfl pack; consulta el
capítulo 11.
9.2 Glosarios
Un glosario es un archivo de texto con un término por línea. Las líneas vacías y
las que empiezan por # se ignoran.
terminos/obligatorios.txt:
# Términos que toda póliza debe contener
plazo de carencia
cobertura contratada
condiciones generales
data::load_glossary(archivo)
Carga el glosario como lista de términos.
check "Glosario cargado" {
terminos = data::load_glossary("terminos/obligatorios.txt")
print("términos en el glosario:", terminos.length)
require terminos.contains("condiciones generales")
}
data::validate_against_reference(archivo)
El camino más directo: devuelve la lista de los términos del glosario que no aparecen en el documento. Una lista vacía significa que está todo.
check "Cláusulas obligatorias" {
faltan = data::validate_against_reference("terminos/obligatorios.txt")
assert faltan.length == 0,
"cláusulas ausentes en la póliza: #{faltan.join("; ")}"
}
La comparación ignora mayúsculas y espaciado: «CONDICIONES GENERALES» satisface a «condiciones generales».
9.3 Conjuntos de datos (CSV)
data::load_dataset(archivo)
Carga un CSV como lista de filas; cada fila es una lista de columnas. Las comillas se tratan según el estándar (un campo entrecomillado puede contener comas).
datos/lotes.csv:
lote,descripcion,caducidad
L2026-08,Lote homologado agosto/2026,2028-08-01
L2026-09,Lote homologado septiembre/2026,2028-09-01
check "Recorriendo la tabla" {
filas = data::load_dataset("datos/lotes.csv")
// La primera fila es la cabecera
print("columnas:", filas.first().join(" | "))
print("registros:", filas.length - 1)
// get(n) es 1-based: get(1) es la primera columna
filas.each { |fila|
print(fila.get(1), "->", fila.get(2))
}
}
data::lookup_value(archivo, clave)
Busca la clave en la primera columna y devuelve el valor de la segunda.
Devuelve null si no la encuentra, y como null es falso, se puede comprobar
directamente.
check "Lote homologado" {
lote = text::extract_from_region(1, region(400, 50, 150, 20)).trim()
descripcion = data::lookup_value("datos/lotes.csv", lote)
assert descripcion,
"el lote #{lote} no consta en la tabla de homologados"
print("lote reconocido:", descripcion)
}
9.4 Bases de consulta
Estas funciones buscan archivos con nombre fijo en las carpetas descritas en 9.1
y devuelven la fila entera como lista (o null).
data::query_gtin(codigo)
Consulta gtin.csv. Ignora la puntuación del código.
datos/gtin.csv:
gtin,descripcion,fabricante
7891234567895,Metamizol sódico 500mg 20 comp,Lab Ejemplo
check "Producto homologado" {
// Cruzando con el código leído del propio envase
codigo = codes::decode_barcode(1)
producto = data::query_gtin(codigo)
assert producto,
"el GTIN #{codigo} no consta en la base de productos"
print("producto:", producto.get(2))
print("fabricante:", producto.get(3))
}
data::query_medicamento(registro_o_nombre)
Consulta medicamentos.csv. Acepta el número de registro (primera columna) o
parte del nombre (segunda columna).
datos/medicamentos.csv:
registro,nombre,principio_activo,franja
1.0298.0123,Metamizol sódico,metamizol monohidratado,libre
1.0298.0456,Amoxicilina,amoxicilina trihidratada,roja
check "Franja correcta en el prospecto" {
registro = text::extract_from_region(1, region(50, 780, 200, 15)).trim()
medicamento = data::query_medicamento(registro)
assert medicamento,
"el registro #{registro} no se encontró en la base del regulador"
// Si la franja es roja, el texto obligatorio tiene que estar en el arte
franja = medicamento.get(4)
print("medicamento:", medicamento.get(2), "| franja:", franja)
assert franja != "roja" || text::require_text("VENDA SOB PRESCRIÇÃO"),
"medicamento de franja roja sin el texto obligatorio"
}
data::query_postal_code(codigo_postal)
Consulta ceps.csv. Acepta el código postal brasileño con o sin guion; exige
8 dígitos.
datos/ceps.csv:
cep,logradouro,bairro,cidade,uf
01310100,Avenida Paulista,Bela Vista,Sao Paulo,SP
check "Dirección del fabricante" {
direccion = data::query_postal_code("01310-100")
assert direccion, "código postal no encontrado en la base"
print("vía:", direccion.get(2))
print("ciudad:", direccion.get(4), "-", direccion.get(5))
}
data::validate_address(codigo_postal, "fragmento")
Comprueba si el fragmento indicado aparece en la dirección de ese código postal.
check "La dirección impresa coincide con el código postal" {
// La dirección del envase tiene que coincidir con el código postal declarado
assert data::validate_address("01310100", "Avenida Paulista"),
"la dirección impresa no corresponde al código postal indicado"
}
9.5 Ejemplo completo
// prospecto_con_bases.pdfl — validación cruzando el PDF con bases locales
// Uso: PDFL_DATA_DIR=./bases pdfl run prospecto_con_bases.pdfl prospecto.pdf
profile "prospecto-con-referencias" {
check "Términos obligatorios del regulador" tags: ["glosario"] {
faltan = data::validate_against_reference("bases/terminos_regulador.txt")
assert faltan.length == 0,
"textos obligatorios ausentes: #{faltan.join("; ")}"
}
check "Producto en la base" tags: ["datos", "critico"] {
codigo = codes::decode_barcode(1)
producto = data::query_gtin(codigo)
assert producto, "GTIN #{codigo} no homologado"
// El nombre de la base tiene que aparecer impreso en el prospecto
nombre = producto.get(2)
assert text::require_text(nombre),
"el nombre '#{nombre}' de la base no aparece en el prospecto"
print("producto comprobado:", nombre)
}
check "Registro y franja" tags: ["regulador"] {
registro = text::extract_from_region(1, region(50, 780, 200, 15)).trim()
med = data::query_medicamento(registro)
assert med, "registro #{registro} no encontrado"
assert med.get(4) != "roja" || text::require_text("VENDA SOB PRESCRIÇÃO"),
"la franja roja exige el texto de prescripción"
}
check "Dirección del fabricante" tags: ["datos"] {
assert data::validate_address("01310100", "Avenida Paulista"),
"la dirección del fabricante no coincide con el código postal"
}
}