Skip to main content

Gestión de devoluciones

1. Listado (/returns)​

app/(dashboard)/returns/page.tsx precarga en servidor la primera página (getReturns(0, 10, [])) junto con estados y almacenes, y se lo pasa a components/returns/table.tsx.

El listado pide a robo-api:

GET /return/filter?page_no=…&page_size=…&sort_by=created&direction=DESC&

El orden es siempre por fecha de creación descendente: no se puede cambiar desde la UI.

Columnas​

return_name_id (con enlace al detalle), external_id, order_number, updated, created, origin (tienda origen), stores (almacenes), final_price, id_return_status, email, country, payment_method, más una columna de acciones. El selector de columnas permite ocultar las que no interesen y usePinColums fija las laterales cuando la tabla desborda.

Filtros​

FiltroComponenteOrigen de los valores
Búsqueda libresearch-inputTexto (con debounce)
Paísescountries-filterconstants/countries.ts
Métodos de pagopayment-methods-filter/return-payment-method/info
Sourcessources-filter/source/info
Estadosstatuses-filter/return-status/info
Almaceneswarehouses-filter/return-store/info
Rango de fechasdate-picker-with-rangereact-day-picker

delete-filters limpia todos de golpe, y aparece solo si hay algún filtro activo o un rango de fechas completo.

Los filtros se combinan con mergeAndAddFilters (en lib/utils.ts), que reemplaza los valores nuevos, elimina los que quedan vacíos y añade los que no existían.

Persistencia del estado​

Filtros y paginación se guardan en sessionStorage bajo la clave RETURNS_TABLE_STATE (getReturnsTableStateFromSessionStorage / setReturnsTableStateToSessionStorage). Así, entrar en el detalle de una devolución y volver atrás conserva la búsqueda; cerrar la pestaña la descarta.

Serialización de filtros​

parseFilters convierte el estado de TanStack en query string (&id=valor). El filtro de fecha tiene un tratamiento especial:

  • Si hay fecha de inicio y fin → date_from=YYYY-MM-DD&date_to=YYYY-MM-DD.
  • Si falta alguna → dateFrom=…&dateTo=… (nombres en camelCase, ver Notas técnicas).

2. Exportación a CSV​

El botón Exportar a CSV enlaza a /api/export/csv/returns?columnFilters=<JSON> con los filtros activos. El handler:

  1. Carga estados y almacenes para poder traducir identificadores a nombres.
  2. Recorre el listado completo en páginas de 100 (while (!last)) hasta agotar los resultados — así la exportación no está limitada a la página visible (commit 7221345).
  3. Construye el CSV con separador ; y BOM UTF-8 (\uFEFF) para que Excel lo abra bien en español.
  4. Lo devuelve como attachment con nombre returns-CSV_<timestamp>.csv.

Columnas exportadas (12): Id, Id. Externo, Id. Pedido, Fecha (modificación), Fecha (creación), Tienda origen, Almacén, Precio final, Estado, E-mail, País y Método de pago. Los estados se exportan con su traducción en español; los almacenes, como nombres separados por comas; los precios, formateados con formatCurrency usando el locale es-<país>.

3. Detalle (/returns/[id])​

getReturn(id) llama a GET /return/info-all/{id}. La pantalla se compone de:

  • ReturnHeader — #HWRET… y badge de estado.
  • OrderInfo — datos del pedido y del cliente.
  • ReturnInfo — datos de la devolución (fechas, método de pago, precio final…).
  • ReturnLinesTable — tabla de líneas editables.
  • Botones PDF, Historial y Editar.

Todo el formulario lo gobierna hooks/useReturnForm.tsx con react-hook-form + zod (returnSchema) y useFieldArray sobre return_line. Cada vez que SWR revalida, el formulario se resetea con los datos frescos.

Campos editables por línea​

id_return_line_status (estado), id_return_origin (origen), claimed_goods_id (mercancía reclamada), id_return_store (almacén), is_waste (desechable) y price_final.

Matriz de editabilidad​

getFieldEditability (en lib/return-utils.ts) decide qué se puede tocar en función de tres flags de sincronización con Dynamics que devuelve robo-api:

