Skip to main content

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

CampoValor
artifactIdlogistic-it
groupIdcom.hawkersco
version1.0.25
Java25
Spring Boot4.0.6
Tipo de artefactojar (ejecutable, Spring Boot batch/CLI, spring.main.web-application-type=none)
MódulosNo 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())RunnerEstado realPropósito
0LogisticItCreateProductsRunnerActivo (@Component sin comentar)Lee csv/ns.csv y crea artículos en LogSolutions vía SOAP; termina con System.exit(0) al final
1LogisticItRunnerActivo, pero solo se alcanza si el runner de orden 0 no ha detenido ya el procesoProcesa pedidos pendientes de Italia y los envía a LogSolutions
  • .configLogisticItConfiguration (beans manuales de logistics-commons + PersistenceManagedTypes), LogisticItConst (namespaces SOAP, código de transportista ASENDIA_IT, AUTH_TOKEN hardcodeado en código fuente, CUSTOMER_REFERENCE, STORE_CODE).
  • .utilsLogisticItUtils (construcción del SOAP CreateOrderRequest, 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

DependenciaPropósito
spring-boot-starterNúcleo de Spring Boot (sin web, es un runner CLI)
com.opencsv:opencsvParseo del CSV de alta de artículos
com.hawkersco:logsolution-clientCliente @HttpExchange + POJOs JAXB para la API SOAP de LogSolutions
com.hawkersco:logistics-commonsEntidades JPA (Order, OrderLine, OrderError) y servicios
com.hawkersco:slack-clientNotificaciones de error
com.hawkersco:pi-function-commonsDateUtils, StorageUtils
com.fasterxml.jackson.core:jackson-databind + tools.jackson.core:jackson-databindAmbas 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

SistemaProtocoloDirecciónDetalle
LogSolutions/Asendia SOAP API (soap.myasendia.it)SOAP sobre HTTP (LogSolutionClient)SalienteAlta de artículos (createArticle, runner 0) y envío de pedidos (createOrder, runner 1)
Google Cloud Storage (bucket pi-logistics-segment)API de GCSSalienteArchivado de peticiones/respuestas del envío de pedidos
SlackHTTP (SlackClient)SalienteAlerta tras 4 intentos fallidos de envío de pedido
PostgreSQL (logistics)JDBCEntrante/SalienteLectura 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).

ClaveDescripción
spring.datasource.*Credenciales de la BD logistics
gcs.bucket.nameBucket de GCS para archivado
logsolution.auth-logistic.client.urlURL 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.idConfiguración de Slack
hawkers.orders.testMarcador de pedidos de prueba

⚠️ Alerta de seguridad

  • Token de autenticación de LogSolutions hardcodeado en código fuente Java: la constante LogisticItConst.AUTH_TOKEN contiene el token real de autenticación SOAP directamente en el código, no en un fichero de propiedades. A diferencia de una credencial en application.properties, este valor no puede rotarse sin modificar el código y volver a desplegar — mismo patrón de severidad detectado en pi-generate-credentials, logistic-erp y update-pim-web en 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 PostgreSQL logistics (la misma ya señalada como expuesta en múltiples proyectos de este ecosistema) y el token de bot de Slack.

Se recomienda:

  1. Mover AUTH_TOKEN de LogisticItConst a una propiedad inyectada por variable de entorno.
  2. Rotar el token de LogSolutions, la contraseña de BD y el token de Slack.
  3. Sustituir los valores hardcodeados de application.properties por 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 (base eclipse-temurin:25-jre, containerizingMode=packaged), publicada en europe-west3-docker.pkg.dev/pi-saldum/pi-repo/logistic-it:<tag>. No hay configuración de extraDirectories en el plugin Jib para incluir el directorio csv/ del repositorio en la imagen — si csv/ns.csv no se empaqueta explícitamente, LogisticItCreateProductsRunner fallaría con FileNotFoundException al intentar leerlo en producción (ver hallazgo en la sección 13).
  • Orquestación: Kubernetes CronJob en el clúster GKE pi-cluster-hw, namespace pi, con credenciales de cuenta de servicio de GCP montadas por volumen, ejecutándose cada 5 minutos.
  • CI/CD (Jenkins): pipeline real de 3 etapas — CheckoutBuild & PushDeploy to GKE. El CLAUDE.md menciona análisis de SonarQube y escaneo KICS que no aparecen en el Jenkinsfile actual.

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_TOKEN hardcodeado en código fuente: ver alerta de seguridad en la sección 7.
  • Doble dependencia de Jackson databind: el pom.xml declara tanto com.fasterxml.jackson.core:jackson-databind como tools.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 menciona csv/new.csv, pero el código real referencia csv/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 SOAP CreateOrderRequest, distribución de campos de dirección a 35 caracteres, reintentos y alerta Slack) coincide con LogisticItUtils, verificado directamente.
  • Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas.