Skip to main content

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

PropiedadValor
artifactIddafiti-create-db
groupIdcom.hawkersco
version1.0.25
Java25
Spring Boot4.0.6
Tipo de artefactoJAR ejecutable (imagen de contenedor vía Jib)
MódulosProyecto ú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

DependenciaVersiónPropósito
spring-boot-starter(gestionada SB4)Base de Spring Boot (contexto, CommandLineRunner, @ConfigurationProperties)
com.hawkersco:dafiti-client1.0.25-SNAPSHOTCliente @HttpExchange de la API Dafiti (DafitiClient), con autenticación OAuth2 client_credentials automática
com.hawkersco:slack-client1.0.25-SNAPSHOTCliente @HttpExchange de Slack, usado para alertas de error
com.hawkersco:logistics-commons1.0.25-SNAPSHOTEntidades JPA y servicios (Order, OrderLine, Customer, Shipment, etc.)
com.hawkersco:pi-function-commons1.0.25-SNAPSHOTUtilidades 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

SistemaProtocoloDirecciónDescripción
API Dafiti Seller Center (vía dafiti-client)HTTP REST, OAuth2SalienteListado 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)SFTPSalienteSubida de la etiqueta PDF generada a /src/pending/
Slack (vía slack-client)HTTP RESTSalienteAlertas de error HTTP en la ingestión y de Source ID no registrado
PostgreSQL (logistics, vía logistics-commons)JDBCSalientePersistencia 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 OrderMarketplace obsoletos antes de re-crearlos.
  • Prefijo de pedido interno: HWD-.
  • Método de envío hardcodeado: ID 1. Tipo de servicio de pedido hardcodeado: ID 33.
  • 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 calcular nmOrderTaxAmt.
  • Tipo de entrega en "ready to ship": dropship.
  • Flujo de estados del pedido: PENDING_READY_TO_SHIPPENDING_SHIPPING_PARCELPENDING_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

PropiedadDescripciónValor en producción
spring.datasource.url/username/passwordConexión PostgreSQL de logística${dbLogisitcsUrl} / ${dbLogisitcsUsername} / ${dbLogisitcsPassword} (mismo typo "Logisitcs" que en chatbot/coppel-create-db)
dafiti.api.urlURL base de la API Dafiti${dafitiApiUrl}
dafiti.api.grantTypeGrant type OAuth2${dafitiApiGrantType}
dafiti.api.clientId / clientSecretCredenciales OAuth2 de la integración Dafiti${dafitiApiClientId} / ${dafitiApiClientSecret}
slack.client.url / slack.auth.tokenConfiguración del cliente Slack${slackClientUrl} / ${slackAuthToken}
slack.channel.idCanal Slack para alertas${slackChannelId}
ftp.server / ftp.port / ftp.user / ftp.passConexión al servidor SFTP de etiquetas${ftpServer} / ${ftpPort} / ${ftpUser} / ${ftpPass}
ftp.dirDirectorio 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 en bradery-create-db, chatbot y coppel-create-db — se recomienda una revisión y rotación conjunta de credenciales en todos los runners *-create-db del ecosistema.

Variables de entorno (perfil de producción)

VariablePropiedad mapeada
dbLogisitcsUrl / Username / Passwordspring.datasource.*
dafitiApiUrl / GrantType / ClientId / ClientSecretdafiti.api.*
slackClientUrl / slackAuthToken / slackChannelIdslack.*
ftpServer / ftpPort / ftpUser / ftpPassftp.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:

EntidadRol
OrderMarketplaceVínculo entre el pedido interno (HWD-...) y el pedido de Dafiti; también almacena la etiqueta en base64 y el tracking code (updateOrderMarketplaceByDsOrderAndIdSource)
Customer / CustomerSourceCliente asociado al pedido, con origen registrado para Source ID 60
OrderPedido logístico completo; rawData guarda el JSON crudo del pedido Dafiti
OrderLineLíneas de producto (SKU del vendedor, cantidad fija a 1, precio, descuento calculado)
Shipment / ShipmentLineEnví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:

  1. Checkout — descarga el código.
  2. Build & Push — sustituye application.properties por application-pro.properties, compila con mvn clean package jib:build -DskipTests -U -Dimage.tag=${BUILD_NUMBER} y publica en europe-west3-docker.pkg.dev/pi-saldum/pi-repo/dafiti-create-db:${BUILD_NUMBER}.
  3. Deploy to GKE — obtiene credenciales del clúster pi-cluster-hw (zona europe-west3-a), elimina el CronJob existente y aplica k8s/cronjob.yaml en el namespace pi.

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: captura RestClientResponseException en 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: captura RestClientResponseException en la llamada de "ready to ship" y solo registra un warning, sin notificar a Slack ni reintentar — el pedido simplemente permanece en PENDING_READY_TO_SHIP hasta la siguiente ejecución del CronJob.
  • DafitiGenerateLabelRunner: captura excepciones genéricas en downloadAndUploadLabel y getTrackingCode y solo registra el error. Contiene dos comentarios TODO explí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 rama else if (item.getAddress().getShipping() != null), la línea countryCustomer = item.getAddress().getBilling().getCountry(); llama a getBilling() en lugar de getShipping(). Si un pedido tiene dirección de envío pero no dirección de facturación (billing == null), esta línea lanzará un NullPointerException al intentar invocar getCountry() sobre un objeto nulo. Dado que la excepción no está capturada específicamente en este punto (se propaga hasta el catch (Exception e) de processOrderItem en DafitiCreateDbRunner, 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 corregir getBilling()getShipping() en esa línea.

  • Dos TODO explícitos sin resolver en DafitiGenerateLabelRunner: processOrderLabel ("marcarlo de alguna manera o repetir hasta que tenga tracking code") y downloadAndUploadLabel ("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" en PENDING_SHIPPING_PARCEL sin 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 del CronJob.

  • Método donwloadFileByUuid con typo en dafiti-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.

  • description del pom.xml heredada de otro proyecto: El pom.xml describe el proyecto como "Dafiti client", pese a ser un runner batch que consume el cliente dafiti-client, no el cliente en sí — probablemente copiado de la plantilla del propio dafiti-client sin actualizar.

  • Referencia a un Source ID legacy (20L) sin más contexto: DafitiCreateDbRunner.processOrderItem busca y elimina registros OrderMarketplace asociados 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 en CLAUDE.md. Pendiente de verificar el motivo exacto de esta migración.

  • Cálculo de impuesto con redondeo HALF_DOWN: setOrderFinancials usa RoundingMode.HALF_DOWN al calcular el 19% de impuesto sobre el total — modo de redondeo menos común que HALF_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.