Dafiti Create DB
1. Descripción general
dafiti-create-db es un microservicio batch que ingiere en la base de datos logística de Hawkers los pedidos del marketplace colombiano Dafiti. La description del pom.xml ("Dafiti client") es heredada/imprecisa — el proyecto no es un cliente HTTP, sino un runner batch que usa el cliente dafiti-client para orquestar todo el ciclo de vida del pedido. No expone ninguna API HTTP: es una aplicación Spring Boot con tres CommandLineRunner ordenados, desplegada como CronJob de Kubernetes cada 15 minutos.
El flujo cubre tres fases secuenciales: (1) ingestión de pedidos pendientes desde la API de Dafiti, (2) notificación a Dafiti de que el pedido está listo para envío ("ready to ship") con el transportista detectado, y (3) generación de la etiqueta de envío en PDF, su subida a un servidor SFTP externo y el registro del código de seguimiento en base de datos.
2. Información técnica
| Propiedad | Valor |
|---|---|
artifactId | dafiti-create-db |
groupId | com.hawkersco |
version | 1.0.25 |
| Java | 25 |
| Spring Boot | 4.0.6 |
| Tipo de artefacto | JAR ejecutable (imagen de contenedor vía Jib) |
| Módulos | Proyecto único (no multi-módulo) |
3. Arquitectura y diseño
Estructura del proyecto:
com.hawkersco.dafiticreatedb
├── DafitiCreateDbApplication.java # @SpringBootApplication + @EnableJpaRepositories
├── DafitiCreateDbRunner.java # CommandLineRunner, orden 1: ingiere pedidos pendientes
├── DafitiReadyToShipRunner.java # CommandLineRunner, orden 2: notifica ready-to-ship
├── DafitiGenerateLabelRunner.java # CommandLineRunner, orden 3: genera etiqueta, sube a SFTP
├── config/
│ ├── DafitiCreateDbConfig.java # Declara manualmente los @Bean de servicios de logistics-commons
│ └── FtpProperties.java # @ConfigurationProperties("ftp") — server/port/user/pass/dir
└── utils/
└── DafitiCreateDbUtils.java # Transformación de pedido Dafiti → entidades logistics-commons
Flujo principal (tres runners ordenados)
sequenceDiagram
participant CronJob as CronJob K8s (cada 15 min)
participant R1 as DafitiCreateDbRunner (orden 1)
participant Dafiti as API Dafiti (Seller Center)
participant DB as PostgreSQL (logistics)
participant R2 as DafitiReadyToShipRunner (orden 2)
participant R3 as DafitiGenerateLabelRunner (orden 3)
participant SFTP as sftp.hawkersco.com
participant Slack
CronJob->>R1: run()
loop paginado (100/página)
R1->>Dafiti: getOrdersPending(limit, offset)
Dafiti-->>R1: pedidos
R1->>DB: crea OrderMarketplace, Customer, Order, OrderLine, Shipment, OrderShipmentStatus (PENDING_READY_TO_SHIP)
R1->>Slack: alerta en error HTTP o Source no reconocido
end
CronJob->>R2: run()
R2->>DB: pedidos en PENDING_READY_TO_SHIP (máx 100)
R2->>Dafiti: getOrderById → detecta transportista y estado
R2->>Dafiti: setStatusReadyToShip(deliveryType=dropship, shippingProvider, orderItems)
R2->>DB: estado → PENDING_SHIPPING_PARCEL
CronJob->>R3: run()
R3->>DB: pedidos en PENDING_SHIPPING_PARCEL (máx 100)
R3->>Dafiti: exportDocument (solicita generación de etiqueta PDF, async)
R3->>R3: espera 30s
R3->>Dafiti: donwloadFileByUuid (descarga el PDF; nombre de método con typo en dafiti-client)
R3->>SFTP: sube el PDF a /src/pending/
R3->>DB: guarda etiqueta en base64 + tracking code, estado → PENDING_SHIPMENT
R3->>R3: System.exit(SpringApplication.exit(context))
DafitiCreateDbConfig declara manualmente cada servicio de logistics-commons como @Bean (mismo patrón que bradery-create-db, chatbot y coppel-create-db) y usa @EnableConfigurationProperties(FtpProperties.class) para vincular la configuración SFTP tipada, en lugar de @Value sueltos.
4. Dependencias principales
| Dependencia | Versión | Propósito |
|---|---|---|
spring-boot-starter | (gestionada SB4) | Base de Spring Boot (contexto, CommandLineRunner, @ConfigurationProperties) |
com.hawkersco:dafiti-client | 1.0.25-SNAPSHOT | Cliente @HttpExchange de la API Dafiti (DafitiClient), con autenticación OAuth2 client_credentials automática |
com.hawkersco:slack-client | 1.0.25-SNAPSHOT | Cliente @HttpExchange de Slack, usado para alertas de error |
com.hawkersco:logistics-commons | 1.0.25-SNAPSHOT | Entidades JPA y servicios (Order, OrderLine, Customer, Shipment, etc.) |
com.hawkersco:pi-function-commons | 1.0.25-SNAPSHOT | Utilidades DateUtils, SftpUtils, ZipUtils, DirectoryUtils |
spring-boot-starter-test | (gestionada SB4) | Testing (scope test) |
5. API / Endpoints
No aplica a este proyecto. dafiti-create-db es un batch CommandLineRunner sin servidor HTTP ni controladores REST.
6. Integraciones externas
| Sistema | Protocolo | Dirección | Descripción |
|---|---|---|---|
API Dafiti Seller Center (vía dafiti-client) | HTTP REST, OAuth2 | Saliente | Listado de pedidos pendientes, consulta de pedido, ready-to-ship, exportación de documentos, descarga de etiqueta |
SFTP (sftp.hawkersco.com, vía pi-function-commons.SftpUtils) | SFTP | Saliente | Subida de la etiqueta PDF generada a /src/pending/ |
Slack (vía slack-client) | HTTP REST | Saliente | Alertas de error HTTP en la ingestión y de Source ID no registrado |
PostgreSQL (logistics, vía logistics-commons) | JDBC | Saliente | Persistencia de pedidos, clientes, líneas, envíos y etiquetas |
Convenciones de negocio relevantes (constantes duplicadas en varios runners y en DafitiCreateDbUtils):
- Source ID de Dafiti: 60L; existe también una referencia a un Source ID legacy (20L) usado para detectar y eliminar registros
OrderMarketplaceobsoletos antes de re-crearlos. - Prefijo de pedido interno:
HWD-. - Método de envío hardcodeado: ID
1. Tipo de servicio de pedido hardcodeado: ID33. - País hardcodeado a Colombia (
CO), independientemente del país real de la dirección del pedido. - Porcentaje de impuesto fijo:
19.00%, aplicado sobre el importe total del pedido (grandTotal) para calcularnmOrderTaxAmt. - Tipo de entrega en "ready to ship":
dropship. - Flujo de estados del pedido:
PENDING_READY_TO_SHIP→PENDING_SHIPPING_PARCEL→PENDING_SHIPMENT.
7. Configuración
El proyecto usa dos perfiles: application.properties (desarrollo, con valores reales) y application-pro.properties (producción, con placeholders ${VAR}). El Jenkinsfile sustituye el primero por el segundo antes de empaquetar.
Grupos de propiedades
| Propiedad | Descripción | Valor en producción |
|---|---|---|
spring.datasource.url/username/password | Conexión PostgreSQL de logística | ${dbLogisitcsUrl} / ${dbLogisitcsUsername} / ${dbLogisitcsPassword} (mismo typo "Logisitcs" que en chatbot/coppel-create-db) |
dafiti.api.url | URL base de la API Dafiti | ${dafitiApiUrl} |
dafiti.api.grantType | Grant type OAuth2 | ${dafitiApiGrantType} |
dafiti.api.clientId / clientSecret | Credenciales OAuth2 de la integración Dafiti | ${dafitiApiClientId} / ${dafitiApiClientSecret} |
slack.client.url / slack.auth.token | Configuración del cliente Slack | ${slackClientUrl} / ${slackAuthToken} |
slack.channel.id | Canal Slack para alertas | ${slackChannelId} |
ftp.server / ftp.port / ftp.user / ftp.pass | Conexión al servidor SFTP de etiquetas | ${ftpServer} / ${ftpPort} / ${ftpUser} / ${ftpPass} |
ftp.dir | Directorio remoto de subida (/src/pending/) | /src/pending/ (fijo, no varía por entorno) |
⚠️ Alerta de seguridad: El fichero
src/main/resources/application.properties(perfil de desarrollo, versionado en el repositorio) contiene credenciales reales en texto plano: contraseña de la base de datos, credenciales OAuth2 de Dafiti, token de bot de Slack y credenciales del servidor SFTP. Ninguno de estos valores se reproduce en este documento. Mismo patrón de exposición de secretos ya observado enbradery-create-db,chatbotycoppel-create-db— se recomienda una revisión y rotación conjunta de credenciales en todos los runners*-create-dbdel ecosistema.
Variables de entorno (perfil de producción)
| Variable | Propiedad mapeada |
|---|---|
dbLogisitcsUrl / Username / Password | spring.datasource.* |
dafitiApiUrl / GrantType / ClientId / ClientSecret | dafiti.api.* |
slackClientUrl / slackAuthToken / slackChannelId | slack.* |
ftpServer / ftpPort / ftpUser / ftpPass | ftp.server / .port / .user / .pass |
8. Persistencia
Base de datos: PostgreSQL (logistics), accedida vía JPA/Hibernate a través de las entidades de logistics-commons.
Entidades principales manipuladas por este runner:
| Entidad | Rol |
|---|---|
OrderMarketplace | Vínculo entre el pedido interno (HWD-...) y el pedido de Dafiti; también almacena la etiqueta en base64 y el tracking code (updateOrderMarketplaceByDsOrderAndIdSource) |
Customer / CustomerSource | Cliente asociado al pedido, con origen registrado para Source ID 60 |
Order | Pedido logístico completo; rawData guarda el JSON crudo del pedido Dafiti |
OrderLine | Líneas de producto (SKU del vendedor, cantidad fija a 1, precio, descuento calculado) |
Shipment / ShipmentLine | Envío generado a partir del pedido y sus líneas |
No se han encontrado migraciones Flyway/Liquibase (spring.jpa.hibernate.ddl-auto=none); el esquema se gestiona externamente.
9. Procesos programados y mensajería
No hay @Scheduled en el código: la periodicidad se gestiona vía Kubernetes. k8s/cronjob.yaml define un CronJob con expresión 0/15 * * * * (cada 15 minutos).
Dentro de cada ejecución, los tres CommandLineRunner se ejecutan secuencialmente según su orden (DafitiCreateDbRunner=1, DafitiReadyToShipRunner=2, DafitiGenerateLabelRunner=3).
10. Ejecución en local
Requisitos previos
- Java 25
- Maven 3.x
- Acceso a la base de datos PostgreSQL de logística
- Credenciales OAuth2 válidas de la API de Dafiti
- Acceso al servidor SFTP
sftp.hawkersco.com - Configuración de Slack para probar alertas
Comandos
# Build sin tests (como en CI)
./mvnw -B -DskipTests clean install
# Build con tests
./mvnw clean install
# Ejecutar localmente (usa application.properties con credenciales de dev)
./mvnw spring-boot:run
# Ejecutar un test concreto
./mvnw test -Dtest=DafitiCreateDbApplicationTests
# Build de imagen Docker
docker build -t dafiti-create-db .
Al no tener servidor HTTP, no hay endpoint de health; la verificación se hace revisando los logs (DafitiCreateDbRunner - END, DafitiGenerateLabelRunner - END) o el estado del Job en Kubernetes.
11. Despliegue
Se despliega como imagen de contenedor en GKE, orquestada por un CronJob, con el mismo patrón que el resto de runners *-create-db del ecosistema:
- Checkout — descarga el código.
- Build & Push — sustituye
application.propertiesporapplication-pro.properties, compila conmvn clean package jib:build -DskipTests -U -Dimage.tag=${BUILD_NUMBER}y publica eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/dafiti-create-db:${BUILD_NUMBER}. - Deploy to GKE — obtiene credenciales del clúster
pi-cluster-hw(zonaeurope-west3-a), elimina elCronJobexistente y aplicak8s/cronjob.yamlen el namespacepi.
Imagen base de runtime (Jib): eclipse-temurin:25-jre.
Job de Jenkins:
https://jenkins-pi.hawkersco.net/job/dafiti-create-db/
12. Manejo de errores y logging
Los tres runners usan java.util.logging.Logger.
DafitiCreateDbRunner: capturaRestClientResponseExceptionen la llamada de listado paginado y notifica a Slack; captura además cualquier excepción al procesar un pedido individual (processOrderItem) sin detener el resto del bucle.DafitiReadyToShipRunner: capturaRestClientResponseExceptionen la llamada de "ready to ship" y solo registra un warning, sin notificar a Slack ni reintentar — el pedido simplemente permanece enPENDING_READY_TO_SHIPhasta la siguiente ejecución delCronJob.DafitiGenerateLabelRunner: captura excepciones genéricas endownloadAndUploadLabelygetTrackingCodey solo registra el error. Contiene dos comentariosTODOexplícitos en el código (ver sección 13) reconociendo que el manejo de "sin tracking code" y "subida SFTP fallida" está incompleto: el pedido se queda silenciosamente sin avanzar de estado, sin marcarlo como error ni notificar, hasta que en una ejecución posterior tenga tracking code o la subida funcione.
No hay configuración de logback específica.
13. Notas y consideraciones
-
⚠️ Posible bug de copia-pega en
DafitiCreateDbUtils.buildNewCustomer: En la ramaelse if (item.getAddress().getShipping() != null), la líneacountryCustomer = item.getAddress().getBilling().getCountry();llama agetBilling()en lugar degetShipping(). Si un pedido tiene dirección de envío pero no dirección de facturación (billing == null), esta línea lanzará unNullPointerExceptional intentar invocargetCountry()sobre un objeto nulo. Dado que la excepción no está capturada específicamente en este punto (se propaga hasta elcatch (Exception e)deprocessOrderItemenDafitiCreateDbRunner, que registra el error y continúa con el siguiente pedido), el efecto práctico es que ese pedido concreto nunca se persiste en cada ejecución, sin ninguna alerta específica más allá del log de error. Se recomienda corregirgetBilling()→getShipping()en esa línea. -
Dos
TODOexplícitos sin resolver enDafitiGenerateLabelRunner:processOrderLabel("marcarlo de alguna manera o repetir hasta que tenga tracking code") ydownloadAndUploadLabel("marcarlo de alguna manera o repetir hasta que se solucione si !uploaded") documentan que el manejo de estos dos casos de fallo está pendiente de diseño — actualmente el pedido queda "atascado" enPENDING_SHIPPING_PARCELsin ningún mecanismo de reintento explícito ni de alerta, dependiendo únicamente de que la condición se resuelva por sí sola en una ejecución futura delCronJob. -
Método
donwloadFileByUuidcon typo endafiti-client: El nombre del método invocado (dafitiClient.donwloadFileByUuid(uuid)) contiene un error tipográfico ("donwload" en lugar de "download"), heredado de la interfaz del cliente — no afecta al comportamiento (es solo un nombre de método Java), pero dificulta la legibilidad y la búsqueda en el código. -
descriptiondelpom.xmlheredada de otro proyecto: Elpom.xmldescribe el proyecto como "Dafiti client", pese a ser un runner batch que consume el clientedafiti-client, no el cliente en sí — probablemente copiado de la plantilla del propiodafiti-clientsin actualizar. -
Referencia a un Source ID legacy (20L) sin más contexto:
DafitiCreateDbRunner.processOrderItembusca y elimina registrosOrderMarketplaceasociados al Source ID legacy 20 antes de crear el nuevo con Source ID 60 — sugiere una migración histórica de identificador de fuente para Dafiti, cuyo detalle no está documentado en el código ni enCLAUDE.md. Pendiente de verificar el motivo exacto de esta migración. -
Cálculo de impuesto con redondeo
HALF_DOWN:setOrderFinancialsusaRoundingMode.HALF_DOWNal calcular el 19% de impuesto sobre el total — modo de redondeo menos común queHALF_UP; pendiente de verificar si es una elección deliberada para cuadrar con el cálculo fiscal de Dafiti o un valor por defecto sin revisar. -
Sin tests funcionales: Solo existe
DafitiCreateDbApplicationTests(test de contexto por defecto); no hay pruebas sobre la lógica de transformación de pedidos, el flujo de ready-to-ship ni la generación/subida de etiquetas.