Skip to main content

logistic-erp

1. Descripción general

Según el pom.xml, el proyecto se describe como "Orders erp send to logistic from Europa". Es el runner batch más extenso de la familia logistic-*: agrupa 7 CommandLineRunner, uno por país/carrier, que envían pedidos ERP pendientes a sus respectivos proveedores logísticos. Es también el único de esta familia que genera facturas y documentos de aduana en PDF (vía Thymeleaf + iText) para determinados destinos.

2. Información técnica

CampoValor
artifactIdlogistic-erp
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 7 CommandLineRunner, todos activos, ejecutados en el orden indicado.

Paquetes principales:

  • com.hawkersco.logisticerp — clase principal (LogisticErpApplication) y los 7 runners de país.
  • .configLogisticErpConfig (declaración manual de servicios de logistics-commons).
  • .utilsLogisticErpComponent (lógica central: construcción de payloads Auro/LogSolution, generación de facturas PDF), LogisticErpUtils (validaciones, construcción de payload Cubbo, transiciones éxito/error genéricas), LogisticErpConst.
  • .modelsProductSend, SkuRow (DTOs internos).
  • Recursos: plantillas Thymeleaf (invoice.html, customs.html, mystyle.css, fuente Montserrat-Regular.ttf), JSON de códigos DANE (dane-code-alt.json, dane-code-mp.json, dane-code-fix.json) y country-zipregex.json.
flowchart TD
A["LogisticErpRunner (order=1)<br/>España/UE — Auro"] --> B[Auro API]
C["LogisticErpMxRunner (order=2)<br/>México — Cubbo"] --> D[Cubbo API]
E["LogisticItErpRunner (order=3)<br/>Italia — LogSolution/Asendia"] --> F[LogSolution SOAP]
G["LogisticGrErpRunner (order=4)<br/>Grecia — Sarmed"] --> H[Sarmed API]
I["LogisticCoErpRunner (order=5)<br/>Colombia — Servientrega"] --> J[Servientrega SOAP]
K["LogisticGbErpRunner (order=6)<br/>Reino Unido — Sprint Logistics GB"] --> L[Sprint Logistics API v2 GB]
M["LogisticDeErpRunner (order=7)<br/>Norte de Europa — Sprint Logistics"] --> N[Sprint Logistics API v2]

A -->|si isInvoice| O[Genera factura/aduana PDF<br/>Thymeleaf + iText]
O -->|SFTP| P[sftp.hawkersco.com /src/invoices/]

Cada runner reproduce el mismo patrón: obtener pedidos ERP no procesados (orderService.findByOrdersNoProcessedErp(41L) o variante específica), omitir pedidos de test, construir el payload del carrier correspondiente (LogisticErpComponent/LogisticErpUtils), enviarlo, subir el resultado a GCS y gestionar reintentos/errores.

4. Dependencias principales

DependenciaPropósito
spring-boot-starterNúcleo de Spring Boot (sin web, es un runner CLI)
spring-boot-starter-mailEnvío de correo (adjuntos de factura/aduanas para pedidos de Andorra)
spring-webRestClient/@HttpExchange
com.googlecode.json-simple:json-simpleParseo de los JSON de códigos DANE
com.hawkersco:auro-clientCliente REST para el carrier Auro (España/UE)
com.hawkersco:cubbo-clientCliente REST para el carrier Cubbo (México)
com.hawkersco:logsolution-clientCliente SOAP para LogSolution/Asendia (Italia)
com.hawkersco:sarmed-clientCliente REST para Sarmed (Grecia)
com.hawkersco:sprintlogistics-clientCliente REST para Sprint Logistics (norte de Europa y Reino Unido, con credenciales distintas por región)
com.hawkersco:servientrega-clientCliente SOAP para Servientrega (Colombia)
com.hawkersco:logistics-commonsEntidades JPA y servicios de logística compartidos
com.hawkersco:pi-function-commonsDateUtils, SftpUtils, PdfUtils, DirectoryUtils, SeveralUtils
com.hawkersco:slack-clientNotificaciones de error a Slack
lombok (versión 1.18.36, distinta de la usada en maven-compiler-plugin, 1.18.46 — ver hallazgo en la sección 13)Generación de código boilerplate
Thymeleaf (SpringTemplateEngine, transitiva) + iText (com.itextpdf)Generación de PDF de factura/aduanas
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

