trustpilot-invitation-sender
1. Descripción general
Según el pom.xml, el proyecto se describe como "Trustpilot Invitation Sender". Es un microservicio batch (runner) que envía invitaciones de reseña de Trustpilot (de servicio y de producto) a clientes cuyo pedido fue entregado hace exactamente 3 días, consultando el datawarehouse corporativo en Snowflake y excluyendo clientes con incidencias de soporte abiertas o direcciones de correo internas.
2. Información técnica
| Campo | Valor |
|---|---|
artifactId | trustpilot-invitation-sender |
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 2 CommandLineRunner, ambos activos.
| Orden | Runner | Marca | Tiendas (id_shop) |
|---|---|---|---|
| 1 | TrustpilotInvitationSenderHwRunner | Hawkers Co | 1, 2, 3, 4, 5, 16, 33, 34, 35, 36 |
| 2 | TrustpilotInvitationSenderNwRunner | Northweek | 13 |
.config—SnowflakeConfig(DataSourcecon autenticación RSA por clave privada),SnowflakeJdbcTemplateConfig(JdbcTemplatesobre Snowflake),SnowflakeProperties..utils—TrustpilotInvitationSenderUtils(consulta SQL a Snowflake + mapeo de filas),SnowflakeKeyLoader(decodifica Base64 y descifra la clave privada PKCS8)..models—InvitationRow(record con los datos de la fila de resultado).
flowchart TD
A["1. TrustpilotInvitationSenderHwRunner"] -->|getInvitations, hoy-3 días| B[(Snowflake DWH)]
C["2. TrustpilotInvitationSenderNwRunner"] --> B
A -->|createInvitationRow, filtra emails internos| D[TrustpilotInvitationsClient]
C --> D
D -->|createInvitation, service+product review| E[Trustpilot API]
Flujo (idéntico en ambos runners, solo cambia el filtro de tiendas y la plantilla/reply-to por marca): ejecuta una consulta SQL sobre Snowflake que obtiene, para las tiendas de la marca, los pedidos entregados (ENTREGADO) exactamente 3 días atrás (o, si la tienda no tuvo entregas confirmadas en los últimos 30 días, los pedidos cuyo último pago fue hace 3 días), excluyendo clientes con casos de soporte abiertos por NOT_RECEIVED_ORDER, RECEIVED_PRODUCT_DEFECT o RECEIVED_ORDER_INCOMPLETE, y excluyendo SKUs con prefijo DN. Por cada fila, filtra los correos internos (@saldum.com o que contengan hawkers), resuelve el idioma/URL de redirección según el país (con casos especiales para países hispanohablantes: AD, MX, CO) y la plantilla según un mapa país→plantilla cargado desde JSON; construye una invitación combinada de reseña de servicio y de producto, y la envía a la API de Trustpilot.
4. Dependencias principales
| Dependencia | Propósito |
|---|---|
spring-boot-starter | Núcleo de Spring Boot (sin web, es un runner CLI) |
spring-jdbc | JdbcTemplate/DataSource para Snowflake |
net.snowflake:snowflake-jdbc | Driver JDBC de Snowflake |
org.bouncycastle:bcprov-jdk18on / bcpkix-jdk18on | Descifrado de la clave privada RSA (PKCS8) usada para autenticación en Snowflake |
com.hawkersco.connectors:api-connectors | TrustpilotInvitationsClient y modelos (Invitation, ServiceReviewInvitation, ProductReviewInvitation) |
commons-io | Lectura de los ficheros JSON de recursos |
com.fasterxml.jackson.core:jackson-databind | Parseo del mapa país→plantilla |
| Lombok (annotation processor) | Generación de código boilerplate |
5. API / Endpoints
No aplica a este proyecto. Es un batch/runner sin capa REST.
6. Integraciones externas
| Sistema | Protocolo | Dirección | Detalle |
|---|---|---|---|
| Snowflake (DWH) | JDBC con autenticación por clave RSA | Entrante | Consulta de pedidos entregados elegibles para invitación |
| Trustpilot API | HTTP (TrustpilotInvitationsClient, librería api-connectors) | Saliente | Envío de invitaciones de reseña de servicio y de producto |
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 |
|---|---|
trustpilot.username / .apikey / .password / .apisecret | Credenciales de la API de Trustpilot |
trustpilot.businessunitid.hw / .nw | ID de business unit de Trustpilot por marca |
bi.filtershopid.hw / .nw | IDs de tienda incluidos en la consulta, por marca |
bi.db.server / .name / .schema / .snowflakewarehouse / .role / .user / .port | Conexión a Snowflake |
bi.db.private-key / .passphrase | Clave privada RSA (Base64/PKCS8 cifrada) y su contraseña, para autenticación en Snowflake |
trustpilot.default.template.hw / .nw | Plantilla de invitación por defecto por marca |
trustpilot.default.replyto.hw / .nw | Dirección de respuesta por marca |
Recursos JSON (trustpilotlocales.json, trustpilotcountrytemplatemap_hw.json, trustpilotcountrytemplatemap_nw.json) | Mapas de país → locale/plantilla/URL de redirección |
⚠️ Alerta de seguridad
El fichero src/main/resources/application.properties (perfil local) contiene actualmente credenciales reales en texto plano: credenciales completas de la API de Trustpilot (usuario, contraseña, API key y API secret — las mismas ya señaladas como expuestas en trustpilot-catalog-updater), y la clave privada RSA completa (Base64/PKCS8 cifrada) y su contraseña de descifrado usadas para autenticar contra Snowflake. Esta última es especialmente sensible: da acceso directo al datawarehouse corporativo. Ninguno de estos valores se ha reproducido en este documento. Se recomienda:
- Rotar la clave privada RSA de Snowflake y su contraseña con prioridad alta, dado el nivel de acceso que otorgan.
- Rotar las credenciales de Trustpilot.
- 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
No hay base de datos propia gestionada por este proyecto: se consulta el datawarehouse corporativo Snowflake en modo solo lectura (tablas lkp_order, lkp_customer, lkp_cs_case, stg_pi_shipment_history, etc.), sin persistir ningún dato localmente. No hay Flyway/Liquibase.
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 11:30 (schedule: "30 11 * * *", zona horaria Europe/Madrid). Se ejecutan en orden los 2 runners de la tabla de la sección 3.
10. Ejecución en local
Requisitos previos: JDK 25, Maven, acceso a Snowflake (clave RSA válida) y credenciales de la API de Trustpilot. Requiere el flag de JVM --add-opens=java.base/java.nio=ALL-UNNAMED (necesario para el driver de Snowflake), ya configurado en el pom.xml y en el CronJob vía JAVA_TOOL_OPTIONS.
# Compilar
./mvnw clean install
# Ejecutar la aplicación localmente
./mvnw spring-boot:run
# Ejecutar tests
./mvnw test
# Ejecutar un test concreto
./mvnw test -Dtest=ClassName
# Generar el JAR
./mvnw clean package
Al ser un CommandLineRunner, no expone Actuator/health: la verificación se hace revisando el log de consola o las invitaciones reflejadas en el panel de Trustpilot Business.
11. Despliegue
- Imagen: construida con
jib-maven-plugin(baseeclipse-temurin:25-jre,containerizingMode=packaged, flag JVM--add-opensincluido), publicada eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/trustpilot-invitation-sender:<tag>. - Orquestación: Kubernetes
CronJoben el clúster GKEpi-cluster-hw, namespacepi, contenedor no privilegiado, ejecutándose diariamente a las 11:30 (Europe/Madrid). - CI/CD (Jenkins): pipeline real de 3 etapas —
Checkout→Build & Push→Deploy to GKE. ElCLAUDE.mddescribe un pipeline conKICS scan,SonarQubeytestque no existen en elJenkinsfileactual (mismo patrón detectado en varios proyectos hermanos de este lote).
Job de Jenkins: https://jenkins-pi.hawkersco.net/job/trustpilot-invitation-sender/
12. Manejo de errores y logging
No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). getInvitations captura cualquier excepción de la consulta a Snowflake, registra Level.SEVERE y devuelve una lista vacía en lugar de propagar el error (mismo patrón de fallo silencioso detectado en trustpilot-catalog-updater). Al enviar cada invitación, un fallo de la API de Trustpilot se registra como warning con el cuerpo de la respuesta, sin interrumpir el envío del resto de invitaciones. Logging mediante java.util.logging.Logger estándar (consola).
13. Notas y consideraciones
- Todo el código Java vive en el paquete
com.hawkersco.trustpilotcatalogupdater, idéntico al de su proyecto hermanotrustpilot-catalog-updater, en lugar decom.hawkersco.trustpilotinvitationsender. Es un resto evidente de haber clonado el proyectotrustpilot-catalog-updatercomo punto de partida sin renombrar el paquete base — no afecta al funcionamiento (Spring Boot escanea el paquete raíz de la clase@SpringBootApplicationtal cual esté), pero puede inducir a confusión a cualquier desarrollador que navegue el código esperando un paquete con el nombre del proyecto. - Fallo silencioso en la consulta a Snowflake: igual que en
trustpilot-catalog-updater, si la consulta SQL falla,getInvitations()registra el error y devuelve una lista vacía; el runner continúa sin enviar ninguna invitación ese día, sin que la ejecución falle explícitamente ni se notifique por ningún canal (no hay integración con Slack en este proyecto). CLAUDE.mdverificado y consistente con el código en el resto de aspectos: los 2 runners y su orden, el filtro de tiendas por marca, la lógica de elegibilidad SQL (estadoENTREGADOa los 3 días, exclusión de casos de soporte y SKUsDN), el filtrado de emails internos, y las clases de configuración de Snowflake, todos coinciden con el código real, verificado directamente enTrustpilotInvitationSenderHwRunneryTrustpilotInvitationSenderUtils.- Pipeline de Jenkins más simple de lo documentado: ver hallazgo en la sección 11.
- Ver alerta de seguridad en la sección 7 sobre la clave privada de Snowflake y las credenciales de Trustpilot expuestas en
application.properties.