Skip to main content

Integraciones

1. robo-api

Toda la persistencia va contra robo-api (ROBO_API_URL), con el access token del empleado autenticado (ver Roles y control de acceso). Endpoints consumidos:

DominioEndpoints
DevolucionesGET /return/filter, GET /return/info-all/{id}, PUT /return/edit/{id}, DELETE /return/delete/{id}
Alta de devoluciónPOST /order/info, POST /order/create-return
HistorialGET /return-log/info/{id}
MétodosGET /return-method/info, POST /return-method/search, POST /new, PUT /edit/{id}, DELETE /delete/{id}
MotivosGET /return-reason/info, POST /return-reason/search, POST /new, PUT /edit/{id}, DELETE /delete/{id}
EstadosGET /return-status/info, GET /return-line-status/info
OrígenesGET /return-origin/info
Métodos de pagoGET /return-payment-method/info
AlmacenesGET /return-store/info, PUT /return-store/edit/{id}
SourcesGET /source/info
Usuarios Auth0GET /return-auth0-user/info, GET /info/{id}, POST /new, PUT /edit/{id}

Comparado con el portal del cliente, que solo usa los endpoints permitidos al scope customer, aquí se explota la superficie completa de escritura y administración de la API.

Modo de pruebas

Igual que el portal del cliente, fetchAuth añade la cabecera X-Test-Mode: true cuando API_TEST_HEADER/API_TEST_HEADER_VALUE están definidas y NODE_ENV !== "production". El TestModeFilter de robo-api redirige entonces las escrituras a las tablas sombra (ReturnTest, ReturnLineTest, ReturnLogTest).

2. Salesforce Marketing Cloud

lib/marketingCloud.tsx es el mismo cliente mínimo que en el portal del cliente: token por client_credentials cacheado en memoria y marketingCloudRequest() sobre MC_API_URL.

La diferencia está en que aquí hay tres journeys, uno por hito del ciclo de vida, cada uno con su propia EventDefinitionKey:

AcciónVariable de entornoCuándo se dispara
CREATEDMC_EVENT_KEY_CREATEDAl confirmar un alta manual, solo si el empleado marca el checkbox de enviar email
APPROVEDMC_EVENT_KEY_APPROVEDAl guardar el detalle con rol STORE, si hay líneas recién aprobadas sin email enviado
REFUNDEDMC_EVENT_KEY_REFUNDEDTiene payload y clave configurada, pero ningún componente lo invoca (ver Notas técnicas)

/api/trigger-journey resuelve la clave a partir de la acción y publica:

POST ${MC_API_URL}/interaction/v1/events
{
ContactKey: data.EmailAddress,
EventDefinitionKey: <clave según la acción>,
Data: { …payload… }
}

:::info Protección en entornos no productivos Si NODE_ENV !== "production", el handler sobrescribe data.EmailAddress con TEST_EMAIL antes de enviar, igual que en el portal del cliente. Como el ContactKey se deriva de ese campo, todos los eventos de prueba se agrupan bajo el mismo contacto. :::

Construcción de los payloads (lib/journeys.ts)

prepareJourneyPayload(action, ctx) es una función sobrecargada por tipos que delega en tres generadores:

generateJourneyDataForCreation — el más completo. Toma el idioma del order.locale (getLanguageFromLocale), monta el items como JSON (excluyendo la línea de gastos de envío), serializa el método de devolución con su título e instrucciones en el idioma del pedido y añade la URL absoluta del PDF:

Country__c, EmailAddress, FirstName, OrderNumber, SubscriberKey (sfsc_user_id),
Return_date, Return_number, shippingCost, showShippingCost, TotalFinal,
Return_method (JSON), items (JSON), pdfUrl

generateJourneyDataForApprovalEmailAddress, Country__c, FirstName, OrderNumber, SubscriberKey, TotalFinal e items. Aquí la línea de gastos de envío sí se incluye, pero con el nombre traducido desde constants/shipping-cost.ts (Gastos de envío, Shipping costs, Frais de port, Versandkosten, Costi di spedizione, Custos de envio, Τέλη αποστολής) en mayúsculas, según el locale del pedido.

generateJourneyDataForRefundEmailAddress, Country__c, FirstName, OrderNumber, SubscriberKey y Amount.

Idempotencia del email de aprobación

El journey APPROVED no debe reenviarse. El mecanismo, en useReturnForm:

  1. getValidItemsToSendApprovalMail selecciona las líneas con estado APPROVED (id 4), is_mail_approval_sent falso y que no sean la línea de gastos de envío con precio 0.
  2. Si hay alguna, se dispara el journey.
  3. Se vuelve a llamar a PUT /return/edit/{id} marcando is_mail_approval_sent = true en esas líneas.

El flag vive en robo-api, así que es persistente entre sesiones y usuarios.

3. El PDF: integración entre los dos portales

Este proyecto firma enlaces de PDF pero no renderiza ninguno: no existe ninguna ruta /doc en el repositorio.

sequenceDiagram
participant E as Empleado
participant B as Back-office
participant C as returns.hawkersco.com<br/>(portal del cliente)
participant R as robo-api

E->>B: Abre el desplegable PDF
B->>B: POST /api/get-pdf-url × 7 idiomas<br/>HMAC(PDF_SIGNATURE_SECRET)
B-->>E: /doc/<base64url>.<hmac> × 7
E->>C: GET https://returns.hawkersco.com/doc/<…>
C->>C: verifySignedPdfUrl (mismo secreto)
C->>R: GET /return/info
C-->>E: PDF renderizado

lib/crypto.tsx es idéntico al del portal del cliente salvo que aquí no existe getSignedPdfStatusUrl: firma { returnId, email, language } con HMAC-SHA256 y devuelve /doc/<base64url>.<firma>. El prefijo https://returns.hawkersco.com se añade en los dos sitios donde se usa (pdf-button.tsx y journeys.ts).

:::warning Dependencia crítica compartida Los dos proyectos deben tener exactamente el mismo PDF_SIGNATURE_SECRET. Si se rota en uno y no en el otro, todos los enlaces que genere el back-office devolverán 400 Invalid or tampered URL en el portal del cliente, y los enlaces ya enviados por email dejarán de funcionar. Cualquier rotación debe ser coordinada entre ambos Secret de Kubernetes. :::

Además, el dominio returns.hawkersco.com está hardcodeado en los dos proyectos, por lo que un back-office de desarrollo genera enlaces que apuntan a producción (siguen siendo válidos, porque la firma no depende del host).