Privalia Client
1. Descripción general
privalia-client es una librería cliente reutilizable (JAR) que encapsula la comunicación con la API logística de Privalia, marketplace/proveedor logístico integrado en el ecosistema de microservicios de Hawkers. El proyecto no expone ningún endpoint REST propio; se publica en el registro de artefactos Maven interno y es consumido por otros microservicios que necesiten consultar operaciones, lotes (batches), órdenes de entrega y paquetes, así como generar etiquetas de envío (PDF y Zebra/ZPL) en el sistema de Privalia.
La librería gestiona de forma transparente la autenticación mediante token Bearer (con caché de 30 minutos), de modo que los servicios consumidores no necesitan implementar ninguna lógica de autenticación.
2. Información técnica
| Propiedad | Valor |
|---|---|
artifactId | privalia-client |
groupId | com.hawkersco |
version | 1.0.25-SNAPSHOT |
| Java | 25 |
| Spring Boot | 4.0.6 |
| Tipo de artefacto | JAR (librería, no ejecutable) |
| Módulos | Proyecto único (no multi-módulo) |
3. Arquitectura y diseño
Estructura del proyecto:
com.hawkersco.privaliaclient
├── PrivaliaClientApplication.java # Clase @SpringBootApplication (residual, sin lógica)
├── client/
│ ├── PrivaliaClient.java # Interfaz @HttpExchange con las 11 operaciones de negocio
│ └── PrivaliaTokenClient.java # Interfaz @HttpExchange para /auth/login
├── config/
│ ├── PrivaliaClientConfig.java # @AutoConfiguration principal (interceptor Bearer + caché 30 min)
│ ├── PrivaliaTokenClientConfig.java # @AutoConfiguration del cliente de token
│ ├── PrivaliaCredentialsProperties.java # @ConfigurationProperties record (username/password)
│ ├── PrivaliaClientConst.java # Constantes de cabeceras y content-types
│ └── CacheStore.java # Cache genérica en memoria (Guava)
└── models/
├── PrivaliaTokenRequest.java / PrivaliaTokenResponse.java
├── PrivaliaOperationsResponse.java, PrivaliaBatchesResponse.java
├── PrivaliaDeliveryOrdersResponse.java, PrivaliaParcelResponse.java
└── PrivaliaLabelResponse.java
Flujo principal de autenticación y llamada
sequenceDiagram
participant Consumidor as Microservicio consumidor
participant Client as PrivaliaClient
participant Cache as CacheStore
participant TokenClient as PrivaliaTokenClient
participant API as API Privalia
Consumidor->>Client: getOperations / getBatches / generateLabel / ...
Client->>Cache: get("token")
alt Token en caché (< 30 min)
Cache-->>Client: token válido
else Token ausente o expirado
Client->>TokenClient: getToken(userName, password)
TokenClient->>API: POST /auth/login
API-->>TokenClient: { "access_token": "...", "expires_in": ... }
TokenClient-->>Client: token
Client->>Cache: add("token", token)
end
Client->>API: request + Authorization: Bearer <token> + Content-Type: application/json-patch+json
API-->>Client: respuesta JSON
Client-->>Consumidor: ResponseEntity<T>
PrivaliaTokenClientConfig se activa condicionalmente con @ConditionalOnProperty(prefix = "privalia.credentials", name = "url"), registrando PrivaliaTokenClient con un RestClient sin interceptor de auth (solo cabecera Content-Type: application/json por defecto).
PrivaliaClientConfig (@AutoConfiguration(after = PrivaliaTokenClientConfig.class), misma condición sobre privalia.credentials.url) registra PrivaliaClient con un RestClient cuyo requestInterceptor resuelve el token (desde caché de 30 minutos o pidiendo uno nuevo) e inyecta las cabeceras Authorization: Bearer <token> y Content-Type: application/json-patch+json en cada llamada. Usa @EnableConfigurationProperties(PrivaliaCredentialsProperties.class) para vincular privalia.credentials.token.username/password mediante un record de configuración (PrivaliaCredentialsProperties), en lugar de @Value individuales como en otros clientes del ecosistema.
El registro de ambas autoconfiguraciones se realiza mediante el fichero estándar de Spring Boot:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
4. Dependencias principales
| Dependencia | Versión | Propósito |
|---|---|---|
spring-boot-starter | (gestionada SB4) | Base de Spring Boot (contexto, autoconfiguración, @ConfigurationProperties) |
spring-web | (gestionada SB4) | RestClient + @HttpExchange / HttpServiceProxyFactory |
com.google.guava:guava | 33.4.0-jre | Implementación de caché en CacheStore (CacheBuilder) |
com.google.code.gson:gson | (gestionada SB4) | Anotaciones @SerializedName en modelos de token (soporte dual con Jackson) |
com.fasterxml.jackson.core:jackson-annotations | (gestionada SB4) | Anotaciones @JsonProperty en modelos de token |
org.projectlombok:lombok | 1.18.42 (dependencia; annotationProcessorPath del compilador usa 1.18.46) | Generación de boilerplate en los modelos |
spring-boot-starter-test | (gestionada SB4) | Testing (scope test) |
5. API / Endpoints
No aplica a este proyecto. privalia-client es una librería cliente JAR que no expone endpoints REST propios. Las operaciones que encapsula sobre la API de Privalia se detallan en la sección 6.
6. Integraciones externas
API logística de Privalia
| Método cliente | HTTP | Ruta remota | Descripción |
|---|---|---|---|
getOperations | GET | /operations | Lista todas las operaciones logísticas disponibles |
getParcels | GET | /parcels/operation/{operationId} | Consulta los paquetes de una operación (respuesta cruda String) |
createParcel | POST | /parcels/operation/{operationId}/batch/{batchId} | Crea un paquete para un lote de una operación |
getParcel | GET | /parcels/operation/{operationId}/batch/{batchId}/deliveryOrder/{deliveryOrder} | Consulta los paquetes de una orden de entrega concreta |
getBatches | GET | /operations/{operationId}/batches?deliveryOrderStatus=Available | Lista los lotes disponibles de una operación |
getBatch | GET | /operations/{operationId}/batches/{batchId} | Consulta un lote (respuesta cruda String) |
getDeliveryOrders | GET | /operations/{operationId}/batches/{batchId} | Consulta las órdenes de entrega de un lote (misma ruta que getBatch, tipado) |
getDeliveryOrder | GET | /operations/{operationId}/batches/{batchId}/deliveryOrder/{deliveryOrder} | Consulta una orden de entrega concreta |
generateLabel | POST | /parcels/.../deliveryOrder/{deliveryOrder}/label?labelType=Pdf&labelDpi=203 | Genera la etiqueta PDF de una orden de entrega |
reGenerateLabel | POST | /parcels/.../parcel/{parcelId}/label?labelType=Pdf&labelDpi=203 | Regenera la etiqueta PDF de un paquete concreto |
generateLabelZpl | POST | /parcels/.../deliveryOrder/{deliveryOrder}/label?labelType=Zebra | Genera la etiqueta en formato Zebra/ZPL de una orden de entrega |
reGenerateLabelZpl | POST | /parcels/.../parcel/{parcelId}/label?labelType=Zebra | Regenera la etiqueta Zebra/ZPL de un paquete concreto |
El endpoint de autenticación es POST /auth/login (vía PrivaliaTokenClient), con userName/password en el body JSON.
Ejemplo de respuesta getOperations (PrivaliaOperationsResponse[]):
[
{
"code": "OP-2026-07",
"beginDate": "2026-07-01T00:00:00Z",
"endDate": "2026-07-31T23:59:59Z",
"status": "Active",
"modes": ["Standard"],
"warehouseID": "WH01"
}
]
Ejemplo de respuesta generateLabel/generateLabelZpl (PrivaliaLabelResponse):
{
"id": 123456,
"fileContents": "<contenido en base64>",
"contentType": "application/pdf",
"fileName": "label-123456.pdf"
}
Protocolo: HTTPS REST (JSON, Content-Type: application/json-patch+json en las llamadas de negocio). Autenticación: Bearer token (obtenido dinámicamente vía /auth/login, cacheado 30 minutos).
7. Configuración
No se incluye ningún application.properties/application.yml en la librería. Las propiedades deben ser inyectadas por la aplicación consumidora.
Propiedades requeridas
| Propiedad | Descripción | Ejemplo de valor |
|---|---|---|
privalia.credentials.url | URL base de la API de Privalia (activa ambas autoconfiguraciones) | ${PRIVALIA_URL} |
privalia.credentials.token.username | Usuario para autenticación en Privalia | ${PRIVALIA_USERNAME} |
privalia.credentials.token.password | Contraseña para autenticación en Privalia | ${PRIVALIA_PASSWORD} |
privalia.credentials.token.username/password se vinculan mediante el record @ConfigurationProperties PrivaliaCredentialsProperties (prefijo privalia.credentials.token).
Importante: Si
privalia.credentials.urlno está definida, niPrivaliaClientniPrivaliaTokenClientse registran (condición@ConditionalOnPropertyen ambas autoconfiguraciones).
Variables de entorno recomendadas
| Variable | Propiedad mapeada |
|---|---|
PRIVALIA_URL | privalia.credentials.url |
PRIVALIA_USERNAME | privalia.credentials.token.username |
PRIVALIA_PASSWORD | privalia.credentials.token.password |
8. Persistencia
No aplica a este proyecto. La librería no accede a ninguna base de datos. El único estado que persiste en memoria es la caché del token Bearer (CacheStore, TTL 30 minutos, backend Guava).
9. Procesos programados y mensajería
No aplica a este proyecto. No existen jobs @Scheduled, listeners de colas/topics ni runners batch.
10. Ejecución en local
privalia-client es una librería JAR, no una aplicación ejecutable. No tiene servidor embebido ni endpoint de health.
Requisitos previos
- Java 25
- Maven 3.x
- Acceso al registro de artefactos Maven interno (
europe-west3-maven.pkg.dev/pi-saldum/pi-repo-maven) para resolver/publicar dependencias.
Compilar e instalar en repositorio local
# Compilar sin tests
mvn clean install -DskipTests
# Compilar con tests
mvn clean install
# Ejecutar un test concreto
mvn test -Dtest=MyTestClass
mvn test -Dtest=MyTestClass#myMethod
Uso como dependencia en un microservicio consumidor
<dependency>
<groupId>com.hawkersco</groupId>
<artifactId>privalia-client</artifactId>
<version>1.0.25-SNAPSHOT</version>
</dependency>
La autoconfiguración se activa automáticamente al declarar privalia.credentials.url (y las credenciales de token) en la aplicación consumidora.
11. Despliegue
El pipeline de Jenkins (Jenkinsfile) consta de dos etapas:
- Checkout — descarga el código del repositorio.
- Publish to Artifact Registry — ejecuta
mvn deploy -DskipTestspara publicar el JAR en Google Artifact Registry.
CLAUDE.md documenta un pipeline de tres etapas (Build, SonarQube, Clean), que no se corresponde con el Jenkinsfile actual del repositorio (dos etapas: Checkout y Publish to Artifact Registry). Se documenta el Jenkinsfile realmente presente.
| Parámetro | Valor |
|---|---|
| JDK | JDK25 (tool Jenkins) |
| Maven | Maven3 (tool Jenkins) |
| Repositorio | europe-west3-maven.pkg.dev/pi-saldum/pi-repo-maven (Artifact Registry GCP) |
No existe Dockerfile ni despliegue como servicio independiente; el artefacto es un JAR publicado en el registro Maven.
Job de Jenkins:
https://jenkins-pi.hawkersco.net/job/privalia-client/
12. Manejo de errores y logging
La librería no implementa ninguna estrategia propia de manejo de excepciones ni logging estructurado. Todos los métodos de PrivaliaClient y PrivaliaTokenClient declaran throws RestClientResponseException, propagando al consumidor los errores HTTP devueltos por la API de Privalia.
En resolveToken, si la respuesta de /auth/login no es 2xx o no tiene body, el método devuelve una cadena vacía ("") en lugar de lanzar una excepción — las llamadas posteriores se autenticarían con Authorization: Bearer (token vacío), fallando con un error HTTP del lado de Privalia en vez de fallar explícitamente en el cliente. Mismo patrón observado en hk-timeslogistics-client y meli-client.
No hay configuración de logback ni de niveles de log específicos en la librería.
13. Notas y consideraciones
-
getBatchygetDeliveryOrderscomparten exactamente la misma ruta: Ambos métodos apuntan aGET /operations/{operationId}/batches/{batchId}, difiriendo únicamente en el tipo de retorno (ResponseEntity<String>crudo vs.ResponseEntity<PrivaliaDeliveryOrdersResponse>tipado). El consumidor debe elegir el método según si necesita el JSON crudo o el modelo deserializado, pese a ser semánticamente la misma llamada HTTP. -
Fallo silencioso en
resolveToken: Igual que enhk-timeslogistics-clientymeli-client, si la autenticación falla,resolveTokendevuelve""en lugar de propagar una excepción, dificultando el diagnóstico de errores de autenticación. -
PrivaliaCredentialsPropertiescomo record@ConfigurationProperties: A diferencia del resto de clientes del ecosistema (que usan@Valueindividuales para cada propiedad de credenciales), este proyecto vinculausername/passwordmediante un record anotado con@ConfigurationProperties, habilitado explícitamente con@EnableConfigurationProperties. Patrón más idiomático de Spring Boot pero inconsistente con el resto de clientes documentados. -
Endpoints de generación/regeneración de etiqueta con nombres asimétricos:
generateLabel/generateLabelZploperan sobre unadeliveryOrder, mientras quereGenerateLabel/reGenerateLabelZploperan sobre unparcelId— la distinción conceptual entre "generar por orden de entrega" y "regenerar por paquete" no está documentada explícitamente en el código; pendiente de verificar contra la documentación oficial de la API de Privalia. -
labelDpi=203hardcodeado engenerateLabel/reGenerateLabel: El parámetro de resolución de impresión está fijado en la anotación@PostExchangey no es configurable desde el método; cualquier necesidad de otra resolución requeriría modificar el código de la librería. -
Uso de
java.util.Dateen modelos:PrivaliaOperationsResponseyPrivaliaBatchesResponseusanjava.util.Dateen lugar dejava.time.LocalDate/LocalDateTime, inconsistente con el resto de clientes del ecosistema que tienden a usarStringo tipos dejava.timepara fechas. -
Sin tests implementados: No existe directorio
src/test/en el proyecto. -
PrivaliaClientApplication.java: Clase principal de Spring Boot en el paquete raíz, sin funcionalidad operativa. Artefacto residual de la generación inicial del proyecto con Spring Initializr, mismo patrón observado en otros clientes del ecosistema.