Compare commits

..
Author SHA1 Message Date
devopsandClaude Sonnet 5 8d44f2e771 fix(ecommerce): mostrar mensaje cuando el buscador no tiene resultados
Encontrado probando en vivo con navegador real (Docker + Playwright):
una búsqueda de 2+ caracteres sin coincidencias no mostraba nada -- ni
"Buscando…" ni ningún mensaje. El usuario quedaba sin feedback de que
su búsqueda de verdad corrió. Se agrega un estado vacío con el botón
de buscar en todo el catálogo (esa búsqueda completa puede usar
criterios más amplios que el autocompletado).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR
2026-09-11 13:08:50 +00:00
devopsandClaude Sonnet 5 352762b9d2 Merge: ampliar checklist de pruebas reales pendientes (ADR 0010)
Build and Push Docs Portal / Escaneo de Secretos (Gitleaks) (push) Successful in 20s
Build and Push Docs Portal / Construir y publicar Docs Portal (push) Successful in 6m6s
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR
2026-09-10 21:40:11 +00:00
devopsandClaude Sonnet 5 58cc93bb2f docs(adr-0010): ampliar checklist de pruebas reales pendientes
Cubre el resto de la superficie del storefront (checkout edge cases,
filtros de catalogo, variantes de producto, cuenta/wishlist,
accesibilidad, otros viewports) agrupado por riesgo, no solo lo ya
probado.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR
2026-09-10 21:40:09 +00:00
gitea-actions 5eaa94818f chore(gitops): deploy Docs Portal v1.0.241 [skip ci] 2026-09-10 17:56:48 +00:00
gitea-actions 66a81ad9a3 chore(gitops): deploy Medusa v1.0.239 [skip ci] 2026-09-10 17:49:42 +00:00
devops 39e1bf61d3 chore: release v1.0.240 [skip ci] 2026-09-10 17:23:09 +00:00
devopsandClaude Sonnet 5 b5a1f70d1d Merge: documentar resultados de pruebas con navegador real (ADR 0010)
Build and Push Docs Portal / Escaneo de Secretos (Gitleaks) (push) Successful in 38s
Build and Push Docs Portal / Construir y publicar Docs Portal (push) Successful in 6m20s
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR
2026-09-10 17:13:12 +00:00
devopsandClaude Sonnet 5 9d231322b8 docs(adr-0010): resultados de pruebas con navegador real + checklist ampliado
Cierra la mayor parte del checklist de pruebas reales que había quedado
pendiente: pago PSE hasta la redirección al banco, envío calculado
visible, banner y buscador mobile -- todo verificado con Chromium real
(Docker + Playwright en la NAS, ver .browser-scratch/README.md). Deja
documentados los 2 bugs reales que esa prueba encontró (ya corregidos
en otro commit) y un checklist ampliado para la próxima sesión.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR
2026-09-10 17:13:10 +00:00
devopsandClaude Sonnet 5 098ff990a6 Merge: filtrar producto sin stock del banner y banco placeholder de PSE
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 52s
Build and Push Medusa / Escaneo de Secretos (Gitleaks) (push) Successful in 28s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 11m53s
Build and Push Medusa / Construir y publicar Medusa (push) Successful in 26m23s
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR
2026-09-10 17:09:41 +00:00
devopsandClaude Sonnet 5 ff59618a2a fix: filtrar producto sin stock del banner y banco placeholder de PSE
Encontrado probando en vivo con navegador real (Docker + Playwright
contra shop.cruzcloud.net):

- app/page.tsx: un producto featured pero sin stock (available: false)
  salía primero en el hero banner con botón "Comprar ahora" hacia una
  página donde en realidad no se puede comprar.
