decathlon-update-stock
1. Descripción general
Según el pom.xml, el proyecto se describe como "Decathlon update stock". Es un microservicio batch (runner) que sincroniza el stock disponible en el ERP interno (Dynamics) con el marketplace Decathlon (Mirakl EU), generando un fichero CSV con el formato offer-sku;quantity y subiéndolo a Decathlon mediante su API de importación de stock (STO01).
Forma parte de la familia de runners de actualización de stock del ecosistema Hawkers (mismo patrón que otros *-update-stock), leyendo el stock disponible desde la librería compartida dynamics-commons.
2. Información técnica
| Campo | Valor |
|---|---|
artifactId | decathlon-update-stock |
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 compuesta por 2 CommandLineRunner que implementan Ordered y se ejecutan de forma secuencial. Al finalizar el segundo runner, la aplicación cierra la JVM explícitamente.
Paquetes principales:
com.hawkersco.decathlonupdatestock— clase principal (DecathlonUpdateStockApplication) y los dos runners (DecathlonHwUpdateStockRunner,DecathlonNwUpdateStockRunner)..config—DynamicsDbConfig(datasource/EntityManager/TransactionManager hacia la BDdynamics-pro),DecathlonUpdateStockConfig(bean manual deProductDynamicsService)..utils—DecathlonUpdateStockUtils, componente compartido con toda la lógica de negocio.
flowchart TD
A["1. DecathlonHwUpdateStockRunner"] --> C[DecathlonUpdateStockUtils]
B["2. DecathlonNwUpdateStockRunner<br/>(Exit JVM al finalizar)"] --> C
C -->|fetchStockBySku| D[(dynamics-pro<br/>ProductDynamicsService)]
C -->|getOffers paginado| E[Decathlon Mirakl EU]
C -->|buildCsvRows + exportAndUploadCsv| F[CSV local]
F -->|importStockFile| E
DynamicsDbConfig declara manualmente el único DataSource/EntityManagerFactory/TransactionManager de la aplicación (marcados como @Primary), apuntando a com.hawkersco.dynamicscommons.repository/dao. DecathlonUpdateStockConfig declara el bean ProductDynamicsService (no es @Component en la librería externa dynamics-commons).
4. Dependencias principales
| Dependencia | Propósito |
|---|---|
spring-boot-starter | Núcleo de Spring Boot (sin web, es un runner CLI) |
spring-test (compile) | Se usa en producción para MockMultipartFile, ver nota en sección 13 |
com.hawkersco:decathlon-client:1.0.25-SNAPSHOT | Cliente @HttpExchange (DecathlonClient) para Mirakl Decathlon EU |
com.hawkersco:dynamics-commons:1.0.25-SNAPSHOT | Entidades/servicios JPA de la BD dynamics-pro (ProductDynamicsService, ProductDynamics) |
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOT | Utilidades comunes (no se ha detectado uso directo en el código actual) |
spring-boot-starter-test (test) | JUnit 5 + Spring Test |
5. API / Endpoints
No aplica a este proyecto. Es un batch/runner sin capa REST.
6. Integraciones externas
| Sistema | Protocolo | Dirección | Detalle |
|---|---|---|---|
Dynamics (dynamics-pro, PostgreSQL) | JDBC | Entrante | Lectura de stock disponible (ProductDynamicsService.findProductDynamicsByWharehouseAndDataAreaId) |
| Mirakl Decathlon EU | HTTP (@HttpExchange vía DecathlonClient) | Entrante/Saliente | Paginación de ofertas (getOffers) y subida del CSV de stock (importStockFile) |
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.jdbc-url | URL JDBC de la BD dynamics-pro | ${dbDynamicsUrl} |
spring.datasource.username | Usuario de BD | ${dbDynamicsUsername} |
spring.datasource.password | Contraseña de BD | ${dbDynamicsPassword} |
decathlon.credentials.url | URL base Mirakl EU | ${decathlonCredUrl} |
decathlon.credentials.key | API key Mirakl EU | ${decathlonCredKey} |
decathlon-au.credentials.url | URL base Mirakl AU (declarada pero no usada en el código, ver sección 13) | (vacío en application-pro.properties) |
decathlon-au.credentials.key | API key Mirakl AU (idem) | (vacío en application-pro.properties) |
decathlon.csv.path | Ruta local del fichero CSV temporal | ./decathlon_stock_update.csv |
spring.output.ansi.enabled | Colores ANSI en el log de consola | ALWAYS |
⚠️ 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 y API keys de Mirakl Decathlon EU y AU. Ninguno de estos valores se ha reproducido en este documento. Se recomienda:
- Rotar las credenciales expuestas (contraseña de BD y API keys de Decathlon).
- 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 dynamics-pro, acceso vía JPA a través de la librería dynamics-commons (@EnableJpaRepositories(basePackages = "com.hawkersco.dynamicscommons.repository")). spring.jpa.hibernate.ddl-auto=none: no hay generación ni migración automática del esquema desde este proyecto. Entidad relevante: ProductDynamics (campos itemNumber, availableOnHandQuantity). 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 15 minutos (schedule: "*/15 * * * *", zona horaria Europe/Madrid, concurrencyPolicy: Forbid). Al arrancar, se ejecutan en orden los 2 runners:
| Orden | Runner | Función |
|---|---|---|
| 1 | DecathlonHwUpdateStockRunner | Ejecuta el flujo de sincronización de stock |
| 2 | DecathlonNwUpdateStockRunner | Ejecuta el mismo flujo de sincronización y cierra la JVM (System.exit(SpringApplication.exit(context))) |
Flujo compartido (DecathlonUpdateStockUtils), invocado de forma idéntica por ambos runners:
fetchStockBySku()— consultaProductDynamicsService.findProductDynamicsByWharehouseAndDataAreaId("AU03", "10")y construye unMap<SKU, availableOnHandQuantity>.buildCsvRows(stockBySku)— paginadecathlonClient.getOffers(100, offset)(con espera de 30s entre páginas), filtra las ofertas cuyo SKU empiece por"DC-"(se excluyen), y para cada oferta restante busca su stock en el mapa de Dynamics; si no hay coincidencia, se omite la fila.- Si hay filas,
exportAndUploadCsv(csvRows, csvPath)— escribe el CSV (offer-sku;quantity), lo sube a Decathlon víadecathlonClient.importStockFile(...)(formulario multipart, ficheroSTO01) y borra el fichero temporal local.
10. Ejecución en local
Requisitos previos: JDK 25, Maven, acceso a la BD dynamics-pro y credenciales válidas de Mirakl Decathlon EU en un application.properties local.
# Compilar sin tests (igual que en CI)
./mvnw -B -DskipTests clean install
# 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 comprobar en el panel de Mirakl que la importación STO01 se procesó correctamente.
11. Despliegue
- Imagen: construida con
jib-maven-plugin(baseeclipse-temurin:25-jre,containerizingMode=packaged), publicada eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/decathlon-update-stock:<tag>. - Orquestación: Kubernetes
CronJob(k8s/cronjob.yaml) en el clúster GKEpi-cluster-hw(zonaeurope-west3-a, proyectopi-saldum), namespacepi, ejecutándose cada 15 minutos. - CI/CD (Jenkins): pipeline con 3 etapas —
Checkout→Build & Push(sustituyeapplication-pro.propertiesporapplication.propertiesantes demvn clean package jib:build) →Deploy to GKE(borra elCronJobexistente con--ignore-not-foundy aplica el manifiesto templado víased). - Las variables sensibles se inyectan en el pod mediante un
Secretde Kubernetes llamado igual que la app (decathlon-update-stock).
Job de Jenkins: https://jenkins-pi.hawkersco.net/job/decathlon-update-stock/
12. Manejo de errores y logging
No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). Cada runner envuelve toda su ejecución en un try/catch genérico que registra la excepción con Level.WARNING y continúa, sin propagarla ni notificar por Slack/otro canal. DecathlonUpdateStockUtils.exportAndUploadCsv registra el resultado de la importación (éxito o error) mediante java.util.logging.Logger estándar (consola), sin un formato estructurado propio ni integración documentada con un sistema centralizado de logs.
13. Notas y consideraciones
CLAUDE.mdgravemente desactualizado: el fichero describe una arquitectura completamente distinta a la real — doble datasource (dynamics-pro+marketplaces), dependenciamarketplaces-commons, cálculo de stock por porcentaje configurado (MarketplaceStockService) y actualización directa víaDecathlonClient.updateOffers. Ninguno de estos elementos existe en el código actual: no hay dependenciamarketplaces-commonsen elpom.xml, no hay segundo datasource, y el mecanismo real es un export a CSV + subida porimportStockFile(ficheroSTO01), con el stock tomado directamente delavailableOnHandQuantityde Dynamics sin aplicar ningún porcentaje. Se recomienda regenerar elCLAUDE.mdpara reflejar el comportamiento real.- Los dos runners son funcionalmente idénticos: a pesar de sus nombres (
DecathlonHwUpdateStockRunnerpara HAWKERS,DecathlonNwUpdateStockRunnerpara NORTHWEEK) y de lo que sugiereCLAUDE.md(procesamiento diferenciado por marca/company code), ninguno de los dos filtra por marca: ambos llaman exactamente a la misma secuenciafetchStockBySku()→buildCsvRows()→exportAndUploadCsv()sin ningún parámetro de compañía. En la práctica, el segundo runner repite el mismo trabajo que el primero (sube el mismo CSV dos veces), lo que indica que la diferenciación por marca prevista en el diseño original nunca se implementó, o se perdió en una refactorización. - Mensaje de log copiado de otro proyecto: en
DecathlonUpdateStockUtils.buildCsvRows, el log de error ante una respuesta no exitosa degetOffersdice"Error al obtener offers de ECI. Status: {0}", mencionando "ECI" en vez de "Decathlon" — evidencia de copia desde un runner de ECI (eci-update-stock) sin actualizar el texto. - Dependencia de test en producción: se usa
org.springframework.mock.web.MockMultipartFile(despring-test, declarada como dependenciacompileen elpom.xml, no solotest) para construir el payload multipart haciadecathlonClient.importStockFile. Aunque funciona, es una utilidad pensada para tests; sería más correcto usarByteArrayResourceo similar en código de producción. - Configuración AU sin uso:
decathlon-au.credentials.urlydecathlon-au.credentials.keyestán declaradas en ambos perfiles de configuración pero no se usan en ningún punto del código (no existe cliente AU en este proyecto, a diferencia dedecathlon-create-db). Es configuración residual que puede eliminarse. - Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas en
application.properties.