Skip to content

feat: [TESIS-125] search the catalog against the backend - #49

Merged
TomasMartin2004 merged 6 commits into
masterfrom
TESIS-125-catalog-search-from-the-backend
Sep 28, 2026
Merged

TomasMartin2004 merged 6 commits into
masterfrom
TESIS-125-catalog-search-from-the-backend

Conversation

@TomasMartin2004

@TomasMartin2004 TomasMartin2004 commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

🔗 Link

https://proyectofinalfrlp.atlassian.net/browse/TESIS-125

📝 Descripción

El buscador de productos del paso 1 del alta manual pasa a filtrar contra el backend. Antes traía una página de cien productos y filtraba en memoria, porque cuando se escribió GET /products sólo paginaba.

El problema que cierra. Más allá de ese corte, un producto no aparecía nunca en el buscador — y el operador no tenía cómo distinguirlo de «no existe». TESIS-62 (proyecto-api#76, mergeado) agregó search, así que el filtro se muda a donde está el catálogo entero.

Decisiones:

  • MUI no vuelve a filtrar. filterOptions={(options) => options} es lo que hace que esto funcione de verdad: sin eso el Autocomplete filtra otra vez la respuesta del servidor por la etiqueta visible, y esconde coincidencias que el backend sí encontró. Es el detalle que convierte «pedirle al backend» en «mostrar lo que el backend contestó».
  • El término se debouncea, así las tres pulsaciones de «sen» son una sola consulta y no tres. Se reusa useDebouncedValue, el mismo del buscador del catálogo.
  • El término entra en la query key, así volver a un término ya tipeado sale de la caché en vez de la red.
  • keepPreviousData evita que la lista parpadee vacía entre búsquedas: mientras llega la nueva se siguen viendo las coincidencias de la anterior.
  • Un término vacío no manda search. El backend lo leería como un filtro por cadena vacía; mismo criterio que toFilters del listado de órdenes. Con el buscador recién abierto se ve la primera página, que es algo en vez de nada.
  • CATALOG_PAGE_SIZE (100) pasa a CATALOG_MATCHES (20). Ya no es «el catálogo entero» sino «cuántas coincidencias se muestran», y veinte alcanzan de sobra para elegir una: el operador acota tipeando.
  • El buscador se limpia al agregar la línea, así el siguiente SKU arranca en limpio y no sobre el filtro anterior.
  • ProductPicker queda controlado. El término vive en la página porque de ahí sale la consulta: el componente no decide cuándo se busca ni con qué demora.

🛠️ Cambios realizados

  • features/orders/api.ts — fetchCatalogProducts(search) manda el término; CATALOG_MATCHES reemplaza a CATALOG_PAGE_SIZE.
  • features/orders/hooks/useCatalogProducts.ts — recibe el término, lo pone en la key y usa keepPreviousData.
  • features/orders/queryKeys.ts — el término entra en la clave.
  • features/orders/components/ProductPicker/ — buscador controlado, sin filterOptions propio.
  • features/orders/pages/NewOrderPage.tsx — el estado del término y su debounce.
  • features/orders/utils/draft.ts — se elimina filterCatalog y sus tests: el filtro ya no vive acá.
  • docs/guidelines/architecture.md — la línea de orders.
  • Tests: 3 en api.test.ts (el término viaja, se recorta, no viaja vacío) y 3 en NewOrderPage.test.tsx (pide al backend lo tipeado, no pide por pulsación, muestra sin volver a filtrar).

🧪 Cómo probarlo (Opcional)

Precondiciones: backend con master actual corriendo en localhost:3000, seeds del tenant norte, sesión iniciada.

Caso 1 — la búsqueda es del backend

  1. Ir a Órdenes → Crear orden. El buscador abierto muestra las primeras coincidencias.
  2. Tipear cab: en la pestaña de red se ve un solo GET /products?page=1&per_page=20&search=cab, no uno por letra.
  3. Borrar todo: vuelve a pedir sin el parámetro search.

Caso 2 — lo que antes no se encontraba

  1. Con más de veinte productos cargados, buscar uno creado en último lugar por su SKU: aparece. Antes, si caía más allá del corte, no aparecía nunca.

Caso 3 — que no filtre dos veces

  1. Buscar cab y esperar los resultados.
  2. Seguir tipeando hasta cable industrial. Mientras viaja la búsqueda nueva, la lista sigue mostrando las coincidencias de cab en vez de vaciarse: eso es lo que el filtro de MUI escondería.

(La versión anterior de este caso decía «un producto que matchea por un campo que la etiqueta no muestra». No existe: el backend busca por sku y por name, y los dos están en la etiqueta. Corregido tras la review de Lorenzo.)

Caso 5 — elegir una opción no es buscar

  1. Tipear PX-9021 y elegir la opción.
  2. En la pestaña de red no aparece un GET /products?...&search=PX-9021-LRG+·+Router+industrial...: lo único que viajó es lo que se tipeó.

Caso 4 — bordes

  1. Agregar una línea: el buscador queda limpio para el siguiente SKU.
  2. Un producto ya agregado sigue apareciendo deshabilitado con «Ya está en la lista».
  3. Con la API caída, el aviso con «Reintentar» sigue apareciendo y el formulario del cliente no se pierde.

📸 Evidencia (Opcional)

Antes Una página de 100 productos filtrada en memoria: lo que cayera más allá del corte era invisible para el buscador, sin forma de distinguirlo de «no existe». Y al elegir una opción, su etiqueta viajaba como término y dejaba la lista vacía.
Después El backend filtra y devuelve hasta 20 coincidencias por término; lo único que se busca es lo que se tipea.

La sonda con la que reproduje el bug de la etiqueta, sobre la rama antes del arreglo:

>>> lastSearch tras elegir: "PX-9021-LRG · Router industrial de alta densidad"

¿Afecta la arquitectura o genera un nuevo patrón?
No. Es el patrón que ya usa el buscador del catálogo: término debounceado, en la query key, filtrado por el backend.

Verificación: npm run lint, prettier --check ., npm run build y npm run test (461 tests, 0 fallas).

Dientes: volver a propagar el reset de MUI pone en rojo los dos ejemplos de la selección; quitar filterOptions pone en rojo el de «no filtrar dos veces». Los escribí primero sin esperar al debounce y pasaban igual: la espera es parte del ejemplo, no un detalle.


🤖 Generated with Claude Code

https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp

The picker pulled one page of a hundred products and filtered it in memory,
because when it was written `GET /products` only paginated. Past that cut a
product simply never appeared, and the operator had no way to tell that
apart from "it does not exist". TESIS-62 added `search`, so the filter moves
to where the whole catalog is.

`filterOptions` now returns the options untouched. Without that MUI filters
the server's answer a second time by the visible label, which would hide
matches the backend did find.

What the term does is debounced, so the three keystrokes of "sen" are one
request, and it travels in the query key, so going back to a term already
typed comes from the cache. `keepPreviousData` keeps the list from blinking
empty between searches.

An empty term does not send `search` at all: the backend would read it as a
filter by empty string.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp
@TomasMartin2004
TomasMartin2004 requested a review from a team as a code owner September 23, 2026 02:39
@TomasMartin2004
TomasMartin2004 requested review from LoLoo03 and removed request for a team September 23, 2026 02:39

@LoLoo03 LoLoo03 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review de TESIS-125: el buscador de productos del alta manual pasa a buscar en el backend

Veredicto: pedir cambios. El enfoque es correcto y sigue el mismo patrón que el buscador del inventario. Hay un bug al elegir una opción, un test que no prueba lo que dice y un comentario con una justificación que no es cierta.

En la rama del PR corrí lint, prettier --check, test y build, y pasaron todos: 454 tests, 0 fallas, igual que dice la descripción. Después volví a TESIS-64-reports-analytics-overview, que quedó limpia.

  1. Bug: al elegir un producto, su etiqueta se manda como búsqueda

ProductPicker.tsx:81

Cuando se elige una opción, MUI escribe la etiqueta en el campo y llama a onInputChange con reason: 'reset'. Como el campo está controlado, ese texto llega a search y, pasado el debounce, al backend. Lo probé con un test temporal en la rama: tras elegir PX-9021, la búsqueda que viaja es:

"PX-9021-LRG · Router industrial de alta densidad"

En el backend, search_catalog hace sku ILIKE … OR name ILIKE …, así que ese texto no encuentra nada. Consecuencias:

Cada vez que se elige un producto se hace un request que no sirve.
La lista queda vacía. Si el operador reabre el buscador para cambiar de producto, ve «Ningún producto coincide.» en lugar de las coincidencias de lo que había tipeado.

Arreglo sugerido (no lo probé): que el campo lo maneje MUI y que solo lo que el operador tipea llegue como término.

// sin inputValue={search}
onInputChange={(_event, value, reason) => {
// Al elegir una opción MUI escribe su etiqueta en el campo: eso no es una búsqueda.
if (reason === 'input' || value === '') onSearchChange(value)
}}

El caso value === '' cubre la cruz de borrar y el reinicio que hace MUI al pasar product a null después de agregar la línea. Con ese cambio, la prop search deja de hacer falta. Convendría agregar un test que cubra esto: elegir un producto y comprobar que lastSearch() sigue siendo lo que se tipeó.

  1. El test de «no volver a filtrar» pasaría también sin el cambio

NewOrderPage.test.tsx:168

El test tipea sensor y espera ver «Sensor de presión X4». Esa opción aparecería igual con el filtro que MUI usa por defecto, porque sensor está en la etiqueta, así que el test pasaría aunque se sacara filterOptions={(options) => options}. Hay que tipear algo que no esté en la etiqueta. Con zzz lo probé y la opción sí aparece, o sea que el comportamiento funciona; lo que falta es que el test lo cubra.

  1. El comentario y el «Caso 3» explican el cambio con algo que no existe

ProductPicker.tsx:84-85

El backend busca solo por sku y name (proyecto-api → app/models/product.rb:94), y los dos ya aparecen en la etiqueta visible. No hay ningún producto que coincida «por su descripción», así que el «Caso 3» del plan de pruebas no se puede reproducir.

Igual está bien no dejar que MUI filtre, porque ese filtro sobra cuando busca el servidor. Hay que corregir el motivo en el comentario y en la descripción del PR. Un efecto real que conviene mencionar: con keepPreviousData, mientras se escribe se ven sin filtrar los resultados de la búsqueda anterior hasta que llega la nueva. Es un costo aceptable, pero debería quedar escrito.

Menores (nits)
Término sin recortar en la query key (queryKeys.ts:36): api.ts recorta el término, pero la key usa el término sin recortar. Entonces "cab" y "cab " son dos entradas de caché y dos requests que devuelven lo mismo. Se resuelve recortando en el hook antes de armar la key.
loading={isPending || isFetching} casi nunca se ve: MUI solo muestra loadingText cuando no hay opciones, y con keepPreviousData casi siempre las hay. Además el texto «Cargando el catálogo…» ya no describe lo que pasa: ahora se está buscando.
Evidencia sin completar: la sección de evidencia todavía dice «(adjuntar captura…)».

Picking an option made MUI write its label into the field, and the
controlled input sent that label to the backend as the search term.
`search_catalog` matches on sku and name, so it found nothing and the
list came back empty for the next time the picker was opened.

MUI owns the field now; only `reason: 'input'` and an empty value reach
the search, which also covers the clear button and the reset that
follows adding a line.

Three more things the review found:

- The test for "do not filter twice" typed a term that was in the
  label, so it passed with MUI's own filter too. It now types one that
  is not, and fails if `filterOptions` is removed.
- The comment justified skipping MUI's filter with a match "by
  description" that does not exist. The real effect is that with
  keepPreviousData the previous matches stay visible while the new
  search travels.
- The query key used the untrimmed term, so "cab" and "cab " were two
  cache entries and two identical requests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp
@TomasMartin2004

Copy link
Copy Markdown
Contributor Author

Respuesta — TESIS-125 (review de Lorenzo, 24/sep)

Commit en TESIS-125-catalog-search-from-the-backend: c9b9321.

Los tres puntos eran reales y los tres están resueltos. Suite 461 tests, 0 fallas (54 archivos) · npm run lint, prettier --check y npm run build limpios.

🔴 1. El bug: la etiqueta de la opción elegida viajaba como búsqueda

Lo reproduje antes de tocar nada, con una sonda propia sobre la rama, y sale exactamente lo que decís:

>>> lastSearch tras elegir: "PX-9021-LRG · Router industrial de alta densidad"

Tomé el arreglo que proponías: el campo lo maneja MUI y sólo lo tipeado se propaga.

onInputChange={(_event, value, reason) => {
  if (reason === 'input' || value === '') onSearchChange(value)
}}

Con eso la prop search dejó de hacer falta y salió del componente y de NewOrderPage. También salió el onSearchChange('') que había dentro de add(): al volver product a null, MUI limpia el campo y avisa con valor vacío, que es justo el caso que el guard deja pasar. O sea que el vaciado sigue ocurriendo, por un solo camino en vez de dos.

Tres ejemplos nuevos, y uno tiene una trampa que vale la pena contar: los escribí, pasaron, y no probaban nada. Lo que viaja pasa por un debounce de 300 ms, así que al afirmar justo después del click el término todavía no había llegado — el ejemplo pasaba por llegar antes, no por el comportamiento. Con la regresión puesta (volver a propagar el reset) seguían en verde. Agregué un settle() que deja vencer el debounce, y recién ahí tienen dientes:

  • does not search for the label of the option that was picked — revisa todos los términos pedidos, no sólo el último.
  • keeps searching for what was typed after picking an option
  • empties the search once the line is added — cubre el vaciado por el camino nuevo.

Con onInputChange={(_event, value) => onSearchChange(value)} de vuelta, los dos primeros caen y los otros 18 del archivo siguen verdes.

🟡 2. El test de «no volver a filtrar» pasaba igual sin el cambio

Cierto: sensor está en la etiqueta, así que el filtro de MUI lo dejaba pasar igual. Ahora tipea zzz, que no está en ninguna. Verificado: quitando filterOptions={(options) => options} el ejemplo se pone en rojo, solo.

🟡 3. El comentario explicaba el cambio con algo que no existe

También cierto, y el error era mío al leer el backend: search_catalog hace sku ILIKE … OR name ILIKE … (app/models/product.rb:94), y los dos están en la etiqueta visible. No hay producto que matchee «por su descripción»: el Caso 3 del plan de pruebas no se podía reproducir porque no existe.

Reescribí el comentario con el efecto real, que es el que mencionás:

Filtrar de nuevo acá no agregaría nada y sí quitaría: con keepPreviousData la lista sigue mostrando las coincidencias de la búsqueda anterior mientras llega la nueva, y el filtro de MUI —que compara contra lo ya tipeado— las escondería tras un «Ningún producto coincide.» que dura lo que tarda el request.

La descripción del PR quedó corregida igual, y el Caso 3 reemplazado por uno que sí se puede hacer.

Nits

  • Término sin recortar en la clave: recortado en el hook, que es donde se arman la clave y el pedido con el mismo valor. Cuatro ejemplos nuevos en useCatalogProducts.test.tsx —archivo que no existía—: que pide lo que recibe, que ' cable ' pide 'cable', que la caché queda bajo el término recortado, y que keepPreviousData sostiene las coincidencias anteriores.
  • loadingText: tenías razón en que el texto ya no describe lo que pasa. Pasó a «Buscando…». El loading lo dejé: se ve poco, pero cuando se ve es en la primera búsqueda, que es justo cuando no hay nada que mostrar.
  • Evidencia: completada en la descripción.

Gracias — el primero era un bug de verdad y los otros dos eran un test y un comentario que decían más de lo que hacían.

🤖 Generated with Claude Code

https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp

TomasMartin2004 and others added 2 commits September 24, 2026 15:47
TESIS-58 and TESIS-61 landed and both touch orders. Three conflicts,
all of them "keep both sides":

- queryKeys.ts: the catalog key keeps the search term, and the keys
  that TESIS-61 added for stocks and warehouses stay.
- api.test.ts: one import list with the helpers of both cards.
- architecture.md: the orders row names the search of this card and the
  steps of the wizard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp
The merge of TESIS-61 brought a second user of ProductPicker: the "add
line" toolbar of the edit screen. It called the catalog with no term,
which the type checker caught — the tests did not, because vitest does
not type check.

It now holds its own debounced term and hands it to the same hook, the
way step 1 of the wizard does, with an example that types in that
search and asserts what travels.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp
@TomasMartin2004

Copy link
Copy Markdown
Contributor Author

Actualizado contra master, que en la última hora se llevó TESIS-58 y TESIS-61.

Tres conflictos, los tres de «conservar las dos partes»: la clave del catálogo —que acá lleva el término y allá sumó las de stocks y depósitos—, la lista de imports de api.test.ts, y la fila de orders en architecture.md, que ahora nombra el buscador de esta card y los pasos del asistente.

Y una cosa que el merge de texto no podía ver: TESIS-61 trajo un segundo consumidor de ProductPicker —la barra de «agregar línea» de la pantalla de modificación—, que llamaba al catálogo sin término. Compilaba antes porque la prop no existía; con esta card, no. Lo destapó npm run build; la suite no, porque Vitest no chequea tipos.

Así que esa pantalla ahora también busca contra el backend: su propio término debounceado, el mismo hook, el mismo patrón que el paso 1. Va con un ejemplo que tipea en ese buscador y verifica qué viaja.

545 tests, 0 fallas (59 archivos) · npm run lint, prettier --check y npm run build limpios.

@LoLoo03 los tres puntos de tu review están resueltos desde c9b9321, con el detalle en el comentario anterior; esto de arriba es sólo la puesta al día con master.

🤖 Generated with Claude Code

https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp

@Sanntinat Sanntinat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revisión — TESIS-125 (PR #49) · El buscador del alta manual busca en el backend

Revisión contra origin/master de proyecto-web, sobre TESIS-125-catalog-search-from-the-backend (9d2167d), con la card TESIS-125 (sin comentarios de alcance), la review de Lorenzo (CHANGES_REQUESTED) y las dos respuestas de Tomás al lado.

Check Resultado
npm run test (local, sobre la rama) 59 archivos · 545 tests · 0 failures
npm run lint · prettier --check . · npm run build (local) Limpios
CI (GitHub Actions) Lint & Format · Branch Naming Convention · Unit Tests · Build: success
Rama vs master Al día (ya trae TESIS-58 y TESIS-61)
Conflictos con los demás PRs abiertos Ninguno (#50 sólo toca un comentario de orders/api.ts, en otra zona)

✅ Los tres puntos de Lorenzo están resueltos, y los verifiqué rompiéndolos

No me quedé con que la suite pase. Deshice cada arreglo, de a uno, y corrí NewOrderPage.test.tsx + useCatalogProducts.test.tsx:

Rotura Resultado
1. Volver a propagar el reset de MUI (onInputChange={(_e, value) => onSearchChange(value)}), que era el bug 2 failures: does not search for the label of the option that was picked y keeps searching for what was typed after picking an option
2. Sacar filterOptions={(options) => options} 1 failure: lists what the backend returned without filtering it again, el que ahora tipea zzz
3. Armar la clave con el término sin recortar (el nit) 2 failures: treats a term with surrounding spaces as the same search y files it in the cache under the trimmed term
Sacar keepPreviousData 1 failure: keeps the previous matches while the new search travels

Cada rotura hace fallar sólo sus ejemplos y ninguno pasa por construcción. El detalle del settle() que cuenta Tomás es real: sin dejar vencer el debounce, los ejemplos de la selección pasarían aunque el bug estuviera.

El comentario de ProductPicker.tsx ahora da el motivo verdadero para no refiltrar (con keepPreviousData, el filtro de MUI escondería las coincidencias anteriores mientras viaja la búsqueda nueva), y el «Caso 3» que no se podía reproducir salió de la descripción.

✅ El segundo consumidor que trajo TESIS-61 quedó bien resuelto

Al actualizarse contra master apareció NewLineToolbar, el buscador de la pantalla de modificación, que usaba el catálogo sin término. Lo detectó npm run build y no la suite, porque Vitest no chequea tipos. La solución repite el patrón del paso 1 (término propio con debounce, mismo hook) y va con su test. También tiene dientes: si OrderEditForm vuelve a pedir el catálogo con '', falla asks the backend for what was typed in the line search.

✅ El criterio de la card, contra la API

La card pide que, con más de 100 productos, buscar uno lo encuentre. Hice una sonda de request spec sobre la API con los parámetros exactos que ahora manda el front (page=1&per_page=20&search=…) y 106 productos cargados:

antes  (page=1, per_page=100, filtrado en memoria) contiene SKU-000: false
ahora  (page=1, per_page=20, search=SKU-000)       -> ["SKU-000"]

Detalle: el listado ordena por created_at descendente, así que el producto que quedaba afuera era el primero creado, no «el creado en último lugar» como dice la card. El criterio se cumple igual. Sólo lo aclaro para quien lo pruebe a mano.

🟡 Menores

  • El estado de carga no es el mismo en las dos pantallas. NewOrderPage pasa loading={catalog.isPending || catalog.isFetching}, y OrderEditForm pasa productsLoading={catalog.isPending}. Hay un caso donde se nota: si la búsqueda anterior no trajo nada (zzz) y se tipea otra, keepPreviousData sostiene la lista vacía. Mientras viaja el request, el paso 1 dice «Buscando…» y la pantalla de modificación dice «Ningún producto coincide.». Es una línea.
  • Formalidad: la review de Lorenzo sigue en CHANGES_REQUESTED. Los tres puntos están resueltos desde c9b9321, pero hace falta que él la actualice para que el PR quede habilitado.

Los criterios de la card

  • fetchCatalogProducts manda el término como search. Un término vacío no viaja.
  • Salen CATALOG_PAGE_SIZE, filterCatalog y el filterOptions propio. El nuevo filterOptions={(o) => o} es lo contrario de un filtro y está justificado.
  • La clave depende del término (recortado) y hay debounce.
  • Los tests de filterCatalog salen y el de «busca por nombre» se reemplazó por los que prueban lo que viaja.
  • Con más de 100 productos, el buscador encuentra el que antes quedaba afuera. Verificado contra la API.

Veredicto

APPROVE.

La review de Lorenzo encontró un bug real y las correcciones lo cierran con tests que lo detectan si vuelve. La actualización contra master resolvió además un problema que el merge de texto no mostraba. El menor del estado de carga no bloquea. Para mergear falta que Lorenzo actualice su review.

The edit screen passed only isPending, so a search that came back empty
kept saying "no product matches" while the next one was still in
flight. Step 1 already used isPending || isFetching.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp

@Sanntinat Sanntinat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-revisión — TESIS-125 (PR #49) · Buscar el catálogo en el backend

Tercera vuelta sobre TESIS-125-catalog-search-from-the-backend (f6bee4b), contra origin/master de proyecto-web. La vuelta anterior la aprobé sobre 9d2167d. Después entró un commit, y la review de Lorenzo sigue en CHANGES_REQUESTED.

Check Resultado
CI (GitHub Actions) Los cuatro jobs en verde
Rama vs master 2 commits atrás (TESIS-82 y TESIS-104). Mergea limpio
Sobre el merge con master (local) 61 archivos · 565 tests · 0 failures · lint, prettier y build limpios
Conflictos con otros PRs Con #52 (TESIS-59): docs/guidelines/architecture.md y src/features/orders/api.test.ts. #52 es mío y está bloqueado, así que lo resuelvo yo cuando éste entre. Con #53, ninguno

✅ El commit nuevo es el menor que había marcado

f6bee4b hace que la pantalla de modificación use isPending || isFetching, como el paso 1. Con eso, una búsqueda que no trajo nada ya no dice «Ningún producto coincide.» mientras la siguiente todavía viaja. Es el cambio exacto, y el comentario explica por qué hace falta con keepPreviousData.

Sobre el merge con master: TESIS-104 pasó los estilos a theme.vars. Revisé el diff de esta rama y no agrega ninguna lectura de theme.palette, así que no queda nada por convertir.

🟡 Menor: ese estado no lo prueba ningún test

Volví la línea a catalog.isPending y los 215 tests de features/orders siguen en verde. Tampoco hay un test del «Buscando…» en el paso 1, así que las dos pantallas están igual de descubiertas. No lo pido para este PR, pero es el tipo de diferencia que se reintroduce sin que nadie lo note.

Los puntos de Lorenzo

Siguen resueltos, como verifiqué en la vuelta anterior (rompiendo cada uno): la etiqueta de la opción elegida ya no viaja como búsqueda, el test de «no volver a filtrar» tipea algo que no está en la etiqueta y el comentario ya no habla de una búsqueda por descripción que no existe. Lo único que falta es formal: que @LoLoo03 actualice su review, que es lo que mantiene el PR en CHANGES_REQUESTED.

Los criterios de la card

  • fetchCatalogProducts manda el término como search; un término vacío no viaja.
  • Salen el tope de página, filterCatalog y el filtro propio del Autocomplete.
  • La clave depende del término recortado y hay debounce.
  • Con más de 100 productos, el buscador encuentra el que antes quedaba afuera (verificado contra la API en la vuelta anterior).

Veredicto

APPROVE.

Sigue en pie la aprobación anterior, y el commit nuevo cierra el menor que había dejado. Del lado del PR no falta nada: sólo la actualización de la review de Lorenzo.

@TomasMartin2004

Copy link
Copy Markdown
Contributor Author

@LoLoo03 ¿podés volver a mirar este? Tu review del 24 sigue en CHANGES_REQUESTED y es lo único que lo frena: el PR tiene CI 4/4 en verde y dos aprobaciones de @Sanntinat, la segunda de anoche.

Los tres puntos que marcaste están resueltos desde c9b9321, y los tres tienen un ejemplo que falla si se deshacen:

1. El bug — la etiqueta de la opción elegida viajaba como búsqueda. Lo reproduje antes de tocar nada con una sonda propia:

>>> lastSearch tras elegir: "PX-9021-LRG · Router industrial de alta densidad"

Tomé tu arreglo: el campo lo maneja MUI y sólo se propaga reason === 'input' o el valor vacío. Con eso la prop search dejó de hacer falta. Una cosa que vale contar: los tres ejemplos nuevos pasaban sin probar nada hasta que agregué la espera del debounce — sin ella la afirmación llega antes que el término y el test verdeaba igual con el bug puesto.

2. El test sin dientes. Cierto: sensor estaba en la etiqueta y el filtro de MUI lo dejaba pasar igual. Ahora tipea zzz, y quitando filterOptions se pone en rojo solo.

3. El comentario con el motivo falso. También cierto, y el error de lectura fue mío: el backend busca por sku y name, los dos visibles en la etiqueta. No existe el producto que matchea «por su descripción». Reescrito con el efecto real —con keepPreviousData, el filtro de MUI escondería las coincidencias anteriores mientras viaja la nueva— y el Caso 3 del plan de pruebas reemplazado por uno reproducible.

Los nits también: el término se recorta en el hook (con cuatro ejemplos nuevos en useCatalogProducts.test.tsx), el loadingText pasó a «Buscando…» y la evidencia quedó completa.

Después de tu review entró TESIS-61 y trajo un segundo consumidor del buscador —la pantalla de modificación— que llamaba al catálogo sin término; lo detectó npm run build y no la suite, porque Vitest no chequea tipos. Esa pantalla ahora busca igual que el paso 1, con su test.

Si encontrás algo más, decilo y lo corrijo; si no, con que actualices la review alcanza para mergearlo.

🤖 Generated with Claude Code

https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp

TESIS-59 landed, so step 3 of the wizard exists now. Two conflicts,
both of them "keep both sides":

- architecture.md: the orders row names the three steps, as master
  does, and keeps the note that the search filters against the backend.
- api.test.ts: master's helpers plus this card's block for
  fetchCatalogProducts. Rebuilt from master's file rather than merged
  line by line, which broke the imports.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xAtddb53LRNVLuyTXyELp
@TomasMartin2004
TomasMartin2004 merged commit 23c547f into master Sep 28, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants