Skip to main content

logistic-de

1. Descripción general

Según el pom.xml, el proyecto se describe como "Send orders to North Europe". Es un microservicio batch (runner) que envía los pedidos pendientes del norte de Europa (findByOrdersNoProcessedEuDe) al carrier Sprint Logistics mediante su API REST v2, sube las respuestas JSON a GCS como auditoría, y notifica por Slack tras fallos repetidos.

A diferencia de logistic-au/logistic-co, este proyecto tiene un único CommandLineRunner (no hay variante de marketplace separada).

2. Información técnica

CampoValor
artifactIdlogistic-de
groupIdcom.hawkersco
version1.0.25
Java25 (maven.compiler.release=25)
Spring Boot4.0.6
Tipo de artefactojar (ejecutable, Spring Boot batch/CLI)
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 un único CommandLineRunner (LogisticDeRunner) que ejecuta todo el flujo y cierra la JVM al finalizar.

Paquetes principales:

  • com.hawkersco.logisticde — clase principal (LogisticDeApplication) y el runner.
  • .configLogisticDeConfig (declaración manual de servicios de logistics-commons), LogisticDeConst.
  • .utilsLogisticDeUtils (construcción de la petición Sprint Logistics + transiciones éxito/error), LogisticDeConst (segunda clase de constantes, con el mismo nombre pero en paquete distinto — duplicación intencional según el propio CLAUDE.md).
flowchart TD
A[LogisticDeRunner] -->|findByOrdersNoProcessedEuDe| B[(logistics · Order)]
A -->|getOrderRequestSprintLogistics| C[LogisticDeUtils]
C -->|POST JSON| D[Sprint Logistics API v2]
A -->|sube response| E[(GCS pi-logistics-segment)]
A -->|si falla 4 veces| F[Slack canal ATC]
A -->|System.exit al terminar| G[Fin del proceso]

4. Dependencias principales

DependenciaPropósito
spring-boot-starterNúcleo de Spring Boot (sin web, es un runner CLI)
com.opencsv:opencsv:5.9Declarada en el pom.xml; sin uso detectado en el código actual
com.google.cloud:google-cloud-storage:2.68.0Cliente de GCS (declarado explícitamente, a diferencia de otros runners que lo obtienen de forma transitiva)
com.hawkersco:sprintlogistics-client:1.0.25-SNAPSHOTCliente @HttpExchange/RestClient (SprintlogisticsV2Client) para la API de Sprint Logistics
com.hawkersco:logistics-commons:1.0.25-SNAPSHOTEntidades JPA y servicios de logística compartidos
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOTDateUtils, SeveralUtils (transliteración de texto), StorageUtils
com.hawkersco:slack-client:1.0.25-SNAPSHOTNotificaciones de error a Slack
lombokGeneración de código boilerplate (@Slf4j, etc.)
spring-boot-starter-test (test)JUnit 5 + Spring Test

5. API / Endpoints

No aplica a este proyecto. Es un batch/runner sin capa REST propia.

6. Integraciones externas

SistemaProtocoloDirecciónDetalle
Sprint Logistics API v2 (api-v2.sprintlogistics.com)HTTP REST (JSON, SprintlogisticsV2Client)SalienteEnvío del pedido (sendOrder)
Google Cloud Storage (bucket pi-logistics-segment)API de GCSSalienteAuditoría de respuestas OK/KO
SlackHTTP (SlackClient)SalienteAlertas tras 4 fallos consecutivos
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ónEjemplo (producción)
spring.datasource.url / .username / .passwordCredenciales de la BD logistics${dbLogisitcsUrl}, etc.
gcs.bucket.nameBucket de GCS para auditoríapi-logistics-segment
sprintlogistics-v2.api.url / .username / .passwordCredenciales de la API de Sprint Logistics${sprintlogisticsApiUrl}, etc.
slack.client.url / .auth.token / .channel.id / .channel-atc.idConfiguración del cliente Slack${slackClientUrl}, etc.
hawkers.orders.testCadena que marca un pedido de prueba dentro de rawDatalahermanadeaxel

⚠️ Alerta de seguridad

El fichero src/main/resources/application.properties (perfil local) contiene actualmente credenciales reales en texto plano: contraseña de la base de datos PostgreSQL, usuario/contraseña de la API de Sprint Logistics (las mismas credenciales ya señaladas como expuestas en infranete-api) y token de bot de Slack (xoxb-...). Ninguno de estos valores se ha reproducido en este documento. Se recomienda:

  1. Rotar la contraseña de BD, las credenciales de Sprint Logistics (coordinando la rotación con infranete-api, que usa las mismas) y el token de Slack.
  2. Sustituir los valores hardcodeados de application.properties por credenciales de un entorno de desarrollo aislado.
  3. Revisar el historial de control de versiones, ya que estas credenciales pueden seguir expuestas en commits anteriores.

