Skip to main content

logistic-gr

1. Descripción general

Según el pom.xml, el proyecto se describe como "Send orders Greece to logistic". Es un microservicio batch (runner) que procesa pedidos pendientes con origen Grecia y los envía a la API logística de Sarmed. Contiene además un segundo runner de sincronización de catálogo de producto desde Google Sheets que, pese a estar descrito como "deshabilitado" en la documentación existente, está activo y bloquea la ejecución del runner principal de pedidos (ver hallazgo crítico en la sección 13).

2. Información técnica

CampoValor
artifactIdlogistic-gr
groupIdcom.hawkersco
version1.0.25
Java25
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 2 CommandLineRunner, ambos activos (ver hallazgo crítico sobre su interacción en la sección 13).

Orden (getOrder())RunnerEstado realPropósito
0LogisticGrCreateProductRunnerActivo (anotado @Component, no comentado)Sincroniza el catálogo de producto desde Google Sheets hacia Sarmed, y termina el proceso con System.exit(0) al final de su ejecución
1LogisticGrRunnerActivo, pero solo se alcanza si el runner de orden 0 no ha llamado ya a System.exitProcesa pedidos pendientes de Grecia y los envía a Sarmed
  • .configLogisticGrConfig (wiring de beans).
  • .utilLogisticGrConst (IDs de fuente, valores de estado, límites de reintento, tamaño de lote), LogisticGrUtils (transformación de pedido a formato Sarmed, cálculo de fecha de entrega a 72 horas laborables, manejadores de éxito/error).
flowchart TD
A["Orden 0: LogisticGrCreateProductRunner<br/>ACTIVO"] -->|lee| B[Google Sheets · catálogo]
B -->|por producto, sleep 5s| C[Sarmed API · alta de producto]
A -->|"System.exit(0) SIEMPRE al terminar"| D[Fin del proceso]
E["Orden 1: LogisticGrRunner<br/>pedidos PENDING_SHIPMENT origen 61/69"] -.->|"nunca se alcanza si A ya llamó a exit"| F[Sarmed API · envío de pedido]

Flujo previsto (documentado): el runner de orden 1 consulta pedidos con id_source 61 o 69 y estado PENDING_SHIPMENT/PENDING_SHIPMENT_FORCE, omite los pedidos de prueba, los transforma a formato Sarmed, los envía, y ante éxito marca el pedido como INIT y archiva el JSON en GCS; ante fallo incrementa el contador de reintentos y alerta a Slack tras 4 fallos. 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.hawkersco:sarmed-clientCliente @HttpExchange para la API Sarmed (sesión, alta de producto, envío de pedido)
com.hawkersco:logistics-commonsEntidades JPA (Order, ShipmentRequest, OrderError) y servicios
com.hawkersco:slack-clientNotificaciones de error
com.hawkersco:pi-function-commonsDateUtils, SheetsServiceUtils, StorageUtils

5. API / Endpoints

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

6. Integraciones externas

SistemaProtocoloDirecciónDetalle
Sarmed API (ecommerce.sarmed.gr)HTTPS con SSL "trust-all" (certificados autofirmados), credenciales en el cuerpo de la peticiónEntrante/SalienteAutenticación por sesión, alta de producto (runner 0), envío de pedido (runner 1)
Google Sheets (hoja 1Uys_bOf8kMqcwsDFWNKkkVrFdYVMZ_dj8rSA6GEtJMc)API de GoogleEntranteCatálogo de producto para sincronizar con Sarmed
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
sarmed.api.url / .username / .password / .trust-all-sslCredenciales y configuración SSL de la API Sarmed
slack.client.url / .auth.token / .channel.id / .channel-atc.idConfiguración de Slack

⚠️ 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 logistics (la misma ya señalada como expuesta en múltiples proyectos de este ecosistema), la contraseña de la API Sarmed, y el token de bot de Slack. Adicionalmente, sarmed.api.trust-all-ssl=true deshabilita la validación de certificados SSL en las llamadas a Sarmed, aceptando certificados autofirmados o inválidos — una práctica que expone a riesgo de intermediación (man-in-the-middle) si la red de tránsito no es de confianza. Ninguna credencial se ha reproducido en este documento. Se recomienda:

  1. Rotar la contraseña de BD, la contraseña de Sarmed y el token de Slack.
  2. Evaluar si trust-all-ssl es realmente necesario (certificado autofirmado del proveedor) o si puede sustituirse por un almacén de confianza (truststore) con el certificado específico de Sarmed.
  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, ShipmentRequest, 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 10 minutos (schedule: "0/10 * * * *", concurrencyPolicy: Forbid, activeDeadlineSeconds: 43200 — 12 horas, un plazo inusualmente generoso comparado con proyectos hermanos de 1 hora, coherente con que el runner de sincronización de producto recorra el catálogo completo con una espera de 5 segundos por producto). 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, credenciales válidas de la API Sarmed y credenciales de aplicación por defecto de Google (Sheets).