SistemaProtocoloRunnerDetalle
AuroHTTP RESTLogisticErpRunnerEnvío de pedido + factura/aduana adjunta en base64 para determinados países/provincias
CubboHTTP RESTLogisticErpMxRunnerCreación de pedido para México
LogSolution/AsendiaSOAPLogisticItErpRunnerEnvío de pedido, con token/referencia de cliente hardcodeados en el código (ver alerta de seguridad)
SarmedHTTP RESTLogisticGrErpRunnerEnvío de pedido para Grecia
ServientregaSOAPLogisticCoErpRunnerEnvío de pedido para Colombia (misma API que logistic-co)
Sprint Logistics API v2 (dos configuraciones: general y GB)HTTP RESTLogisticGbErpRunner, LogisticDeErpRunnerEnvío de pedido para Reino Unido y norte de Europa respectivamente, con credenciales independientes por región
SFTP (sftp.hawkersco.com)SFTPLogisticErpRunner/LogisticErpComponentSubida de facturas PDF a /src/invoices/
Google Cloud Storage (bucket pi-logistics-segment)API de GCSTodos los runnersAuditoría de requests/responses
SlackHTTP (SlackClient)Todos los runnersAlertas de fallo
PostgreSQL (logistics)JDBCTodos los runnersLectura 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 de un número muy elevado de integraciones (ver alerta de seguridad).

Clave (prefijo)Descripción
spring.datasource.*Credenciales de la BD logistics
gcs.bucket.nameBucket de GCS para auditoría
logisticerp.ftp.* / logisticerp.auro.ftp.*Credenciales SFTP para facturas y para Auro
auro.auth.client.*Credenciales de la API de Auro
cubbo.client.*Credenciales de la API de Cubbo (incluye storeid.prod, ver hallazgo en la sección 13)
sarmed.api.*Credenciales de la API de Sarmed
sprintlogistics-v2.api.* / sprintlogistics-gb-v2.api.*Credenciales de Sprint Logistics, dos configuraciones distintas
logisticco.servientrega.credentials.*Credenciales SOAP de Servientrega
logsolution.auth-logistic.client.urlURL del servicio SOAP de LogSolution (las credenciales de autenticación están hardcodeadas en el código, no en esta propiedad — ver alerta de seguridad)
slack.*Configuración del cliente Slack (varios canales)
hawkers.order.invoice.countrycode.list / .provincecode.listPaíses/provincias que requieren factura adjunta

⚠️ Alerta de seguridad (severidad crítica)

Este proyecto combina dos problemas de seguridad graves:

  1. application.properties local contiene credenciales reales de prácticamente todos los carriers logísticos de Europa/México/Colombia: contraseña de BD, credenciales SFTP (dos servidores), credenciales de Auro, credenciales de producción de Cubbo (clientid/clientsecret), credenciales de Sarmed, credenciales de Sprint Logistics (dos configuraciones, ES y GB), credenciales SOAP de Servientrega (las mismas ya señaladas como expuestas en logistic-co), y el token de bot de Slack. Ninguno de estos valores se ha reproducido en este documento.
  2. Credenciales de LogSolution hardcodeadas directamente en el código fuente, no en configuración: LogisticErpComponent declara LOGSOLUTION_AUTH_TOKEN y LOGSOLUTION_CUSTOMER_REF como constantes private static final String con valores reales tipo GUID. A diferencia de las demás credenciales (que al menos están externalizadas en application.properties/application-pro.properties y pueden rotarse vía Secret de Kubernetes sin recompilar), estas quedan fijas en el binario compilado: rotarlas exige modificar el código fuente y desplegar una nueva versión, y cualquiera con acceso al .jar/repositorio puede extraerlas sin necesidad de acceder a la configuración de despliegue.

Se recomienda con prioridad máxima:

  1. Rotar todas las credenciales listadas (Auro, Cubbo, Sarmed, Sprint Logistics ES/GB, Servientrega, Slack, BD, SFTP) y coordinar la rotación de Servientrega/Sprint Logistics con logistic-co/logistic-de/logistic-gb, que comparten las mismas credenciales.
  2. Mover LOGSOLUTION_AUTH_TOKEN y LOGSOLUTION_CUSTOMER_REF a application.properties/application-pro.properties como cualquier otra credencial, y rotarlas también.
  3. Revisar el historial de control de versiones para valorar el alcance real de la exposición.

8. Persistencia

Base de datos PostgreSQL (logistics), acceso vía JPA a través de la librería logistics-commons. spring.jpa.hibernate.ddl-auto=none. No hay Flyway/Liquibase en este repositorio. Además de las entidades habituales (Order, OrderLine, OrderError), este proyecto usa OrderLineProperty (propiedades de línea de pedido, para atributos "creativos" de gafas graduadas/personalizadas de la fuente SOURCE_ID_CREATIVES=17).

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 10 minutos (schedule: "*/10 * * * *"). Se ejecutan en orden los 7 runners:

