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
| Campo | Valor |
|---|---|
artifactId | logistic-de |
groupId | com.hawkersco |
version | 1.0.25 |
| Java | 25 (maven.compiler.release=25) |
| Spring Boot | 4.0.6 |
| Tipo de artefacto | jar (ejecutable, Spring Boot batch/CLI) |
| 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 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..config—LogisticDeConfig(declaración manual de servicios delogistics-commons),LogisticDeConst..utils—LogisticDeUtils(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 propioCLAUDE.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
| Dependencia | Propósito |
|---|---|
spring-boot-starter | Núcleo de Spring Boot (sin web, es un runner CLI) |
com.opencsv:opencsv:5.9 | Declarada en el pom.xml; sin uso detectado en el código actual |
com.google.cloud:google-cloud-storage:2.68.0 | Cliente de GCS (declarado explícitamente, a diferencia de otros runners que lo obtienen de forma transitiva) |
com.hawkersco:sprintlogistics-client:1.0.25-SNAPSHOT | Cliente @HttpExchange/RestClient (SprintlogisticsV2Client) para la API de Sprint Logistics |
com.hawkersco:logistics-commons:1.0.25-SNAPSHOT | Entidades JPA y servicios de logística compartidos |
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOT | DateUtils, SeveralUtils (transliteración de texto), StorageUtils |
com.hawkersco:slack-client:1.0.25-SNAPSHOT | Notificaciones de error a Slack |
lombok | Generació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
| Sistema | Protocolo | Dirección | Detalle |
|---|---|---|---|
Sprint Logistics API v2 (api-v2.sprintlogistics.com) | HTTP REST (JSON, SprintlogisticsV2Client) | Saliente | Envío del pedido (sendOrder) |
Google Cloud Storage (bucket pi-logistics-segment) | API de GCS | Saliente | Auditoría de respuestas OK/KO |
| Slack | HTTP (SlackClient) | Saliente | Alertas tras 4 fallos consecutivos |
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 | Ejemplo (producción) |
|---|---|---|
spring.datasource.url / .username / .password | Credenciales de la BD logistics | ${dbLogisitcsUrl}, etc. |
gcs.bucket.name | Bucket de GCS para auditoría | pi-logistics-segment |
sprintlogistics-v2.api.url / .username / .password | Credenciales de la API de Sprint Logistics | ${sprintlogisticsApiUrl}, etc. |
slack.client.url / .auth.token / .channel.id / .channel-atc.id | Configuración del cliente Slack | ${slackClientUrl}, etc. |
hawkers.orders.test | Cadena que marca un pedido de prueba dentro de rawData | lahermanadeaxel |
⚠️ 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:
- 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. - Sustituir los valores hardcodeados de
application.propertiespor credenciales de un entorno de desarrollo aislado. - 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:
- Obtiene los pedidos EU/DE no procesados (
orderService.findByOrdersNoProcessedEuDe). - Los pedidos de test (
rawDatacontiene la cadena configurada) se marcan comoTESTy se omiten (continue, correctamente, a diferencia delbreakvisto enlogistic-au/logistic-co). - 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). - Tras 4 intentos fallidos (
nmSendLogistic >= 4), crea unOrderErrory notifica al canal Slack "ATC". - 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(baseeclipse-temurin:25-jre,containerizingMode=packaged), publicada eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/logistic-de:<tag>. ElCLAUDE.mdmenciona una imagen baseeclipse-temurin:25-jdk-alpine, que no coincide exactamente con la configuración delpom.xml(usajib-maven-pluginconeclipse-temurin:25-jre). - Orquestación: Kubernetes
CronJob(k8s/cronjob.yaml) en el clúster GKEpi-cluster-hw, namespacepi, ejecutándose cada 5 minutos,restartPolicy: OnFailure. - CI/CD (Jenkins): pipeline real de 3 etapas —
Checkout→Build & Push→Deploy to GKE. ElCLAUDE.mddescribeBuild → Test → Push → Deployment → Clean, que no coincide exactamente con las etapas reales delJenkinsfile. - Las variables sensibles se inyectan en el pod mediante un
Secretde 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.processOrderErrorinvocaorderService.updateNmSendLogistic(order.getNmSendLogistic() + 1, ...)dos veces seguidas con los mismos argumentos (líneas consecutivas idénticas). Como ambas lecturas deorder.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-auylogistic-co(donde detectar un pedido de test corta todo el lote conbreak), aquí se usacontinue, 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.LogisticDeConstyutils.LogisticDeConst) en paquetes distintos; el propioCLAUDE.mdindica que es una duplicación deliberada y no un error a corregir, aunque solo se ha verificado el uso deutils.LogisticDeConsten el código revisado. - Dependencia
opencsvsin uso: declarada en elpom.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 coninfranete-api(mismas credenciales de Sprint Logistics).