7. Espacio de nombres codes:: — códigos de barras y QR

prepress:: · Índice · Siguiente: fix::

13 funciones para detectar, decodificar y validar los códigos de barras y los códigos QR impresos en el documento.

El escaneo renderiza las páginas en alta resolución y se ejecuta una sola vez, en el primer uso de cualquier función codes::. Los scripts que no usan el espacio de nombres no pagan ese coste.

Formatos reconocidos: EAN-8/13, UPC-A/E, Code 128, Code 39, ITF, código QR, Data Matrix, Aztec y PDF417, entre otros.


7.1 Detección

codes::detect_barcodes() y codes::detect_qrcodes()

check "El envase lleva código" {
  assert codes::detect_barcodes(),
    "no se encontró ningún código de barras en el arte"

  // QR de trazabilidad
  assert codes::detect_qrcodes(),
    "falta el código QR de trazabilidad"
}

codes::count_barcodes()

Cantidad total de códigos detectados (barras + QR).

check "Cantidad de códigos" {
  total = codes::count_barcodes()
  print("códigos detectados:", total)

  assert total == 2,
    "esperaba 2 códigos (EAN + QR) y encontré #{total}"
}

codes::get_barcode_type(n)

Formato del enésimo código (1-based): "EAN_13", "QR_CODE", "CODE_128"...

check "Tipo del código principal" {
  tipo = codes::get_barcode_type(1)
  assert tipo == "EAN_13",
    "el código principal debería ser EAN-13, y es #{tipo}"
}

check "Listándolos todos" {
  // Recorre por índices, del 1 hasta la cantidad
  print("primero:", codes::get_barcode_type(1))
  print("segundo:", codes::get_barcode_type(2))
}

codes::get_barcode_location(n)

Dónde está el código: [pagina, x, y] en puntos (origen en la esquina inferior izquierda).

check "Posición del código" {
  lugar = codes::get_barcode_location(1)
  print("página:", lugar.get(1), "x:", lugar.get(2), "y:", lugar.get(3))

  // El código debe estar en la primera página
  assert lugar.first() == 1,
    "el código de barras no está en la portada"
}

7.2 Decodificación y validación

codes::decode_barcode(n)

El contenido decodificado del enésimo código.

check "Contenido del código" {
  codigo = codes::decode_barcode(1)
  print("código leído:", codigo)
  assert codigo.starts_with("789"),
    "el GTIN no es brasileño (debería empezar por 789): #{codigo}"
}

codes::validate_barcode_checksum(n)

Valida el dígito verificador GTIN del enésimo código detectado.

check "Dígito verificador" {
  // Un GTIN con el dígito equivocado lo rechaza la caja del supermercado
  assert codes::validate_barcode_checksum(1),
    "dígito verificador inválido en el código #{codes::decode_barcode(1)}"
}

codes::validate_gtin(texto) y codes::validate_ean(texto)

Sinónimos. Validan el dígito verificador de una cadena (EAN-8/13, UPC-A, GTIN-14), útil para comprobar un número que viene de otra fuente.

check "GTIN indicado en el texto" {
  require codes::validate_gtin("7891234567895")
  require !codes::validate_gtin("7891234567890")   // dígito erróneo

  // Comprobando el número impreso debajo de las barras
  impreso = text::extract_from_region(1, region(400, 20, 180, 15)).trim()
  assert codes::validate_ean(impreso),
    "el número impreso no es un GTIN válido: #{impreso}"
}

codes::validate_code128()

Verdadero si hay algún Code 128 decodificado con éxito (el checksum del Code 128 se valida en la propia decodificación).

check "Código logístico" {
  assert codes::validate_code128(),
    "falta el Code 128 de logística"
}

7.3 Comprobación cruzada

codes::compare_barcode_with_text()

Verdadero si el contenido de todos los códigos aparece en el texto del documento.

Esta es la comprobación que atrapa el error más caro del sector: el código de barras apuntando a un producto y el texto impreso diciendo otro.

check "El código coincide con el texto impreso" {
  assert codes::compare_barcode_with_text(),
    "el número del código de barras no aparece en el texto — ¿arte con datos cambiados?"
}

codes::validate_barcode_format(regex)

Verdadero si el contenido de todos los códigos casa con la expresión regular.

check "Formato esperado" {
  // Solo EAN-13: exactamente 13 dígitos
  assert codes::validate_barcode_format("^\d{13}$"),
    "hay un código fuera del formato EAN-13"
}

check "El QR apunta al sitio oficial" {
  assert codes::validate_barcode_format("^https://empresa\.com/.*"),
    "el código QR apunta a una dirección no autorizada"
}

codes::validate_barcode_position(region) o (x0, y0, x1, y1)

Verdadero si todos los códigos están dentro del área. Acepta una region o cuatro números en puntos.

check "Código en el área reservada" {
  // Con una región con nombre: más legible
  area = region(400, 20, 180, 80, "área del código")
  assert codes::validate_barcode_position(area),
    "código de barras fuera del área reservada del envase"
}

check "Con coordenadas directas" {
  // x0, y0, x1, y1 en puntos
  assert codes::validate_barcode_position(400, 20, 580, 100),
    "código fuera de la posición especificada"
}

7.4 Ejemplo completo

// prospecto_farmaceutico.pdfl — validación del código de lote en un prospecto
// Uso: pdfl run prospecto_farmaceutico.pdfl prospecto.pdf
profile "prospecto-regulado" {

  check "Presencia de los códigos" tags: ["codes"] {
    assert codes::detect_barcodes(), "prospecto sin código de barras"
    assert codes::count_barcodes() >= 1,
      "esperaba al menos el EAN del producto"
  }

  check "Integridad del código" tags: ["codes"] {
    codigo = codes::decode_barcode(1)
    tipo = codes::get_barcode_type(1)
    print("código:", tipo, "=", codigo)

    assert tipo == "EAN_13", "el código principal no es EAN-13 (es #{tipo})"
    assert codes::validate_barcode_checksum(1),
      "dígito verificador inválido: #{codigo}"
    assert codigo.starts_with("789"),
      "el GTIN no es brasileño: #{codigo}"
  }

  check "Comprobación con el texto" tags: ["codes", "critico"] {
    // El error más caro: el código de un producto y el texto de otro
    assert codes::compare_barcode_with_text(),
      "el número del código no aparece en el texto del prospecto"
  }

  check "Posición en el arte" tags: ["codes", "layout"] {
    area_reservada = region(400, 20, 180, 90, "área del código")
    assert codes::validate_barcode_position(area_reservada),
      "código fuera del área reservada — puede cortarse en el acabado"
  }

  check "Cruce con la base de productos" tags: ["datos"] {
    // Se integra con data:: — consulta el capítulo 9
    codigo = codes::decode_barcode(1)
    producto = data::query_gtin(codigo)
    assert producto,
      "el GTIN #{codigo} no consta en la base de productos homologados"
    print("producto:", producto.get(2))
  }
}

prepress:: · Índice · Siguiente: fix::