OrdenRunnerPaís/regiónCarrier
1LogisticErpRunnerEspaña/UEAuro
2LogisticErpMxRunnerMéxicoCubbo
3LogisticItErpRunnerItaliaLogSolution/Asendia
4LogisticGrErpRunnerGreciaSarmed
5LogisticCoErpRunnerColombiaServientrega
6LogisticGbErpRunnerReino UnidoSprint Logistics (config. GB)
7LogisticDeErpRunnerNorte de EuropaSprint Logistics (config. general)

El CLAUDE.md del proyecto lista los mismos 7 runners pero deja el carrier de México y Alemania como "—" (sin especificar); se ha verificado que LogisticErpMxRunner sí usa un carrier concreto (Cubbo), y que LogisticDeErpRunner usa Sprint Logistics. El propio LogisticErpRunner (Auro) solo procesa pedidos cuyo warehouseId sea "02"/"2"; para cualquier otro almacén, no hace nada (default -> {}), sin notificar ni marcar el pedido de ningún modo.

Generación de factura (solo LogisticErpRunner/Auro): si el país de envío está en la lista de países que requieren factura (y, para España, si la provincia está en la lista de provincias forales/especiales), se genera un PDF de factura con Thymeleaf, se combina con un PDF de aduanas si el destino es Reino Unido, se codifica en base64 para incluirlo en el payload de Auro, y además se sube por SFTP a /src/invoices/.

10. Ejecución en local

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

# Compilar
./mvnw clean install

# Compilar sin tests (usado en CI)
./mvnw -B -DskipTests clean install

# Ejecutar tests
./mvnw test

# Ejecutar un test concreto
./mvnw test -Dtest=MyTestClass

# 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, los ficheros subidos a GCS, o las facturas subidas al SFTP de facturas.

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-erp:<tag>.
  • Orquestación: Kubernetes CronJob (k8s/cronjob.yaml) en el clúster GKE pi-cluster-hw, namespace pi, ejecutándose cada 10 minutos.
  • CI/CD (Jenkins): pipeline real de 3 etapas — CheckoutBuild & PushDeploy to GKE.
  • Las variables sensibles se inyectan en el pod mediante un Secret de Kubernetes llamado igual que la app (logistic-erp), con un número muy elevado de claves (una por cada carrier).

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

12. Manejo de errores y logging

No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). Cada runner captura RestClientResponseException (o la excepción SOAP equivalente) de forma independiente por pedido, notificando a Slack tras 4 reintentos fallidos (MAX_SEND_ATTEMPTS/MAX_SEND_LOGISTIC_RETRIES según el runner). createInvoiceOrderPdf/createMailAttachment capturan IOException/DocumentException con e.printStackTrace() además del log estándar — un patrón menos consistente que el resto del ecosistema, que normalmente solo usa Logger. Logging mediante java.util.logging.Logger estándar (consola).

13. Notas y consideraciones

  • Credenciales de LogSolution hardcodeadas en el código fuente: ver alerta de seguridad en la sección 7. Es el hallazgo más grave de este proyecto: a diferencia del resto de credenciales (externalizadas y rotables sin recompilar), las de LogSolution están fijas en LogisticErpComponent como constantes de clase.
  • Property cubbo.client.storeid.prod posiblemente sin efecto: LogisticErpUtils.getOrderCubboRequest fija el storeID con el literal "1720" directamente en el código (con un comentario //1687 test 1720 prod), en lugar de leerlo de la propiedad cubbo.client.storeid.prod ya declarada en application.properties. Si el valor de producción cambiase, habría que actualizarlo en dos sitios (o el cambio en la propiedad no tendría ningún efecto real).
  • LogisticErpRunner (Auro) ignora silenciosamente pedidos de almacenes no reconocidos: el switch sobre order.getWarehouseId() solo gestiona los casos "02"/"2"; cualquier otro valor cae en default -> {} sin log, sin marca de error y sin notificación — un pedido con un warehouseId inesperado queda indefinidamente sin procesar por este runner, sin ninguna señal visible de que existe.
  • Versión de Lombok inconsistente: la dependencia lombok se declara con versión 1.18.36 en el bloque <dependencies>, mientras que el annotationProcessorPath del maven-compiler-plugin usa 1.18.46. Ambas versiones conviviendo puede producir comportamientos sutilmente distintos entre el procesamiento de anotaciones en compilación y la biblioteca en tiempo de ejecución; se recomienda unificar a una sola versión.
  • CLAUDE.md incompleto respecto a los carriers de México y Alemania: los deja como "—" en su tabla de runners, cuando el código confirma que usan Cubbo y Sprint Logistics respectivamente. El resto de la descripción arquitectónica coincide con el código real.
  • Ver alerta de seguridad crítica en la sección 7.