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 , 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.