Skip to main content

glovo-update-stock

1. Descripción general

Según el pom.xml, el proyecto se describe como "Glovo update stock". Es un microservicio batch (runner) que sincroniza stock y precio de un conjunto acotado de productos (lista blanca de SKUs) entre el ERP interno (Dynamics) y el marketplace de entrega a domicilio Glovo, generando ficheros CSV por tienda y subiéndolos por SFTP.

2. Información técnica

CampoValor
artifactIdglovo-update-stock
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 un único CommandLineRunner (GlovoUpdateStockRunner) que ejecuta todo el flujo y cierra la JVM al finalizar.

Paquetes principales:

  • com.hawkersco.glovoupdatestock — clase principal (GlovoUpdateStockApplication) y el runner.
  • .configGlovoUpdateStockConfig (declaración manual de servicios de dynamics-commons), GlovoFtpProperties (record @ConfigurationProperties, prefijo glovo.ftp).
flowchart TD
A[GlovoUpdateStockRunner] -->|findAll price agreements| B[(dynamics-pro<br/>SalesPriceAgreementsService)]
A -->|findRetailStoresGlovo| C[(dynamics-pro<br/>RetailStoresService)]
A -->|findRetailAssortmentProductLinesGlovo| D[(dynamics-pro<br/>RetailAssortmentProductLinesService)]
A -->|por tienda: findProductDynamicsByWharehouseAndDataAreaId| E[(dynamics-pro<br/>ProductDynamicsService)]
A -->|filtra por SKU_PERMITED hardcoded| A
A -->|CSV maestro → chunks de 5000 líneas| F[glovo_dir/*.csv]
F -->|SFTP| G[sftp-partners.glovoapp.com]

GlovoUpdateStockConfig declara manualmente los servicios de dynamics-commons (no son @Component en la librería externa) junto con el bean PersistenceManagedTypes para el escaneo de com.hawkersco.dynamicscommons.dao. Declara también RetailAssortmentChannelLinesService, que no se usa en el runner actual (ver sección 13).

4. Dependencias principales

DependenciaPropósito
spring-boot-starterNúcleo de Spring Boot (sin web, es un runner CLI)
com.opencsv:opencsv:5.9Generación/lectura de los ficheros CSV
com.hawkersco:dynamics-commons:1.0.25-SNAPSHOTEntidades/servicios JPA de la BD dynamics-pro (tiendas, surtido, precios, stock)
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOTSftpUtils, DirectoryUtils, DateUtils
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

SistemaProtocoloDirecciónDetalle
Dynamics (dynamics-pro, PostgreSQL)JDBCEntranteLectura de tiendas Glovo, líneas de surtido, acuerdos de precio y stock disponible por almacén
Servidor SFTP Glovo (sftp-partners.glovoapp.com)SFTP (SftpUtils)SalienteSubida de los ficheros CSV Hawkers_stock_<fecha>_<hora>_NN.csv

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ónEjemplo (producción)
spring.datasource.urlURL JDBC de la BD dynamics-pro${dbDynamicsUrl}
spring.datasource.username / .passwordCredenciales de BD${dbDynamicsUsername} / ${dbDynamicsPassword}
glovo.ftp.server / .port / .user / .passConexión SFTP al servidor de Glovo${glovoFtpServer} / ${glovoFtpPort} / ${glovoFtpUser} / ${glovoFtpPass}
glovo.ftp.dirRuta remota de subida (bucket SFTP dedicado de Glovo)/glovoapp-partners-sftp-bucket-121e9009/home/hawkers-es/Input_hawkers-es/

⚠️ 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 contraseña del servidor SFTP de Glovo. Ninguno de estos valores se ha reproducido en este documento. Se recomienda:

  1. Rotar la contraseña de BD y la contraseña SFTP de Glovo.
  2. Sustituir los valores hardcodeados de application.properties por credenciales de un entorno de desarrollo aislado.
  3. 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("com.hawkersco.dynamicscommons.repository")). spring.jpa.hibernate.ddl-auto=none, spring.jpa.open-in-view=false. Entidades relevantes: RetailStores, RetailAssortmentProductLines, SalesPriceAgreements, ProductDynamics. 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 una vez al día a las 07:00 (schedule: "0 7 * * *", zona horaria Europe/Madrid, concurrencyPolicy: Forbid). Flujo único de GlovoUpdateStockRunner:

  1. Limpia y recrea el directorio local glovo_dir/.
  2. Construye un mapa SKU→precio a partir de todos los acuerdos de precio (SalesPriceAgreementsService.findAll(), sin filtrar por almacén).
  3. Obtiene las líneas de surtido de producto asignadas a Glovo (retailAssortmentProductLinesService.findRetailAssortmentProductLinesGlovo()).
  4. Para cada tienda Glovo (retailStoresService.findRetailStoresGlovo()), construye el mapa de stock disponible por SKU en su almacén y escribe una fila por cada línea de surtido cuyo SKU: tenga precio distinto de cero, y esté presente en la lista blanca SKU_PERMITED (conjunto estático de ~700 referencias hardcodeadas directamente en el código del runner).
  5. Divide el CSV maestro en ficheros de como máximo 5.000 líneas cada uno (Hawkers_stock_<yyyyMMdd>_<HHmm>_NN.csv).
  6. Borra el CSV maestro y sube todos los ficheros troceados al SFTP de Glovo.

10. Ejecución en local

Requisitos previos: JDK 25, Maven, acceso a la BD dynamics-pro y credenciales SFTP válidas de Glovo en un application.properties local.

# Compilar sin tests
./mvnw clean install -DskipTests

# Compilar con tests
./mvnw clean install

# Ejecutar tests
./mvnw test

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

# 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 partners de Glovo que los ficheros de stock se actualizaron.

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/glovo-update-stock:<tag>.
  • Orquestación: Kubernetes CronJob (k8s/cronjob.yaml) en el clúster GKE pi-cluster-hw (zona europe-west3-a, proyecto pi-saldum), namespace pi, ejecutándose diariamente a las 07:00.
  • CI/CD (Jenkins): pipeline con 3 etapas — CheckoutBuild & Push (sustituye application-pro.properties por application.properties antes de mvn clean package jib:build) → Deploy to GKE. El CLAUDE.md describe un pipeline de 5 etapas (Build → Test → Push → Deploy → Clean), más granular que el Jenkinsfile real de 3 etapas.
  • Las variables sensibles se inyectan en el pod mediante un Secret de Kubernetes llamado igual que la app (glovo-update-stock).

Job de Jenkins: https://jenkins-pi.hawkersco.net/job/glovo-update-stock/

12. Manejo de errores y logging

No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). deleteMasterCsvAndUpload y uploadFile capturan JSchException/SftpException y registran con Level.SEVERE, sin reintento ni notificación a otro canal (este proyecto no depende de slack-client, a diferencia de otros runners de la familia). El resto del flujo no captura errores explícitamente: una excepción durante la construcción de los mapas de precio/stock o la escritura del CSV propagaría y detendría la ejecución, quedando registrada como fallo del Job de Kubernetes. Logging mediante java.util.logging.Logger estándar (consola).

13. Notas y consideraciones

  • Lista blanca de SKUs hardcodeada en el código: SKU_PERMITED es un Set<String> estático de varios cientos de referencias declarado literalmente en GlovoUpdateStockRunner. Cualquier alta o baja de producto en Glovo requiere modificar el código fuente y redesplegar el servicio; sería más mantenible externalizarla a una tabla de configuración en Dynamics o a un fichero de configuración versionado por separado. El CLAUDE.md la describe como "~2,000 items"; el recuento real del conjunto es sensiblemente menor (varios cientos).
  • Precios sin filtrar por almacén: buildSkuPriceMap construye el mapa de precios a partir de todos los acuerdos de precio (findAll()), sin acotarlos por tienda/almacén ni por canal; si existieran acuerdos de precio solapados para el mismo SKU en distintos contextos, el mapa se queda con el último valor procesado (HashMap.put sobrescribe), sin ninguna regla de desempate explícita.
  • Bean RetailAssortmentChannelLinesService sin uso: GlovoUpdateStockConfig lo declara, pero no se inyecta en GlovoUpdateStockRunner ni se usa en ningún otro punto del código actual; es un bean vestigial.
  • Firma de excepción incorrecta: deleteMasterCsvAndUpload declara throws JSchException, IllegalAccessException, pero el cuerpo del método nunca lanza IllegalAccessException (ni tiene sentido en este contexto de E/S); es casi con certeza un error de copia/pega de otra firma, sin impacto funcional pero confuso para el mantenimiento.
  • Sin notificación de errores: a diferencia de otros runners de marketplace del ecosistema, este proyecto no integra slack-client; los fallos de conexión SFTP solo quedan en el log de Kubernetes/Jenkins.
  • Ver alerta de seguridad en la sección 7 sobre credenciales reales expuestas en application.properties.