logistic-it
1. Descripción general
Según el pom.xml, el proyecto se describe como "Logistic ITALY". Es un microservicio batch (runner) que procesa pedidos pendientes con destino Italia y los envía a la API SOAP de LogSolutions/Asendia. Contiene además un segundo runner de alta masiva de artículos/SKU que, según el propio CLAUDE.md, debería estar desactivado — pero en el código actual ambos runners están activos simultáneamente, lo que probablemente impide que el runner principal de pedidos llegue a ejecutarse (ver hallazgo crítico en la sección 13).
2. Información técnica
| Campo | Valor |
|---|---|
artifactId | logistic-it |
groupId | com.hawkersco |
version | 1.0.25 |
| Java | 25 |
| Spring Boot | 4.0.6 |
| Tipo de artefacto | jar (ejecutable, Spring Boot batch/CLI, spring.main.web-application-type=none) |
| Módulos | No aplica (proyecto de módulo único) |
3. Arquitectura y diseño
No es una API REST: es una aplicación Spring Boot CLI con 2 CommandLineRunner, ambos activos (ver hallazgo crítico en la sección 13).
Orden (getOrder()) | Runner | Estado real | Propósito |
|---|---|---|---|
| 0 | LogisticItCreateProductsRunner | Activo (@Component sin comentar) | Lee csv/ns.csv y crea artículos en LogSolutions vía SOAP; termina con System.exit(0) al final |
| 1 | LogisticItRunner | Activo, pero solo se alcanza si el runner de orden 0 no ha detenido ya el proceso | Procesa pedidos pendientes de Italia y los envía a LogSolutions |
.config—LogisticItConfiguration(beans manuales delogistics-commons+PersistenceManagedTypes),LogisticItConst(namespaces SOAP, código de transportistaASENDIA_IT,AUTH_TOKENhardcodeado en código fuente,CUSTOMER_REFERENCE,STORE_CODE)..utils—LogisticItUtils(construcción del SOAPCreateOrderRequest, distribución de campos de dirección con límite de 35 caracteres, marshalling JAXB a XML, manejo de éxito/error con subida a GCS).
flowchart TD
A["Orden 0: LogisticItCreateProductsRunner<br/>ACTIVO"] -->|lee csv/ns.csv| B[LogSolutions SOAP · createArticle]
A -->|"System.exit(0) SIEMPRE al terminar"| C[Fin del proceso]
D["Orden 1: LogisticItRunner<br/>pedidos pendientes Italia"] -.->|"nunca se alcanza si A ya detuvo el proceso"| E[LogSolutions SOAP · createOrder]
E -->|éxito/error| F[GCS pi-logistics-segment]
Flujo previsto (documentado) del runner principal: obtiene pedidos no procesados vía OrderService, omite pedidos de prueba y SKUs de tipo donación (NORTHWEEKCARE, S00233), convierte cada pedido a CreateOrderRequest SOAP, lo envía a LogSolutions, sube petición/respuesta a GCS, y reintenta hasta 4 veces antes de notificar a Slack. En la práctica, este flujo puede no llegar a ejecutarse nunca en una invocación dada del CronJob, ver hallazgo crítico en la sección 13.
4. Dependencias principales
| Dependencia | Propósito |
|---|---|
spring-boot-starter | Núcleo de Spring Boot (sin web, es un runner CLI) |
com.opencsv:opencsv | Parseo del CSV de alta de artículos |
com.hawkersco:logsolution-client | Cliente @HttpExchange + POJOs JAXB para la API SOAP de LogSolutions |
com.hawkersco:logistics-commons | Entidades JPA (Order, OrderLine, OrderError) y servicios |
com.hawkersco:slack-client | Notificaciones de error |
com.hawkersco:pi-function-commons | DateUtils, StorageUtils |
com.fasterxml.jackson.core:jackson-databind + tools.jackson.core:jackson-databind | Ambas variantes de Jackson databind declaradas simultáneamente (ver hallazgo en la sección 13) |
5. API / Endpoints
No aplica a este proyecto. Es un batch/runner sin capa REST.
6. Integraciones externas
| Sistema | Protocolo | Dirección | Detalle |
|---|---|---|---|
LogSolutions/Asendia SOAP API (soap.myasendia.it) | SOAP sobre HTTP (LogSolutionClient) | Saliente | Alta de artículos (createArticle, runner 0) y envío de pedidos (createOrder, runner 1) |
Google Cloud Storage (bucket pi-logistics-segment) | API de GCS | Saliente | Archivado de peticiones/respuestas del envío de pedidos |
| Slack | HTTP (SlackClient) | Saliente | Alerta tras 4 intentos fallidos de envío de pedido |
PostgreSQL (logistics) | JDBC | Entrante/Saliente | Lectura de pedidos pendientes y actualización de estado |
7. Configuración
En producción (application-pro.properties) las credenciales llegan por variables de entorno inyectadas como Secret de Kubernetes; en local (application.properties) el repositorio contiene actualmente valores reales hardcodeados (ver alerta de seguridad).
| Clave | Descripción |
|---|---|
spring.datasource.* | Credenciales de la BD logistics |
gcs.bucket.name | Bucket de GCS para archivado |
logsolution.auth-logistic.client.url | URL de la API SOAP de LogSolutions (con la URL de preproducción comentada en el propio fichero) |
slack.client.url / .auth.token / .channel.id / .channel-atc.id | Configuración de Slack |
hawkers.orders.test | Marcador de pedidos de prueba |
⚠️ Alerta de seguridad
- Token de autenticación de LogSolutions hardcodeado en código fuente Java: la constante
LogisticItConst.AUTH_TOKENcontiene el token real de autenticación SOAP directamente en el código, no en un fichero de propiedades. A diferencia de una credencial enapplication.properties, este valor no puede rotarse sin modificar el código y volver a desplegar — mismo patrón de severidad detectado enpi-generate-credentials,logistic-erpyupdate-pim-weben este ecosistema. No se ha reproducido en este documento. - El fichero
src/main/resources/application.properties(perfil local) contiene además la contraseña real en texto plano de la base de datos PostgreSQLlogistics(la misma ya señalada como expuesta en múltiples proyectos de este ecosistema) y el token de bot de Slack.
Se recomienda:
- Mover
AUTH_TOKENdeLogisticItConsta una propiedad inyectada por variable de entorno. - Rotar el token de LogSolutions, la contraseña de BD y el token de Slack.
- Sustituir los valores hardcodeados de
application.propertiespor credenciales de un entorno de desarrollo aislado.
8. Persistencia
Base de datos PostgreSQL logistics (spring.jpa.hibernate.ddl-auto=none). Entidades relevantes: Order, OrderLine, OrderError. Entity scan vía PersistenceManagedTypesScanner (reemplazo de @EntityScan, eliminado en Spring Boot 4). No hay Flyway/Liquibase en este repositorio.
9. Procesos programados y mensajería
No hay @Scheduled ni listeners de colas: la periodicidad la impone el CronJob de Kubernetes (k8s/cronjob.yaml), que ejecuta el contenedor cada 5 minutos (schedule: "0/5 * * * *", concurrencyPolicy: Forbid). Los 2 runners descritos en la sección 3 se ejecutan en orden, pero ver el hallazgo crítico de la sección 13 sobre si el segundo llega a ejecutarse.
10. Ejecución en local
Requisitos previos: JDK 25, Maven, acceso a la BD logistics y credenciales válidas de LogSolutions.
# Compilar sin tests
mvn -B -DskipTests clean install
# Ejecutar tests
mvn test
# Build Docker
docker build -t logistic-it:latest .
# Ejecutar contenedor
docker run -Xmx256m logistic-it:latest
Al ser un CommandLineRunner, no expone Actuator/health: la verificación se hace revisando el log de consola o el estado de los pedidos en la BD logistics.
11. Despliegue
- Imagen: construida con
jib-maven-plugin(baseeclipse-temurin:25-jre,containerizingMode=packaged), publicada eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/logistic-it:<tag>. No hay configuración deextraDirectoriesen el plugin Jib para incluir el directoriocsv/del repositorio en la imagen — sicsv/ns.csvno se empaqueta explícitamente,LogisticItCreateProductsRunnerfallaría conFileNotFoundExceptional intentar leerlo en producción (ver hallazgo en la sección 13). - Orquestación: Kubernetes
CronJoben el clúster GKEpi-cluster-hw, namespacepi, con credenciales de cuenta de servicio de GCP montadas por volumen, ejecutándose cada 5 minutos. - CI/CD (Jenkins): pipeline real de 3 etapas —
Checkout→Build & Push→Deploy to GKE. ElCLAUDE.mdmenciona análisis de SonarQube y escaneo KICS que no aparecen en elJenkinsfileactual.
Job de Jenkins: https://jenkins-pi.hawkersco.net/job/logistic-it/
12. Manejo de errores y logging
No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). LogisticItCreateProductsRunner solo envuelve en try/catch la llamada a createArticle por SKU (no la apertura del fichero CSV, que puede lanzar una excepción no capturada si el fichero no existe). LogisticItRunner incrementa un contador de reintentos por pedido y notifica a Slack tras 4 fallos. Logging mediante java.util.logging.Logger estándar (consola).
13. Notas y consideraciones
⚠️ Hallazgo crítico: dos runners activos simultáneamente, contradiciendo la propia instrucción del CLAUDE.md
El CLAUDE.md de este proyecto es explícito: "Two CommandLineRunners — only one active at a time (the other has @Component commented out)" y ofrece instrucciones para alternar manualmente cuál debe estar activo ("Toggle @Component on LogisticItRunner vs LogisticItUtilsRunner. Only one should be active at a time."). Sin embargo, en el código real ambas clases tienen la anotación @Component presente y sin comentar: LogisticItRunner (orden 1) y LogisticItCreateProductsRunner (orden 0 — nótese además que el nombre real de la clase no coincide con el LogisticItUtilsRunner citado en CLAUDE.md).
Dado que LogisticItCreateProductsRunner tiene orden 0 (se ejecuta primero) y llama incondicionalmente a System.exit(0) al final de su run() — igual que se ha detectado en el proyecto hermano logistic-gr — es muy probable que, en cada invocación del CronJob, el proceso: (1) lea csv/ns.csv, (2) cree artículos en LogSolutions, (3) llame a System.exit(0), y (4) nunca llegue a ejecutar LogisticItRunner, que es el runner que efectivamente procesa y envía los pedidos de Italia — el propósito principal del servicio según su propio pom.xml.
Como agravante adicional: si el fichero csv/ns.csv no está presente en el contenedor de producción (no hay extraDirectories configurado en el plugin Jib para incluirlo, ver sección 11), LogisticItCreateProductsRunner lanzaría una FileNotFoundException no capturada al intentar abrirlo, lo cual también puede abortar la cadena de ejecución de los CommandLineRunner de Spring Boot antes de llegar a LogisticItRunner.
En cualquiera de los dos escenarios (éxito silencioso con salida limpia, o excepción no capturada), el resultado observable es el mismo: los pedidos de Italia podrían no estarse enviando a LogSolutions. Se recomienda verificar con el equipo si hay pedidos con destino Italia acumulados sin avanzar de estado PENDING_SHIPMENT, y de confirmarse el problema, quitar la anotación @Component de LogisticItCreateProductsRunner (o eliminar su llamada a System.exit(0)) con prioridad alta — exactamente la misma recomendación aplicable a logistic-gr.
Otros hallazgos
AUTH_TOKENhardcodeado en código fuente: ver alerta de seguridad en la sección 7.- Doble dependencia de Jackson databind: el
pom.xmldeclara tantocom.fasterxml.jackson.core:jackson-databindcomotools.jackson.core:jackson-databind(el segundo es el nuevo groupId de Jackson 3.x) simultáneamente — es probable que sea un resto de una migración de versión de Jackson sin completar, o una dependencia añadida por error; convendría verificar cuál de las dos se usa realmente en tiempo de ejecución. - Nombre de fichero CSV inconsistente con
CLAUDE.md: el documento existente mencionacsv/new.csv, pero el código real referenciacsv/ns.csv(y en el repositorio también existe un tercer fichero,csv/newSkus.csv, sin ninguna referencia en el código Java actual). - El resto de la arquitectura descrita en
CLAUDE.md(construcción del SOAPCreateOrderRequest, distribución de campos de dirección a 35 caracteres, reintentos y alerta Slack) coincide conLogisticItUtils, verificado directamente. - Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas.