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
| Campo | Valor |
|---|---|
artifactId | glovo-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 (GlovoUpdateStockRunner) que ejecuta todo el flujo y cierra la JVM al finalizar.
Paquetes principales:
com.hawkersco.glovoupdatestock— clase principal (GlovoUpdateStockApplication) y el runner..config—GlovoUpdateStockConfig(declaración manual de servicios dedynamics-commons),GlovoFtpProperties(record@ConfigurationProperties, prefijoglovo.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
| Dependencia | Propósito |
|---|---|
spring-boot-starter | Núcleo de Spring Boot (sin web, es un runner CLI) |
com.opencsv:opencsv:5.9 | Generación/lectura de los ficheros CSV |
com.hawkersco:dynamics-commons:1.0.25-SNAPSHOT | Entidades/servicios JPA de la BD dynamics-pro (tiendas, surtido, precios, stock) |
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOT | SftpUtils, 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
| Sistema | Protocolo | Dirección | Detalle |
|---|---|---|---|
Dynamics (dynamics-pro, PostgreSQL) | JDBC | Entrante | Lectura 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) | Saliente | Subida 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).
| Clave | Descripción | Ejemplo (producción) |
|---|---|---|
spring.datasource.url | URL JDBC de la BD dynamics-pro | ${dbDynamicsUrl} |
spring.datasource.username / .password | Credenciales de BD | ${dbDynamicsUsername} / ${dbDynamicsPassword} |
glovo.ftp.server / .port / .user / .pass | Conexión SFTP al servidor de Glovo | ${glovoFtpServer} / ${glovoFtpPort} / ${glovoFtpUser} / ${glovoFtpPass} |
glovo.ftp.dir | Ruta 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:
- Rotar la contraseña de BD y la contraseña SFTP de Glovo.
- 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("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:
- Limpia y recrea el directorio local
glovo_dir/. - Construye un mapa SKU→precio a partir de todos los acuerdos de precio (
SalesPriceAgreementsService.findAll(), sin filtrar por almacén). - Obtiene las líneas de surtido de producto asignadas a Glovo (
retailAssortmentProductLinesService.findRetailAssortmentProductLinesGlovo()). - 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 blancaSKU_PERMITED(conjunto estático de ~700 referencias hardcodeadas directamente en el código del runner). - Divide el CSV maestro en ficheros de como máximo 5.000 líneas cada uno (
Hawkers_stock_<yyyyMMdd>_<HHmm>_NN.csv). - 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(baseeclipse-temurin:25-jre,containerizingMode=packaged), publicada eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/glovo-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 diariamente a las 07:00. - CI/CD (Jenkins): pipeline con 3 etapas —
Checkout→Build & Push(sustituyeapplication-pro.propertiesporapplication.propertiesantes demvn clean package jib:build) →Deploy to GKE. ElCLAUDE.mddescribe un pipeline de 5 etapas (Build → Test → Push → Deploy → Clean), más granular que elJenkinsfilereal de 3 etapas. - Las variables sensibles se inyectan en el pod mediante un
Secretde 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_PERMITEDes unSet<String>estático de varios cientos de referencias declarado literalmente enGlovoUpdateStockRunner. 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. ElCLAUDE.mdla describe como "~2,000 items"; el recuento real del conjunto es sensiblemente menor (varios cientos). - Precios sin filtrar por almacén:
buildSkuPriceMapconstruye 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.putsobrescribe), sin ninguna regla de desempate explícita. - Bean
RetailAssortmentChannelLinesServicesin uso:GlovoUpdateStockConfiglo declara, pero no se inyecta enGlovoUpdateStockRunnerni se usa en ningún otro punto del código actual; es un bean vestigial. - Firma de excepción incorrecta:
deleteMasterCsvAndUploaddeclarathrows JSchException, IllegalAccessException, pero el cuerpo del método nunca lanzaIllegalAccessException(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.