From e7e82d95ad847d0342c480143422dd009181adf0 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 14 Sep 2026 10:53:11 +0200 Subject: [PATCH] =?UTF-8?q?feat(customers):=20implementar=20pruebas=20y=20?= =?UTF-8?q?l=C3=B3gica=20de=20ordenaci=C3=B3n=20para=20la=20lista=20de=20c?= =?UTF-8?q?lientes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/customer-list-sorting.test.ts | 44 ++++++++++++++ .../list/controllers/customer-list-sorting.ts | 45 ++++++++++++++ .../use-list-customers.controller.ts | 58 +++++++------------ .../datatable/data-table-column-header.tsx | 11 +++- tests/e2e/customers/customer-list.spec.ts | 29 ++++++++++ tests/e2e/customers/customer-search.spec.ts | 27 +++++++++ tests/e2e/customers/customer-sort.spec.ts | 43 ++++++++++++++ .../support/create-customer-via-ui.ts | 8 ++- .../support/search-customers-via-ui.ts | 16 +++++ 9 files changed, 239 insertions(+), 42 deletions(-) create mode 100644 modules/customers/src/web/list/controllers/customer-list-sorting.test.ts create mode 100644 modules/customers/src/web/list/controllers/customer-list-sorting.ts create mode 100644 tests/e2e/customers/customer-list.spec.ts create mode 100644 tests/e2e/customers/customer-search.spec.ts create mode 100644 tests/e2e/customers/customer-sort.spec.ts create mode 100644 tests/e2e/customers/support/search-customers-via-ui.ts diff --git a/modules/customers/src/web/list/controllers/customer-list-sorting.test.ts b/modules/customers/src/web/list/controllers/customer-list-sorting.test.ts new file mode 100644 index 00000000..50dfa664 --- /dev/null +++ b/modules/customers/src/web/list/controllers/customer-list-sorting.test.ts @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + CUSTOMER_LIST_SORT_FIELDS, + DEFAULT_SORT, + isCustomerListSortField, + normalizeCustomerListSort, + toCustomerListTableSort, +} from "./customer-list-sorting"; + +test("acepta customer emitido por el grid y conserva ASC/DESC en el criterio API y la URL", () => { + for (const direction of ["asc", "desc"] as const) { + const sort = normalizeCustomerListSort({ field: "customer", direction }); + assert.ok(sort); + assert.deepEqual(sort, { field: "name", direction }); + assert.deepEqual( + { orderBy: CUSTOMER_LIST_SORT_FIELDS[sort.field], order: sort.direction }, + { orderBy: "name", order: direction } + ); + assert.equal(isCustomerListSortField(sort.field), true); + assert.deepEqual(toCustomerListTableSort(sort), { field: "customer", direction }); + } +}); + +test("mantiene URLs name y preferencias antiguas NAME con el ID de columna estable", () => { + for (const field of ["name", "NAME"]) { + const sort = normalizeCustomerListSort({ field, direction: "asc" }); + assert.ok(sort); + assert.deepEqual(sort, { field: "name", direction: "asc" }); + assert.deepEqual(toCustomerListTableSort(sort), { field: "customer", direction: "asc" }); + } + assert.deepEqual(toCustomerListTableSort(DEFAULT_SORT), { field: "customer", direction: "desc" }); +}); + +test("preserva el mapping de los demás campos semánticos y rechaza campos ajenos", () => { + const sort = normalizeCustomerListSort({ field: "tradeName", direction: "desc" }); + assert.ok(sort); + assert.equal(CUSTOMER_LIST_SORT_FIELDS[sort.field], "trade_name"); + assert.deepEqual(toCustomerListTableSort(sort), sort); + for (const field of ["contact", "address", "trade_name", "toString"]) { + assert.equal(normalizeCustomerListSort({ field, direction: "asc" }), undefined); + } +}); diff --git a/modules/customers/src/web/list/controllers/customer-list-sorting.ts b/modules/customers/src/web/list/controllers/customer-list-sorting.ts new file mode 100644 index 00000000..50707d2f --- /dev/null +++ b/modules/customers/src/web/list/controllers/customer-list-sorting.ts @@ -0,0 +1,45 @@ +import type { DataTableSort, DataTableSortDirection } from "@repo/rdx-ui/components"; + +// Contrato semántico existente de URL y preferencias → columnas de API. +export const CUSTOMER_LIST_SORT_FIELDS = { + name: "name", + tradeName: "trade_name", + reference: "reference", + tin: "tin", + emailPrimary: "email_primary", + mobilePrimary: "mobile_primary", +} as const; + +export type CustomerListSortField = keyof typeof CUSTOMER_LIST_SORT_FIELDS; + +export const DEFAULT_SORT = { + field: "name", + direction: "desc", +} as const satisfies DataTableSort; + +export const isCustomerListSortField = (value: string | null): value is CustomerListSortField => { + return value !== null && Object.hasOwn(CUSTOMER_LIST_SORT_FIELDS, value); +}; + +export const isSortDirection = (value: string | null): value is DataTableSortDirection => { + return value === "asc" || value === "desc"; +}; + +// customer es el ID estable de la columna compuesta; NAME procede de preferencias antiguas. +const SORT_FIELD_ALIASES: Readonly> = { + customer: "name", + NAME: "name", +}; +const SORT_COLUMN_IDS: Readonly> = { name: "customer" }; + +export function normalizeCustomerListSort(sort: DataTableSort) { + const field = SORT_FIELD_ALIASES[sort.field] ?? sort.field; + if (!(isCustomerListSortField(field) && isSortDirection(sort.direction))) { + return undefined; + } + return { field, direction: sort.direction }; +} + +export function toCustomerListTableSort(sort: DataTableSort): DataTableSort { + return { ...sort, field: SORT_COLUMN_IDS[sort.field] ?? sort.field }; +} diff --git a/modules/customers/src/web/list/controllers/use-list-customers.controller.ts b/modules/customers/src/web/list/controllers/use-list-customers.controller.ts index 9223e847..6341a92d 100644 --- a/modules/customers/src/web/list/controllers/use-list-customers.controller.ts +++ b/modules/customers/src/web/list/controllers/use-list-customers.controller.ts @@ -1,10 +1,7 @@ import { useDebounce } from "@erp/core/hooks"; import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria"; -import { - type DataTableSort, - type DataTableSortDirection, - useDataTablePreferences, -} from "@repo/rdx-ui/components"; +import type { DataTableSort } from "@repo/rdx-ui/components"; +import { useDataTablePreferences } from "@repo/rdx-ui/components"; import { NumberHelper } from "@repo/rdx-utils"; import { useCallback, useMemo, useState } from "react"; import { useSearchParams } from "react-router-dom"; @@ -16,6 +13,15 @@ import { useCustomersListQuery, } from "../../shared"; +import { + CUSTOMER_LIST_SORT_FIELDS, + DEFAULT_SORT, + isCustomerListSortField, + isSortDirection, + normalizeCustomerListSort, + toCustomerListTableSort, +} from "./customer-list-sorting"; + type CustomerListStatusFilter = "all" | CustomerStatus; // Datos por defecto mientras se carga la consulta o en caso de error. @@ -27,33 +33,6 @@ const EMPTY_CUSTOMERS_LIST: CustomerList = { totalItems: 0, }; -// Campos que se permiten ordenar para la lista de proformas (consulta). -const CUSTOMER_LIST_SORT_FIELDS = { - name: "name", - tradeName: "trade_name", - reference: "reference", - tin: "tin", - emailPrimary: "email_primary", - mobilePrimary: "mobile_primary", -} as const; - -type CustomerListSortField = keyof typeof CUSTOMER_LIST_SORT_FIELDS; - -const DEFAULT_SORT = { - field: "NAME", - direction: "desc", -} satisfies DataTableSort; - -const DEFAULT_API_SORT_FIELD = CUSTOMER_LIST_SORT_FIELDS.name; - -const isCustomerListSortField = (value: string | null): value is CustomerListSortField => { - return value !== null && value in CUSTOMER_LIST_SORT_FIELDS; -}; - -const isSortDirection = (value: string | null): value is DataTableSortDirection => { - return value === "asc" || value === "desc"; -}; - export const useListCustomersController = () => { const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); @@ -102,10 +81,12 @@ export const useListCustomersController = () => { } : undefined; - const sort = urlSort ?? preferencesSorting ?? DEFAULT_SORT; + const sort = + urlSort ?? + (preferencesSorting ? normalizeCustomerListSort(preferencesSorting) : undefined) ?? + DEFAULT_SORT; - const orderBy = - CUSTOMER_LIST_SORT_FIELDS[sort.field as CustomerListSortField] ?? DEFAULT_API_SORT_FIELD; + const orderBy = CUSTOMER_LIST_SORT_FIELDS[sort.field]; const order = sort.direction; @@ -200,8 +181,9 @@ export const useListCustomersController = () => { ); const setSortValue = useCallback( - (nextSort: DataTableSort) => { - if (!isCustomerListSortField(nextSort.field)) { + (tableSort: DataTableSort) => { + const nextSort = normalizeCustomerListSort(tableSort); + if (!nextSort) { return; } @@ -246,7 +228,7 @@ export const useListCustomersController = () => { search, setSearchValue, - sort, + sort: toCustomerListTableSort(sort), setSort: setSortValue, statusFilter, diff --git a/packages/rdx-ui/src/components/datatable/data-table-column-header.tsx b/packages/rdx-ui/src/components/datatable/data-table-column-header.tsx index 41eae4fa..2f12372c 100644 --- a/packages/rdx-ui/src/components/datatable/data-table-column-header.tsx +++ b/packages/rdx-ui/src/components/datatable/data-table-column-header.tsx @@ -37,6 +37,7 @@ export function DataTableColumnHeader({