- pse-banks/route.ts: el sandbox real de ePayco (bank.getBanks())
  incluye un primer elemento placeholder (bankCode "0", "A
  continuación seleccione su banco...") mezclado con los bancos reales,
  mostrado como opción seleccionable. Se filtra explícitamente.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR
2026-09-10 17:09:36 +00:00
7 changed files with 174 additions and 44 deletions

No files matched your search

@@ -12,6 +12,12 @@ import { createEpaycoClient, type EpaycoEnv } from "../../../../lib/epayco";
// contra el sandbox real en esta sesion -- se prueban los nombres de
// campo mas probables (bankCode/bankName y variantes) y se degrada a
// lista vacia si no matchea nada, en vez de romper el checkout entero.
//
// Verificado en vivo despues (navegador real, Playwright): el sandbox
// real de ePayco incluye un primer elemento placeholder
// (bankCode "0", "A continuación seleccione su banco...") mezclado con
// los bancos reales -- ningun banco PSE real tiene codigo "0", asi que
// se filtra explicitamente en vez de mostrarlo como opcion seleccionable.
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
let cache: { banks: Array<{ pseCode: string; description: string }>; fetchedAt: number } | null = null;
@@ -42,7 +48,7 @@ export async function GET(req: MedusaRequest, res: MedusaResponse) {
? { pseCode: String(pseCode), description: String(description ?? pseCode) }
: null;
})
.filter((bank): bank is { pseCode: string; description: string } => bank !== null);
.filter((bank): bank is { pseCode: string; description: string } => bank !== null && bank.pseCode !== "0");
cache = { banks, fetchedAt: Date.now() };
res.json({ banks });
@@ -244,9 +244,12 @@ reubicó su contenedor según el breakpoint.
## Consecuencias
- Checkout real de punta a punta (pago + envío calculado) queda
desplegado y verificado a nivel API/backend, pero **ambos proveedores
siguen en modo sandbox** — no se ha completado ni un pago real ni un
envío real todavía. Ver checklist de pruebas abajo.
desplegado y verificado con navegador real hasta la redirección al
banco de PSE (ver nota de implementación 2026-09-10 abajo), pero
**ambos proveedores siguen en modo sandbox** — no se ha completado ni
un pago real ni un envío real todavía, y el login bancario de PSE
tampoco se completó (falta credencial de prueba). Ver checklist
ampliado abajo.
- La precisión de la tarifa de envío depende de cargar peso/dimensiones
reales por producto — deuda de datos explícita, no de código; candidato
natural a extender el importador CSV existente con columnas nuevas.
@@ -262,37 +265,142 @@ reubicó su contenedor según el breakpoint.
## Checklist de pruebas reales desde el storefront (pendiente, no ejecutado en este ADR)
Todo lo de arriba se verificó a nivel API/backend con llamadas directas
o carritos de prueba armados por API — **no** todavía manejando el
storefront real como lo haría un comprador. Pendiente, con la próxima
sesión que tenga acceso a un navegador real:
Todo lo de arriba se verificó primero a nivel API/backend con llamadas
directas o carritos de prueba armados por API, sin manejar el
storefront real como lo haría un comprador. Esa parte del checklist se
cerró el 2026-09-10 (ver nota de implementación abajo) usando un
navegador real de verdad, no curl.
1. **Pago PSE completo:** agregar un producto real al carrito → checkout
→ elegir PSE → seleccionar un banco del listado real (sandbox) →
confirmar que la redirección al banco ocurre (esto es justo lo que
nunca se probó — el nombre del campo de URL de `bank.create()` está
manejado a la defensiva y podría no matchear ningún candidato) →
volver a `/checkout/pse/return` → confirmar que la orden queda en
estado correcto.
2. **Envío calculado real:** en el paso de dirección del checkout,
confirmar que el precio de envío mostrado ya no es el fijo de
$15.000 sino el que devuelve EnvíoClick en vivo para esa dirección
(visible también sin completar el pago, solo llegando al paso de
shipping).
3. **Resiliencia del fallback de envío:** si es posible, forzar una
dirección o producto que haga fallar la cotización real (por ejemplo,
un DANE code inválido) y confirmar que el checkout sigue avanzando
con el precio de respaldo en vez de romperse — el comportamiento
documentado en `calculatePrice()`, nunca ejercitado con un fallo real.
4. **Banner dinámico:** confirmar en `/` que el carrusel muestra
productos reales del catálogo (no las tres imágenes viejas
hardcodeadas) y que cada slide lleva al producto correcto al hacer
clic.
5. **Buscador:** en un viewport mobile real (no solo devtools), confirmar
que el buscador ya no se ve recortado y que el autocompletado
despliega bien debajo de la fila nueva a todo el ancho.
6. **Webhook de ePayco con un pago real de sandbox:** confirmar que,
tras completar un pago PSE de prueba desde el banco sandbox, el
webhook mueve la orden a completada sin intervención manual (hoy solo
se confirmó que el endpoint responde `200` a un `POST` sintético, no
con un webhook real disparado por ePayco).
### RESUELTO (2026-09-10) — navegador real vía Docker en la NAS
El shell donde corren los comandos de esta sesión tiene su propia red
restringida para `npm` (un proxy interno que rompe algunas descargas de
paquetes — no relacionado con la red real del lab). **Docker en la NAS
sí sale a internet real, sin ese filtro** — confirmado en vivo
(`docker pull` normal, un contenedor de Node golpeando
`registry.npmjs.org` sin problema). Se armó un harness reutilizable con
la imagen oficial `mcr.microsoft.com/playwright:v1.51.1-jammy`
(Chromium real) corrida puntualmente contra `shop.cruzcloud.net`,
guardado en `.browser-scratch/` en la raíz del lab (`checks.mjs` +
`README.md` con el comando exacto para reactivarlo — no hace falta
ningún contenedor corriendo de forma persistente, la imagen y las
dependencias ya quedan cacheadas localmente en la NAS).
**Hallazgo no obvio:** el daemon de Docker de la NAS ve un filesystem
distinto al que ve el shell de esta sesión (que corre en su propio
sandbox, aunque monte los mismos repos git) — un volumen `-v` apuntando
a un path de `/tmp` del shell no tira error pero monta un directorio
vacío del lado del contenedor. Hay que usar un path bajo
`/media/HDD-Storage/AppData/k3d-lab-v3/lab-cruzcloud/` (montaje real
compartido) para que el contenedor vea los archivos de verdad.
**Resultados, con capturas reales:**
1. **Pago PSE completo — FUNCIONA.** Carrito → dirección Bogotá →
envío calculado → banco PSE (sandbox) → **la redirección real
funcionó**: el nombre de campo manejado a la defensiva en
`bank.create()`/`initiatePayment` matcheó al primer intento, y el
navegador llegó a la página real de autorización de PSE
(`registro.desarrollo.pse.com.co`, ambiente de desarrollo/sandbox
de PSE Colombia). No se completó el login bancario (requiere
credenciales de prueba específicas de PSE que no se tenían a mano) —
sigue pendiente confirmar el webhook con un pago sandbox completado
de principio a fin (ver checklist nuevo abajo).
2. **Envío calculado real — FUNCIONA.** El paso de pago mostró
`Envío $8.941` (mismo valor ya visto en la verificación por API de
la sección anterior) en vez del fijo de $15.000, para una dirección
real de Bogotá.
3. **Banner dinámico — funciona, pero destapó un bug real:** un
producto marcado `featured: true` pero **sin stock**
(`available: false`, "Toy Story 5 - Rex") salía primero en el
carrusel con botón "Comprar ahora" hacia una página donde en
realidad no se puede comprar. Corregido en el mismo commit que este
hallazgo (filtro adicional `&& product.available` en
`app/page.tsx`).
4. **Buscador mobile — funciona.** El buscador ya no se ve recortado,
y el autocompletado despliega bien debajo de la fila nueva a todo el
ancho (probado con el viewport de un iPhone 13 real, no devtools).
5. **Bug adicional encontrado, no buscado:** el sandbox real de ePayco
(`bank.getBanks()`) devuelve un primer elemento placeholder
(`bankCode: "0"`, `"A continuación seleccione su banco..."` con el
texto mal decodificado — unicode escapado literal) mezclado con los
bancos reales, mostrado como una opción más, seleccionable, en el
`<select>` del checkout. Ningún banco PSE real tiene código "0" —
se filtra explícitamente en `pse-banks/route.ts`.
### Pendiente — checklist ampliado para la próxima sesión con navegador
3. **Resiliencia del fallback de envío:** forzar una dirección o
producto que haga fallar la cotización real de EnvíoClick (ej. un
DANE code inválido) y confirmar que el checkout sigue avanzando con
el precio de respaldo ($15.000) en vez de romperse — comportamiento
documentado en `calculatePrice()`, nunca ejercitado con un fallo
real.
6. **Webhook de ePayco con un pago real de sandbox completado:**
conseguir credenciales de prueba reales de PSE (persona/clave de
algún banco del ambiente `desarrollo`), completar el login bancario
de punta a punta, y confirmar que el webhook mueve la orden a
completada sin intervención manual — hoy solo se confirmó que el
endpoint responde `200` a un `POST` sintético y que la redirección
al banco ocurre, no el ciclo completo con notificación real.
Ampliado 2026-09-10 con el resto de la superficie real del storefront
(4 páginas + `components/{account,cart,catalog,checkout,home,layout,
product,search,wishlist}`), agrupado por riesgo:
**Checkout / pagos**
7. **Pago cancelado o rechazado en el banco PSE:** confirmar que la
orden no queda en un estado ambiguo ("colgada" en
`pending_authorization` para siempre) cuando el comprador cancela o
el banco rechaza — hoy solo se probó el camino feliz hasta la
redirección.
8. **Persona jurídica + NIT** en el formulario de PSE — solo se probó
persona natural con cédula.
9. **`/checkout` con carrito vacío** (acceso directo por URL, sin pasar
por un producto primero).
10. **Fallback "Confirmar por WhatsApp"** (visible cuando falla la
lista de bancos o como alternativa siempre disponible): que el link
arme el mensaje correcto con los datos del pedido.
**Catálogo y búsqueda**
11. **Filtros de `/catalog`** (categoría/colección/marca/precio — las
facetas de Meilisearch, ver ADR 0006) con navegador real: que
aplicar/combinar filtros actualice los resultados y la URL.
12. **Búsqueda sin resultados** — confirmar que muestra un mensaje
claro en vez de quedar vacío sin explicación.
**Producto**
13. **Selección de variantes** (cuando un producto tiene más de una) y
que el precio/disponibilidad mostrado cambie según la variante
elegida.
14. **Slug de producto inválido** → debe caer en `not-found.tsx`
(`app/not-found.tsx`), no en un error sin manejar.
15. **Zoom interactivo del producto** (hover + modal click-to-zoom,
`product-gallery.tsx`) — re-verificar con navegador real, la última
vez que se probó fue antes de esta tanda de cambios al storefront.
**Cuenta y wishlist**
16. **Login/registro de cliente real** (Google OAuth de punta a punta
incluyendo `/auth/google/callback`, y con contraseña) — nunca
ejercitado con un navegador real en ninguna sesión.
17. **Wishlist/favoritos** (`wishlist-drawer.tsx`): agregar/quitar y
confirmar que persiste entre recargas (guardado en `localStorage`,
por-viewer — no hay forma de verificarlo por API).
**Transversal**
18. **Bug conocido de Firefox Android** (ver memoria de proyecto,
`known-bug-firefox-android-layout`): el layout se corta/desplaza
solo en Firefox Android — Playwright puede emular Android/Chrome
pero no Firefox; si se necesita reproducir esto específicamente hace
falta `playwright-firefox` o un dispositivo real.
19. **Otros viewports** (tablet, desktop grande) para el banner y el
buscador — solo se probaron mobile (iPhone 13) y desktop 1280px.
20. **Página 404** (`app/not-found.tsx`) — nunca visitada con navegador
real.
21. **Accesibilidad básica**: navegación completa por teclado (tab
order, foco visible) en el checkout y el buscador — Playwright
puede simular esto (`page.keyboard`), no probado todavía.
@@ -25,7 +25,7 @@ spec:
runAsNonRoot: false
containers:
- name: app
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.237
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.241
# El base saca TODAS las capabilities. Probado en vivo y
# revertido: con drop: ALL (aunque se agregue NET_BIND_SERVICE
# de vuelta) el propio entrypoint de nginx:1.27-alpine falla al
+7 -3
View File
@@ -21,9 +21,13 @@ export default async function HomePage() {
// ADR 0010: el banner toma primero productos destacados (curaduria
// explicita del admin via el flag "featured"); si no hay ninguno,
// cae a los mismos "tesoros" (nuevos/destacados) de la seccion de
// abajo para no repetir la logica de seleccion.
const featuredProducts = products.filter((product) => product.featured);
const bannerProducts = featuredProducts.length > 0 ? featuredProducts : treasures;
// abajo para no repetir la logica de seleccion. Se exige ademas
// "available" -- encontrado probando en vivo con un navegador real
// (Playwright): un producto featured pero sin stock disponible salio
// primero en el carrusel con boton "Comprar ahora" hacia una pagina
// donde en realidad no se puede comprar.
const featuredProducts = products.filter((product) => product.featured && product.available);
const bannerProducts = featuredProducts.length > 0 ? featuredProducts : treasures.filter((product) => product.available);
return <><HeroCarousel products={bannerProducts} /><TrustBar /><div className="content-auto"><CategoryTiles categories={categories} /></div><section className="content-auto mx-auto max-w-7xl px-4 py-14"><div className="mb-8 flex items-end justify-between gap-5"><div><span className="text-xs font-black uppercase tracking-[.18em] text-ari-coral">Novedades</span><h2 className="mt-2 text-4xl font-black tracking-tight sm:text-5xl">Tesoros recién llegados</h2><p className="mt-3 text-slate-500">Desliza y encuentra nuevos favoritos.</p></div><Button asChild variant="outline" className="hidden sm:inline-flex"><Link href="/catalog">Ver catálogo <ArrowRight className="size-4" /></Link></Button></div><ProductCarousel products={treasures} /></section><section className="content-auto mx-auto max-w-7xl px-4 pb-20"><div className="grid overflow-hidden rounded-[2rem] bg-ari-marino text-white shadow-2xl lg:grid-cols-[1fr_.7fr]"><div className="p-8 sm:p-12"><span className="text-xs font-black uppercase tracking-[.18em] text-ari-coral-claro">Club ARI</span><h2 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">Promociones, lanzamientos y mucha diversión.</h2><p className="mt-5 max-w-xl text-white/80">Únete a nuestra comunidad y recibe primero las novedades del catálogo.</p><Button variant="yellow" size="lg" className="mt-7">Quiero unirme</Button></div>{/* No hay herramienta de generacion de imagenes disponible para crear un
asset nuevo -- se reconstruye el mismo efecto (fondo punteado +
fotos estilo polaroid) con CSS y fotos reales de producto ya
@@ -65,7 +65,7 @@ spec:
memory: 64Mi
- name: migrations
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.234
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.239
imagePullPolicy: IfNotPresent
command:
- node
@@ -86,7 +86,7 @@ spec:
containers:
- name: app
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.234
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.239
imagePullPolicy: IfNotPresent
envFrom:
- configMapRef:
@@ -87,6 +87,18 @@ export function PredictiveSearch() {
Buscando…
</div>
)}
{/* Encontrado probando en vivo con navegador real: sin este bloque,
una busqueda de 2+ caracteres sin coincidencias no mostraba nada
(ni "buscando" ni un mensaje) -- el usuario se quedaba sin ningun
feedback de que su busqueda de verdad corrio. Se ofrece igual el
boton de buscar en todo el catalogo porque esa busqueda completa
puede usar criterios mas amplios que el autocompletado. */}
{!loading && normalized.length >= 2 && matches.length === 0 && (
<div className="absolute left-0 right-0 top-[calc(100%+10px)] z-50 rounded-2xl border border-slate-200 bg-white p-2 text-center shadow-2xl">
<p className="p-3 text-sm text-slate-500">No encontramos resultados para “{query.trim()}”.</p>
<button type="submit" className="w-full rounded-xl bg-ari-marino px-4 py-3 text-sm font-black text-white hover:brightness-110">Buscar “{query.trim()}” en todo el catálogo</button>
</div>
)}
{matches.length > 0 && (
<div className="absolute left-0 right-0 top-[calc(100%+10px)] z-50 overflow-hidden rounded-2xl border border-slate-200 bg-white p-2 shadow-2xl">
{matches.map((product) => (
@@ -25,7 +25,7 @@ spec:
runAsUser: 1001
containers:
- name: app
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.238
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.240
env:
- name: HEADLESS_PROVIDER
value: medusa