showroom-update-stock
1. Descripción general
Según el pom.xml, el proyecto se describe como "Showroom update stock". Es un microservicio batch (runner) que sincroniza el stock disponible en Dynamics 365 con las ofertas activas del marketplace Showroom Privé (plataforma Mirakl), generando un fichero CSV de stock y subiéndolo mediante el endpoint de importación de la API de Mirakl.
2. Información técnica
| Campo | Valor |
|---|---|
artifactId | showroom-update-stock |
groupId | com.hawkersco |
version | 1.0.25 |
| Java | 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 (ShowroomUpdateStockRunner).
.config—ShowroomUpdateStockConfig(beanProductDynamicsService),DynamicsDbConfig(datasource,EntityManagerFactoryyTransactionManagerdel único datasource,dynamics-pro).
flowchart TD
A[ShowroomUpdateStockRunner] -->|findProductDynamicsByWharehouseAndDataAreaId<br/>AU03 / dataAreaId 10| B[(dynamics-pro · ProductDynamics)]
A -->|getOffers, paginado 100, 30s entre páginas| C[Mirakl Showroom API]
A -->|filtra SKU con prefijo DC-, cruza con stock| D[CSV offer-sku;quantity]
D -->|importStockFile, multipart| C
A -->|borra tras subir| E[Fichero CSV local]
Flujo: obtiene el stock disponible (availableOnHandQuantity) de Dynamics para el almacén AU03 y dataAreaId=10, indexado por SKU (itemNumber); pagina las ofertas activas de Mirakl (100 por página, con 30s de espera entre páginas), descarta las que tienen SKU con prefijo DC-, y para cada oferta con stock conocido en Dynamics genera una fila SKU;stock (redondeando hacia abajo, RoundingMode.FLOOR); si hay filas, escribe el CSV, lo sube a Mirakl como multipart/form-data (usando MockMultipartFile de spring-test, ver hallazgo en la sección 13) y borra el fichero local tras la subida.
4. Dependencias principales
| Dependencia | Propósito |
|---|---|
spring-boot-starter | Núcleo de Spring Boot (sin web, es un runner CLI) |
spring-boot-starter-test (scope test) | JUnit 5 + Spring Test |
spring-test (scope compile, no test) | Aporta MockMultipartFile, usada en producción para construir la subida CSV (ver hallazgo en la sección 13) |
com.hawkersco:showroom-client | Cliente @HttpExchange para la API de Mirakl Showroom (ShowroomClient) |
com.hawkersco:dynamics-commons | ProductDynamicsService, entidad ProductDynamics |
com.hawkersco:pi-function-commons | Utilidades compartidas |
| Lombok (annotation processor) | Generación de código boilerplate |
5. API / Endpoints
No aplica a este proyecto. Es un batch/runner sin capa REST.
6. Integraciones externas
| Sistema | Protocolo | Dirección | Detalle |
|---|---|---|---|
| Mirakl Showroom API | HTTP REST (ShowroomClient) | Entrante/Saliente | Lectura paginada de ofertas (getOffers) e importación de stock (importStockFile) |
PostgreSQL (dynamics-pro) | JDBC | Entrante | Lectura de stock disponible por almacén/dataAreaId |
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 |
|---|---|
spring.datasource.* | Credenciales de la BD dynamics-pro (único datasource) |
showroom.credentials.url / .key | Credenciales de la API de Mirakl Showroom |
showroom-flash.credentials.url / .key | Presentes pero vacías en ambos perfiles; sin uso aparente en este proyecto (mismo patrón de restos de copia/pegado detectado en showroom-create-db/showroom-flash-create-db) |
showroom.csv.path | Ruta local del fichero CSV temporal (por defecto ./showroom_stock_update.csv) |
⚠️ 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 dynamics-pro (la misma ya señalada como expuesta en otros proyectos de este ecosistema) y la clave de la API de Mirakl Showroom (la misma ya señalada como expuesta en showroom-create-db). Ninguno de estos valores se ha reproducido en este documento. Se recomienda:
- Rotar la contraseña de BD y la clave de Mirakl Showroom.
- 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, único datasource (DynamicsDbConfig, @Primary), acceso vía JPA a través de dynamics-commons (ddl-auto=none). Entidad relevante: ProductDynamics (paquete com.hawkersco.dynamicscommons.dao, repositorios en com.hawkersco.dynamicscommons.repository). 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). Único runner, descrito en la sección 3.
10. Ejecución en local
Requisitos previos: JDK 25, Maven, acceso a la BD dynamics-pro y credenciales válidas de la API de Mirakl Showroom.
# Compilar sin tests
./mvnw clean install -B -DskipTests
# Ejecutar la aplicación localmente
./mvnw spring-boot:run
No existe todavía ninguna clase de test en el repositorio. Al ser un CommandLineRunner, no expone Actuator/health: la verificación se hace revisando el log de consola o el estado de las ofertas en el panel de Mirakl.
11. Despliegue
- Imagen: construida con
jib-maven-plugin(baseeclipse-temurin:25-jre,containerizingMode=packaged), publicada eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/showroom-update-stock:<tag>. ElCLAUDE.mdmenciona una imagen baseeclipse-temurin:25-jdk-alpine, que no coincide con la configuración real vía Jib. - Orquestación: Kubernetes
CronJoben el clúster GKEpi-cluster-hw, namespacepi, contenedor no privilegiado, con credenciales de cuenta de servicio de GCP montadas como volumen (GOOGLE_APPLICATION_CREDENTIALS) — patrón no presente en los proyectos hermanosshowroom-create-db/showroom-flash-create-db, y no mencionado en absoluto por elCLAUDE.md. - CI/CD (Jenkins): pipeline real de 3 etapas —
Checkout→Build & Push→Deploy to GKE. ElCLAUDE.mddescribe un pipeline conKICS security scanySonarQubeque no existen en elJenkinsfileactual.
Job de Jenkins: https://jenkins-pi.hawkersco.net/job/showroom-update-stock/
12. Manejo de errores y logging
No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). El método run() envuelve toda la lógica de negocio en un único try/catch genérico, registrando Level.WARNING ante cualquier error sin interrumpir el proceso de salida (System.exit). Un fallo HTTP al paginar ofertas de Mirakl se registra y detiene la paginación (break) sin lanzar excepción; un fallo al importar el CSV también se registra sin excepción. La eliminación del CSV temporal se intenta siempre tras la subida, capturando su propio IOException de forma independiente. Logging mediante java.util.logging.Logger estándar (consola).
13. Notas y consideraciones
CLAUDE.mddescribe una arquitectura de doble datasource y ajuste de stock por porcentaje que no existe en el código actual: afirma que el runner consulta un segundo datasource (marketplaces, vía una claseMarketplacesDbConfigyMarketplaceStockServicede una libreríamarketplaces-commons) para obtener "ECI marketplace stock percentages" y calcularfloor(stock × eciPct / 100). En el código real solo existe un datasource (dynamics-pro, gestionado porDynamicsDbConfig), no hay claseMarketplacesDbConfig, la libreríamarketplaces-commonsni siquiera es una dependencia delpom.xml, y el cálculo de stock es una simple copia del valor de Dynamics redondeado hacia abajo (stockBySku.get(shopSku)→FLOOR), sin ningún ajuste porcentual. Es la desviación más significativa encontrada entreCLAUDE.mdy el código real en este lote de proyectos: describe una funcionalidad (integración con ECI) que no está implementada.- Uso de
MockMultipartFile(utilidad de test) en código de producción:ShowroomUpdateStockRunner.exportAndUploadCsvconstruye la petición multipart de subida del CSV usandoorg.springframework.mock.web.MockMultipartFile, y elpom.xmldeclaraspring-testconscope=compile(notest) específicamente para poder usarla en producción. Esta clase está pensada para pruebas unitarias, no para tráfico real; es funcional pero supone una dependencia de una API no garantizada como estable para uso productivo, y sorprende a cualquier desarrollador que audite las dependencias del proyecto. - Pipeline de Jenkins más simple de lo documentado: ver hallazgo en la sección 11.
- Credenciales de cuenta de servicio de GCP montadas por volumen: a diferencia de otros runners de este ecosistema, el
CronJobmonta un secreto de Kubernetes como fichero (sa_credentials.json) y exponeGOOGLE_APPLICATION_CREDENTIALS, aunque no se ha encontrado ningún uso de la API de Google (GCS, Sheets) en el código Java de este proyecto — posible resto de configuración no utilizada, pendiente de verificar con el equipo. - Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas en
application.properties.