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
| Filtro | Componente | Origen de los valores |
|---|---|---|
| Búsqueda libre | search-input | Texto (con debounce) |
| Países | countries-filter | constants/countries.ts |
| Métodos de pago | payment-methods-filter | /return-payment-method/info |
| Sources | sources-filter | /source/info |
| Estados | statuses-filter | /return-status/info |
| Almacenes | warehouses-filter | /return-store/info |
| Rango de fechas | date-picker-with-range | react-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:
- Carga estados y almacenes para poder traducir identificadores a nombres.
- 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 (commit7221345). - Construye el CSV con separador
;y BOM UTF-8 (\uFEFF) para que Excel lo abra bien en español. - Lo devuelve como
attachmentcon nombrereturns-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:
| Campo | Editable cuando… |
|---|---|
id_return_method | no is_return_payment_dynamics y no is_return_receive_dynamics |
id_return_line_status | no is_return_payment_dynamics |
id_return_origin | no payment_dynamics y no receive_dynamics |
claimed_goods_id | no payment_dynamics y no receive_dynamics |
id_return_store | no payment_dynamics y no receive_dynamics |
is_waste | no payment_dynamics y no receive_dynamics |
price_final | no payment_dynamics y no receive_dynamics |
| Aprobar todas | no payment_dynamics y no receive_dynamics |
| Reembolsar todas | no 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_finalsolo es editable si el precio actual es mayor que 0.- Si el origen es
WITHDRAWALy ya hay mercancía reclamada,claimed_goods_idse 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:
| Contexto | Estados ofrecidos |
|---|---|
is_return_payment_dynamics | solo REFUNDED y CANCELLED |
is_return_receive_dynamics | APPROVED, REFUNDED, CANCELLED |
is_return_create_dynamics | todos menos CREATED y REFUNDED |
Estado no APPROVED/REFUNDED | se oculta REFUNDED |
Estado no CREATED | se oculta CREATED |
Rol STORE | se oculta REFUNDED (una tienda no reembolsa) |
Reglas de negocio en el guardado
- No se permite la cancelación parcial: si algunas líneas tienen origen
CANCELLEDpero no todas, se muestra un aviso y no se guarda. - El precio final no puede ser negativo — se marca error en
return_line. - Mercancía reclamada implica almacén: si
claimed_goods_ides Sí,id_return_storees obligatorio (superRefine); si es No,formatReturnLineItemsfuerzaid_return_store = -1antes de enviar. - El método de devolución solo se envía si alguna línea tiene mercancía reclamada (
canEditReturnMethod); en caso contrario se mandaid_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:
- El usuario tiene rol
STORE(if (!userHasStoreRole) return). - Hay líneas que cumplen
getValidItemsToSendApprovalMail: estadoAPPROVED(id 4),is_mail_approval_senten 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.