eci-convert-edi-to-xlsx-sales
1. Descripción general
Según el pom.xml, el proyecto se describe como "Eci convert edi to xlsx sales". Es un microservicio batch (runner) que convierte ficheros EDI (UN/EDIFACT) de ventas de El Corte Inglés (ECI) en informes XLSX. Actualmente solo está activo el flujo de la división Sport, que lee los .edi desde una carpeta de Google Drive, genera/actualiza el XLSX correspondiente y lo vuelve a subir a Drive, marcando los ficheros de origen como procesados.
2. Información técnica
| Campo | Valor |
|---|---|
artifactId | eci-convert-edi-to-xlsx-sales |
groupId | com.hawkersco |
version | 1.0.25 |
| Java | 25 (maven.compiler.source/target=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 dos CommandLineRunner, de los cuales solo uno está activo.
Paquetes principales:
com.hawkersco.eciconverteditoxlsxsales— clase principal (EciConvertEdiToXlsxSalesApplication) y los dos runners..config—GoogleDriveConfig(beanDriveautenticado con cuenta de servicio delegada),EciConvertEdiToXlsxSalesConfig(bean manual deOrderServicedelogistics-commons)..models—EdiyEdiSport(records inmutables con los campos extraídos de cada segmento EDI)..service—EciConvertEdiToXlsxSalesService, con toda la lógica de parseo EDI como métodos estáticos.
flowchart TD
A["EciConvertEdiToXlsxSportSalesRunner (order=0, ACTIVO)"] -->|lista carpetas no procesadas| B[Google Drive]
B -->|descarga .edi| C[parseEdiSport]
C -->|escribe/actualiza XLSX| D[Google Drive]
C -->|marca appProperties.processed=true| B
E["EciConvertEdiToXlsxSalesRunner (order=1, DESACTIVADO)"] -.->|lee edis/*.edi local| F[parseEdi]
F -.->|enriquece con| G[(logistics · OrderService)]
F -.->|genera| H[edis/content.xlsx local]
GoogleDriveConfig construye el cliente Drive autenticado vía GoogleCredentials.getApplicationDefault() con delegación a la cuenta de servicio gcs-pi-kafka@pi-saldum.iam.gserviceaccount.com. EciConvertEdiToXlsxSalesConfig declara manualmente el bean OrderService (no es @Component en la librería externa logistics-commons) y el PersistenceManagedTypes para el escaneo de com.hawkersco.logisticscommons.dao.
4. Dependencias principales
| Dependencia | Propósito |
|---|---|
spring-boot-starter | Núcleo de Spring Boot (sin web, es un runner CLI) |
org.apache.poi:poi-ooxml:5.5.0 | Generación de ficheros XLSX |
com.google.apis:google-api-services-drive | Cliente de la API de Google Drive |
com.google.apis:google-api-services-sheets | Cliente de la API de Google Sheets (declarada, sin uso detectado en el código actual) |
com.google.code.gson:gson:2.13.2 | Utilidad JSON (uso puntual) |
tools.jackson.core:jackson-databind:3.0.3 | Deserialización de stores.json en el runner desactivado |
com.hawkersco:logistics-commons:1.0.25-SNAPSHOT | OrderService y entidades JPA (Order) para enriquecer el flujo local desactivado |
com.hawkersco:pi-function-commons:1.0.25-SNAPSHOT | Utilidades comunes (no se ha detectado uso directo en el código actual) |
lombok | Generación de código boilerplate |
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 |
|---|---|---|---|
Google Drive (carpeta 0AFAvqksVA3npUk9PVA) | HTTP (API de Google Drive) | Entrante/Saliente | Descarga de .edi pendientes, subida/actualización del XLSX resultante, marcado de ficheros procesados |
PostgreSQL (logistics) | JDBC | Entrante | Enriquecimiento de líneas EDI con datos de Order (solo usado por el runner local, actualmente desactivado) |
7. Configuración
Este proyecto es una excepción dentro de la familia de runners: no dispone de un perfil application-pro.properties independiente (ver hallazgo en la sección 13); solo existe src/main/resources/application.properties, con valores de base de datos hardcodeados que, en producción, son sobrescritos por variables de entorno estándar de Spring Boot definidas directamente en el CronJob.
| Clave | Descripción | Ejemplo |
|---|---|---|
spring.datasource.url | URL JDBC de la BD logistics | jdbc:postgresql://<host>:5432/logistics |
spring.datasource.username | Usuario de BD | ${SPRING_DATASOURCE_USERNAME} (producción) |
spring.datasource.password | Contraseña de BD | ${SPRING_DATASOURCE_PASSWORD} (producción) |
spring.output.ansi.enabled | Colores ANSI en el log de consola | ALWAYS |
La autenticación contra Google Drive no usa una propiedad de configuración: se resuelve en tiempo de ejecución mediante GoogleCredentials.getApplicationDefault(), que en el CronJob toma las credenciales del fichero montado en /etc/gcp/sa_credentials.json (variable GOOGLE_APPLICATION_CREDENTIALS, procedente del Secret pi-saldum-gcp-credentials).
⚠️ Alerta de seguridad
El fichero src/main/resources/application.properties contiene actualmente la contraseña real en texto plano de la base de datos PostgreSQL (pi-noctua-dbuser). Ninguno de estos valores se ha reproducido en este documento. Se recomienda:
- Rotar la contraseña de la base de datos expuesta.
- Sustituir el valor hardcodeado por una variable de entorno o un perfil separado, siguiendo el patrón de perfiles duales (
application.properties/application-pro.properties) usado en el resto de runners del ecosistema, ya que en este proyecto en producción los valores se sobrescriben vía variables de entorno estándar de Spring (SPRING_DATASOURCE_*) inyectadas por Kubernetes, pero el fichero local sigue conteniendo la credencial real. - Revisar el historial de control de versiones, ya que esta credencial puede seguir expuesta en commits anteriores.
8. Persistencia
Base de datos PostgreSQL (logistics), acceso vía JPA a través de la librería logistics-commons (@EnableJpaRepositories("com.hawkersco.logisticscommons.repository")). spring.jpa.hibernate.ddl-auto=none. Solo se usa desde el runner local desactivado (OrderService.findOrdersByCdOrderExternalList). El runner activo (Sport, vía Drive) no accede a base de datos. 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 06:00 (schedule: "0 6 * * *", concurrencyPolicy: Forbid).
EciConvertEdiToXlsxSportSalesRunner(order=0, activo): lista las subcarpetas de la carpeta raíz de Drive que no empiecen por"[PROCESSED] "; para cada una, descarga el XLSX existente (si lo hay) o crea uno nuevo con la hoja"Desglose EDI Sport"; recorre los ficheros.edino marcados como procesados (appProperties.processed != true), los parsea conparseEdiSport, añade las filas al XLSX, sube el resultado a Drive (actualiza si ya existía, crea si no) y marca cada.ediprocesado renombrándolo con el prefijo"[PROCESSED] "y estableciendoappProperties.processed=true/processedAt=<timestamp>.EciConvertEdiToXlsxSalesRunner(order=1, desactivado — la anotación@Componentestá comentada): lee todos los.edidel directorio localedis/, filtra por el mes de venta hardcodeadoSALE_MONTH_FILTER = "06/2026", descarta líneas de devolución/anulación (QTY+212:-), enriquece cada línea con elOrdercorrespondiente (víalogistics-commons) usando el número de pedido truncado, resuelve el nombre de la tienda desdestores.jsony generaedis/content.xlsxlocalmente. Termina la JVM (System.exit(SpringApplication.exit(context))).
10. Ejecución en local
Requisitos previos: JDK 25, Maven, credenciales de aplicación por defecto de Google (GOOGLE_APPLICATION_CREDENTIALS apuntando a una cuenta de servicio con permiso de delegación sobre gcs-pi-kafka@pi-saldum.iam.gserviceaccount.com) y, si se reactiva el runner local, acceso a la BD logistics y ficheros .EDI en el directorio edis/.
# Compilar
./mvnw clean package
# Ejecutar
./mvnw spring-boot:run
# Ejecutar el JAR directamente
java -jar target/eci-convert-edi-to-xlsx-sales-1.0.25.jar
No hay tests automatizados en este proyecto. Al ser un CommandLineRunner, no expone Actuator/health: la verificación se hace revisando el log de consola o comprobando en Drive que las carpetas se renombraron con el prefijo "[PROCESSED] ".
Para reactivar EciConvertEdiToXlsxSalesRunner: descomentar @Component en la cabecera de la clase y colocar los ficheros .EDI en edis/ antes de ejecutar; el resultado se genera en edis/content.xlsx. Importante: actualizar primero SALE_MONTH_FILTER (ver sección 13).
11. Despliegue
- Imagen: construida con
jib-maven-plugin(baseeclipse-temurin:25-jre,containerizingMode=packaged), publicada eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/eci-convert-edi-to-xlsx-sales:<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 06:00.restartPolicy: Never(a diferencia deOnFailure, usado en el resto de runners de la familia). - CI/CD (Jenkins): pipeline con 3 etapas —
Checkout→Build & Push→Deploy to GKE. Ver hallazgo crítico en la sección 13 sobre el pasomv application-pro.properties application.properties, que referencia un fichero inexistente en este repositorio. - Las credenciales de base de datos se inyectan como variables de entorno estándar de Spring (
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAME,SPRING_DATASOURCE_PASSWORD) desde elSecreteci-convert-edi-to-xlsx-sales-secret(nombre distinto al patrón${APP_NAME}usado en el resto de runners). Las credenciales de Google Drive se inyectan vía el volumenpi-saldum-gcp-credentialscomún a otros proyectos.
Job de Jenkins: https://jenkins-pi.hawkersco.net/job/eci-convert-edi-to-xlsx-sales/
12. Manejo de errores y logging
No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). EciConvertEdiToXlsxSalesService.parseEdi lanza IllegalArgumentException si un segmento QTY+ contiene un valor 0 o no numérico, deteniendo el procesamiento completo del fichero. El runner local envuelve la carga de stores.json en un try/catch que relanza como RuntimeException. El runner de Drive no captura excepciones explícitamente: cualquier error de la API de Drive o del parseo detiene la ejecución y el Job de Kubernetes queda marcado como fallido (sin reintento automático, dado restartPolicy: Never). Logging mediante java.util.logging.Logger estándar (consola), sin notificación a Slack ni otro canal.
13. Notas y consideraciones
- Jenkinsfile referencia un fichero inexistente: el paso
Build & Pushejecutamv src/main/resources/application-pro.properties src/main/resources/application.properties, pero este repositorio no contiene ningúnapplication-pro.properties(a diferencia de todos los demás runners de la familia*-create-db/*-update-stock). Si elJenkinsfileno se ha actualizado tras eliminar ese perfil, elmvfallaría el build; si el build actual pasa, es porque Jenkins usa una copia distinta del repositorio con ese fichero presente, o porque el paso falla silenciosamente sin que se haya detectado. Se recomienda verificar el estado real en Jenkins y limpiar elJenkinsfilepara que sea coherente con el repositorio. - Filtro de mes hardcodeado en el runner desactivado:
EciConvertEdiToXlsxSalesRunner.SALE_MONTH_FILTER = "06/2026"es un valor fijo en código; si se reactiva sin actualizarlo primero, el runner filtrará silenciosamente todas las líneas de venta que no correspondan a junio de 2026, produciendo un XLSX vacío o incompleto sin ningún aviso de error. - Posible fuga de estado entre registros en
parseEdiSport: al construir cadaEdiSporten el bloqueNAD+, el código de reseteo de las variables de línea (ean,quantity,price,completionSlip8) está comentado (líneas 258-266 deEciConvertEdiToXlsxSalesService), a diferencia deparseEdique sí resetea sus variables tras cada inserción. Si dentro de un mismo bloqueLOC+162+aparecen varios ciclosLIN+/RFF+SS+/MOA+/QTY+/NAD+y alguno de ellos no repite todos los segmentos (p. ej. falta unMOA+), el registro resultante podría arrastrar el valor deprice(u otro campo) del ítem anterior. Conviene confirmar si esto es intencional (fallback deseado) o un descuido al desactivar el reseteo. CLAUDE.mdcon versiones de dependencias desactualizadas: mencionalogistics-commonsypi-function-commonsen versión1.0.17"pendientes de actualizar a Java 25 / Spring Boot 4", cuando elpom.xmlactual ya usa la versión1.0.25-SNAPSHOTde ambas bajo el stack Java 25 / Spring Boot 4.0.6 — la actualización mencionada como pendiente ya se ha realizado. El resto de la descripción arquitectónica deCLAUDE.md(runners, flujo Drive, flujo local desactivado,stores.json) coincide con el código actual.- Dependencias declaradas sin uso aparente:
google-api-services-sheetsypi-function-commonsestán en elpom.xmlpero no se ha detectado ningún uso directo en el código fuente actual; podrían ser vestigios de una versión anterior del proyecto. - Ver alerta de seguridad en la sección 7 sobre la contraseña real de base de datos expuesta en
application.properties.