# Compilar
./mvnw clean package

# Compilar sin tests
./mvnw -B -DskipTests clean package

# Ejecutar tests
./mvnw test

# Ejecutar con perfil de producción
java -jar target/logistic-gr-1.0.25.jar --spring.profiles.active=pro

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-gr:<tag>. El CLAUDE.md menciona una imagen base eclipse-temurin:25-jdk-alpine con el JAR renombrado a logistic-gr.jar, que no coincide con la configuración real vía Jib.
  • 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 10 minutos con un plazo de actividad de 12 horas.
  • CI/CD (Jenkins): pipeline real de 3 etapas — CheckoutBuild & PushDeploy to GKE. El CLAUDE.md describe un pipeline de 4 etapas (Build → KICS Scan → SonarQube Analysis → Test) que no coincide con el Jenkinsfile actual.

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

12. Manejo de errores y logging

No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). LogisticGrCreateProductRunner captura IOException al leer Google Sheets y Exception genérica por producto individual, sin interrumpir el resto del catálogo. LogisticGrRunner 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: el runner de sincronización de producto no está deshabilitado y probablemente impide la ejecución del runner principal de pedidos

El CLAUDE.md de este proyecto afirma explícitamente que LogisticGrCreateProductRunner está "Currently disabled (getOrder() returns 0)". Esta afirmación es incorrecta: en Spring Boot, el valor devuelto por getOrder()/Ordered únicamente determina la prioridad de ejecución entre CommandLineRunners — no controla si un runner se ejecuta o no. LogisticGrCreateProductRunner está anotado con @Component (sin comentar) y por tanto se registra y ejecuta en cada invocación del CronJob, con orden 0 (es decir, el primero en ejecutarse, antes que LogisticGrRunner, que tiene orden 1).

Más grave aún: al final de su método run(), LogisticGrCreateProductRunner llama incondicionalmente a System.exit(0) — independientemente de si el procesamiento del catálogo tuvo éxito o no. Dado que Spring Boot invoca los CommandLineRunner de forma secuencial y síncrona en el mismo hilo durante el arranque de la aplicación, esta llamada a System.exit(0) termina la JVM antes de que el control regrese al framework para invocar el siguiente runner. Esto significa que, en cada ejecución del CronJob (cada 10 minutos), el proceso:

  1. Ejecuta LogisticGrCreateProductRunner (sincroniza el catálogo completo de Google Sheets con Sarmed, con 5 segundos de espera por producto).
  2. Llama a System.exit(0).
  3. LogisticGrRunner — el runner que procesa y envía los pedidos pendientes de Grecia, el propósito principal descrito en el pom.xml y en el propio CLAUDE.md — nunca llega a ejecutarse.

Si esta hipótesis se confirma en producción, ningún pedido de Grecia se estaría enviando actualmente a Sarmed a través de este microservicio, pese a que el CronJob se ejecuta cada 10 minutos sin errores aparentes (el proceso termina con código de salida 0, no con un fallo). Se recomienda verificar con el equipo si esto es el comportamiento observado en producción (por ejemplo, comprobando si hay pedidos con id_source 61/69 acumulados en estado PENDING_SHIPMENT sin avanzar), y de confirmarse, quitar la anotación @Component de LogisticGrCreateProductRunner (o eliminar la llamada a System.exit(0) de su interior) con prioridad alta.

Otros hallazgos

  • Pipeline de Jenkins e imagen base más simples de lo documentado: ver hallazgos en la sección 11.
  • El resto de la arquitectura descrita en CLAUDE.md (fuentes de pedido 61/69, pedidos de prueba, cálculo de fecha de entrega a 72h laborables, reintentos y alerta Slack) coincide con el código de LogisticGrRunner y LogisticGrUtils, verificado directamente.
  • Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas en application.properties y sobre trust-all-ssl.