8. Persistencia

Base de datos PostgreSQL (logistics), acceso vía JPA a través de la librería logistics-commons (@EnableJpaRepositories, deducido del patrón compartido con el resto de runners logistic-*). spring.jpa.hibernate.ddl-auto=none. Entidades relevantes: Order, OrderLine, OrderError. 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 * * * *"). Flujo único de LogisticDeRunner:

  1. Obtiene los pedidos EU/DE no procesados (orderService.findByOrdersNoProcessedEuDe).
  2. Los pedidos de test (rawData contiene la cadena configurada) se marcan como TEST y se omiten (continue, correctamente, a diferencia del break visto en logistic-au/logistic-co).
  3. Para cada pedido real, construye el payload de Sprint Logistics (excluyendo líneas sin SKU o de tipo DONATION), lo envía, y gestiona éxito/error. Una respuesta de error que contenga el texto de "ya existe" se trata como éxito (idempotencia).
  4. Tras 4 intentos fallidos (nmSendLogistic >= 4), crea un OrderError y notifica al canal Slack "ATC".
  5. Cierra la JVM al terminar.

10. Ejecución en local

Requisitos previos: JDK 25, Maven, acceso a la BD logistics y credenciales válidas de Sprint Logistics en un application.properties local.

# Compilar
./mvnw clean install

# Compilar sin tests
./mvnw clean install -DskipTests

# Ejecutar tests
./mvnw test

# Ejecutar la aplicación localmente
./mvnw spring-boot:run

Al ser un CommandLineRunner, no expone Actuator/health: la forma de verificar la ejecución es revisar el log de consola o los ficheros subidos a GCS.

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-de:<tag>. El CLAUDE.md menciona una imagen base eclipse-temurin:25-jdk-alpine, que no coincide exactamente con la configuración del pom.xml (usa jib-maven-plugin con eclipse-temurin:25-jre).
  • Orquestación: Kubernetes CronJob (k8s/cronjob.yaml) en el clúster GKE pi-cluster-hw, namespace pi, ejecutándose cada 5 minutos, restartPolicy: OnFailure.
  • CI/CD (Jenkins): pipeline real de 3 etapas — CheckoutBuild & PushDeploy to GKE. El CLAUDE.md describe Build → Test → Push → Deployment → Clean, que no coincide exactamente con las etapas reales del Jenkinsfile.
  • Las variables sensibles se inyectan en el pod mediante un Secret de Kubernetes llamado igual que la app (logistic-de).

Job de Jenkins: https://jenkins-pi.hawkersco.net/job/logistic-de/

12. Manejo de errores y logging

No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). Todo el bucle principal está envuelto en un try/catch genérico que registra con log.error y continúa hasta el final (cerrando igualmente la JVM). processOrder captura específicamente RestClientResponseException para distinguir el caso de "pedido ya existente" (idempotencia) de un error real. Logging mediante Lombok @Slf4j (SLF4J, consola).

13. Notas y consideraciones

  • Llamada duplicada en processOrderError: LogisticDeUtils.processOrderError invoca orderService.updateNmSendLogistic(order.getNmSendLogistic() + 1, ...) dos veces seguidas con los mismos argumentos (líneas consecutivas idénticas). Como ambas lecturas de order.getNmSendLogistic() provienen del mismo objeto en memoria (no se recarga entre llamadas), el efecto neto en base de datos es el mismo valor escrito dos veces — no se duplica el incremento — pero es claramente una línea sobrante fruto de un copiar/pegar accidental. Se recomienda eliminar la llamada duplicada por claridad, aunque no representa un bug funcional activo.
  • Pedido de test correctamente gestionado: a diferencia de logistic-au y logistic-co (donde detectar un pedido de test corta todo el lote con break), aquí se usa continue, permitiendo que el resto de pedidos reales de la misma ejecución se procesen con normalidad. Es el patrón correcto y podría usarse como referencia para corregir los otros dos proyectos.
  • Duplicación intencional de LogisticDeConst: existen dos clases con el mismo nombre (config.LogisticDeConst y utils.LogisticDeConst) en paquetes distintos; el propio CLAUDE.md indica que es una duplicación deliberada y no un error a corregir, aunque solo se ha verificado el uso de utils.LogisticDeConst en el código revisado.
  • Dependencia opencsv sin uso: declarada en el pom.xml, no se ha encontrado ningún uso de generación/lectura de CSV en el código fuente actual.
  • Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas en application.properties, compartidas con infranete-api (mismas credenciales de Sprint Logistics).