CampoEditable cuando…
id_return_methodno is_return_payment_dynamics y no is_return_receive_dynamics
id_return_line_statusno is_return_payment_dynamics
id_return_originno payment_dynamics y no receive_dynamics
claimed_goods_idno payment_dynamics y no receive_dynamics
id_return_storeno payment_dynamics y no receive_dynamics
is_wasteno payment_dynamics y no receive_dynamics
price_finalno payment_dynamics y no receive_dynamics
Aprobar todasno payment_dynamics y no receive_dynamics
Reembolsar todasno payment_dynamics y el estado de la devolución es APPROVED

En resumen: una vez la devolución ha entrado en el flujo de pago de Dynamics, deja de ser editable.

createFieldsConfiguration afina esa matriz línea a línea:

  • La línea de gastos de envío (SKU S00233) no permite editar origen, mercancía reclamada, almacén ni desechable.
  • price_final solo es editable si el precio actual es mayor que 0.
  • Si el origen es WITHDRAWAL y ya hay mercancía reclamada, claimed_goods_id se bloquea.
  • Marcar una línea como desechable (is_waste) bloquea la selección de almacén.

Estados de línea disponibles​

getFilteredLineStatuses restringe el desplegable de estados según el contexto:

ContextoEstados ofrecidos
is_return_payment_dynamicssolo REFUNDED y CANCELLED
is_return_receive_dynamicsAPPROVED, REFUNDED, CANCELLED
is_return_create_dynamicstodos menos CREATED y REFUNDED
Estado no APPROVED/REFUNDEDse oculta REFUNDED
Estado no CREATEDse oculta CREATED
Rol STOREse oculta REFUNDED (una tienda no reembolsa)

Reglas de negocio en el guardado​

  1. No se permite la cancelación parcial: si algunas líneas tienen origen CANCELLED pero no todas, se muestra un aviso y no se guarda.
  2. El precio final no puede ser negativo — se marca error en return_line.
  3. Mercancía reclamada implica almacén: si claimed_goods_id es Sí, id_return_store es obligatorio (superRefine); si es No, formatReturnLineItems fuerza id_return_store = -1 antes de enviar.
  4. El método de devolución solo se envía si alguna línea tiene mercancía reclamada (canEditReturnMethod); en caso contrario se manda id_return_method: null.

Cálculo del precio final​

price_final = Σ price_final de las líneas cuyo estado ≠ 5 (CANCELLED) − precio del método de devolución

Un return_method_price negativo se normaliza a 0 antes de restar (commit 0029207).

Acciones masivas​

Aprobar todas y Reembolsar todas aplican el estado a todas las líneas de golpe. Reembolsar todas se bloquea si alguna línea sigue en revisión, con el mensaje correspondiente del diccionario.

Journey de aprobación​

Tras un guardado correcto, triggerJourney puede enviar el email de aprobación al cliente. Las condiciones son estrictas:

  1. El usuario tiene rol STORE (if (!userHasStoreRole) return).
  2. Hay líneas que cumplen getValidItemsToSendApprovalMail: estado APPROVED (id 4), is_mail_approval_sent en falso y que no sean la línea de gastos de envío con precio 0.

Si se cumplen, se dispara el journey APPROVED y, a continuación, se hace un segundo guardado marcando is_mail_approval_sent = true en esas líneas, para no reenviar el email en ediciones posteriores. Detalles del payload en Integraciones.

4. Historial de cambios​

El botón Historial abre un Drawer (vaul) que carga GET /return-log/info/{id}.

components/returns/return/logs.tsx no muestra el log crudo: compara cada entrada con la anterior y pinta un diff legible. Distingue el tipo de entrada por su método HTTP (POST = creación, PUT = edición) y resuelve los identificadores contra los catálogos para mostrar nombres en lugar de ids: método de devolución, artículos (estado, origen, almacén, mercancía reclamada, precio final), etc. Los valores anteriores se resaltan en amarillo y los nuevos en gris, con una flecha entre ambos.

5. Descarga del PDF​

El botón PDF es un desplegable con los 7 idiomas. Al abrirlo, firma en paralelo una URL por idioma (POST /api/get-pdf-url) y las presenta como enlaces a:

https://returns.hawkersco.com/doc/<base64url>.<hmac>

El idioma del pedido aparece primero en la lista (defaultLanguage). El PDF lo renderiza el portal del cliente, no este proyecto: ver Integraciones y PDFs y URLs firmadas.