Inventario de funcionalidades¶
Qué hace hoy el storefront, de la gestión de contenido a la operativa de venta. Es un inventario de lo implementado en este repositorio, no de lo que podría hacer Medusa. Donde la lógica vive en el backend se aclara.
Leyenda de origen: CMS = Payload · MED = Medusa V2 · ALG = Algolia · SF = lógica propia del storefront.
1. Gestión de contenido (Payload CMS)¶
El contenido editorial no está en el código: se arma en Payload y el storefront
lo renderiza. Cliente en
src/lib/payload.ts
(login usuario/clave, JWT cacheado en memoria).
| Funcionalidad | Origen | Dónde |
|---|---|---|
| Home editable — hero, bloques y grillas definidos en Payload | CMS | getHomepage() |
Páginas por slug — cualquier página institucional en /{countryCode}/{slug} |
CMS | getPageBySlug(), ruta [slug]/page.tsx |
| Menú de navegación principal — árbol de ítems con enlaces a categoría, colección, filtro o URL propia | CMS | navigation-service.ts (global main-navigation) |
| Header configurable | CMS | header-service.ts (global header) |
| Formularios dinámicos — definidos en Payload, con mensaje de confirmación y email de respuesta | CMS | getPayloadForm(), extractConfirmationMessage(), extractEmailMessage() |
| Renderizado de texto enriquecido (Lexical de Payload) | SF | modules/common/components/RichText/ |
Bloques de contenido soportados¶
Implementados en el renderizador de [slug]/page.tsx. Un blockType no
contemplado se ignora en producción y avisa en pantalla en desarrollo.
hero · content · richText · mediaBlock (incluye modo fullscreen) ·
cta · banner · archive (por selección o por consulta) · formBlock /
form · productGrid / cuadriculaProductos · slider / heroSlider
Los campos de formulario soportados son text, email, textarea, select y
checkbox.
2. Catálogo y búsqueda¶
Conviven dos motores: Algolia para búsqueda y navegación filtrada, Medusa para categorías, colecciones y la ficha de producto.
| Funcionalidad | Origen | Notas |
|---|---|---|
| Búsqueda con autocompletado | ALG | SearchBar, SearchModal, índice de query suggestions aparte |
| Grilla de tienda con InstantSearch | ALG | algolia-store-template, scroll infinito y paginación |
| Filtros facetados — sidebar de categorías, rango de precio, atributos | ALG | algolia-sidebar, algolia-store-filters, mobile-filter |
| Ordenamiento de resultados | ALG | refinement-list/sort-products |
| Filtros sincronizados con la URL | SF | parse-search-params.ts, enrutador de InstantSearch |
| Navegación por categorías | MED | categories/[...category], jerarquía anidada |
| Colecciones | MED | collections/[handle] |
| Tarjeta de producto con badge de descuento | SF | calcula sobre special_price / regular_price, y sobre el tipo OFERTA. Solo se muestra si supera el 5%, y se oculta si una etiqueta del CMS ocupa su esquina |
| Etiquetas de producto en la tarjeta | CMS + ALG | Texto, estilo, zona, orden y vigencia en la colección product-badges de Payload; se cruzan con los product_tags de Algolia. lib/product-badges.ts, product-badge. Contrato en el RF-008 del repo del CMS |
| Tarjeta sin prefetch | SF | El enlace de la tarjeta no precarga la ficha: en producción era un pedido al servidor, y a Medusa, por cada tarjeta visible. Ver ADR-0009 |
3. Ficha de producto¶
| Funcionalidad | Origen | Notas |
|---|---|---|
URL propia por variante — products/[handle]/[[...variantId]] |
SF | Ruta opcional catch-all: se comparte el enlace de una variante concreta y la página abre con esa variante seleccionada |
| Galería de imágenes | MED | image-gallery |
| Selección de variantes con títulos y atributos | SF | variant-helpers.ts (variantOptionsToKeymap, variantHasStock) |
| Descripción por variante | SF | variant-description |
| Precio con precio especial y porcentaje de descuento | SF | product-price, get-precentage-diff.ts |
| Disponibilidad por sucursal | SF | product-availability: lee stock_aviadores y stock_multiplaza de la metadata de la variante y muestra stock en Aviadores del Chaco y Shopping Multiplaza |
| Pestañas de información | MED | product-tabs |
| Productos relacionados | ALG | related-products: misma categoría que el producto (metadata.category de Medusa), con stock, consultado desde el navegador con scroll infinito. Muestra también otras variantes del mismo producto |
| Simulador de cuotas | SF | Ver §8 |
4. Carrito¶
| Funcionalidad | Origen | Notas |
|---|---|---|
| Alta, modificación y baja de líneas | MED | addToCart, updateLineItem, deleteLineItem |
| Carrito persistente por cookie | MED | _medusa_cart_id, HTTP-only |
| Códigos de promoción | MED | applyPromotions, removeDiscount, submitPromotionForm |
| Gift cards | MED | applyGiftCard, removeGiftCard |
| Nudge de envío gratis — cuánto falta para alcanzar el umbral | SF | free-shipping-price-nudge, calcula monto restante y porcentaje |
| Traspaso del carrito al iniciar sesión | MED | transferCart() |
| Cambio de región recalculando el carrito | MED | updateRegion() |
5. Checkout¶
Flujo por pasos con ?step= en la URL: dirección → entrega → pago → revisión.
5.1 Direcciones y geografía¶
| Funcionalidad | Origen | Notas |
|---|---|---|
| Selector de departamento y ciudad de Paraguay | SF + MED | department-select y city-select; las ciudades se filtran por el departamento elegido. Datos desde getGeoZones() (lib/data/geozones.ts) |
| Dirección de envío y de facturación por separado | MED | shipping-address, billing_address |
| Reutilización de direcciones guardadas | MED | address-select |
5.2 Envíos y promesas de entrega¶
Es la parte con más lógica propia.
| Funcionalidad | Origen | Notas |
|---|---|---|
| Envío a domicilio vs. retiro en sucursal | SF + MED | Se separan las opciones por service_zone.fulfillment_set.type === "pickup" y se presentan como dos grupos distintos |
| Promesa de entrega calculada por sucursal | SF | calculateMaxDeliveryTimes() en checkout/components/shipping/: recorre los ítems del carrito y toma el máximo tiempo de entrega. Para el proveedor interno (supplier_id = 1) usa t_entrega_aviadores y t_entrega_multi de la metadata de cada variante; para proveedores externos, t_entrega aplica a ambas sucursales |
| Promesa expresada en horas hábiles | SF | El texto distingue "Retiro inmediato en horas hábiles" de una cantidad de horas, con singular/plural |
| Precios de envío calculados en vivo | MED | calculatePriceForShippingOption(), resuelto por opción con Promise.allSettled |
| Listado de opciones según el carrito | MED | listCartShippingMethods() |
El cálculo depende de que la metadata de cada variante venga cargada desde Medusa (
supplier_id,t_entrega,t_entrega_aviadores,t_entrega_multi,stock_aviadores,stock_multiplaza). Si falta, la promesa queda en 0.
5.3 Pagos¶
Los proveedores que el storefront sabe representar están en
src/lib/constants.tsx.
Quién está habilitado lo decide Medusa, no este repo.
| Método | provider_id |
Estado |
|---|---|---|
| Bancard QR | pp_bancard_qr_bancard_qr |
En uso. Se paga en el paso de revisión; el pedido nace pagado — ver abajo |
| Tarjeta (Bancard vPOS) | pp_bancard_vpos_bancard_vpos |
Iframe de Bancard en el paso de revisión (historia #149) |
| Transferencia bancaria | pp_bank_transfer_bank_transfer |
En uso. Con descuento aplicado por el backend (código TRANSFERED_PAYMENT) |
| Pago manual | pp_system_default |
Disponible |
| Stripe (tarjeta, iDeal, Bancontact) | pp_stripe_* |
Código heredado del starter, inactivo |
| PayPal | pp_paypal_paypal |
Código heredado del starter, inactivo |
Flujo Bancard (modules/checkout/components/bancard-qr-panel/ y bancard-vpos-frame/):
en el paso de revisión abre el intento de pago en el backend (reserva de stock y carrito
bloqueado), muestra el QR con su cuenta regresiva o el iframe de tarjeta, y escucha el resultado
en tiempo real por SSE contra /payment-events/{sessionId} (use-bancard-payment-sse.ts,
reconexión hasta 5 intentos) con sondeo de respaldo. El pedido lo crea el backend al confirmar
el pago; finishPaidCheckout cierra el checkout y redirige. Maneja pagado, rechazado,
vencido y cancelado.
El guaraní (pyg) está declarado como moneda sin división por 100.
6. Pedidos y post-venta¶
| Funcionalidad | Origen | Notas |
|---|---|---|
| Confirmación de pedido | MED | order/[id]/confirmed |
| Historial y detalle de pedidos | MED | account/@dashboard/orders |
| Panel de transferencia bancaria con los datos para pagar | SF | order/components/bank-transfer-panel |
| Detalle de envío y de pago del pedido | MED | shipping-details, payment-details |
| Transferencia de pedido entre cuentas — solicitar, aceptar y rechazar por token | MED | createTransferRequest, acceptTransferRequest, declineTransferRequest; rutas order/[id]/transfer/[token]/{accept,decline} |
7. Cuentas de cliente¶
| Funcionalidad | Origen | Notas |
|---|---|---|
| Registro e inicio de sesión con email | MED | JWT en cookie _medusa_jwt HTTP-only |
| Login con Google (OAuth) | SF + MED | google-login-button, callback en auth/google/callback, createGoogleCustomer() y refreshGoogleAuth() |
| Recuperación y reseteo de contraseña | MED | forgot-password, reset-password |
| Perfil: nombre, email, teléfono, contraseña, dirección de facturación | MED | account-info y componentes profile-* |
| Libreta de direcciones (alta, edición, baja) | MED | address-book, address-card |
Panel con rutas paralelas @dashboard / @login |
SF | Renderiza uno u otro según sesión |
8. Financiación — formulario de crédito¶
Funcionalidad propia, sin equivalente en el starter.
| Funcionalidad | Origen | Notas |
|---|---|---|
| Simulador de cuotas | SF | installment-calculator.ts: cuota = (precio / factor) / meses, redondeada al millar. Plazos y factores tabulados, monto mínimo 1.000.000 Gs |
| Cálculo de total financiado y % de recargo | SF | calculateTotalWithFinancing(), calculateSurchargePercent() |
| Solicitud de crédito en modal | SF + CMS | CreditButton, CreditModal, CreditRequestForm; la configuración del formulario viene de Payload (getCreditFormSettings()) |
| Proxy server-side hacia Payload | SF | app/api/credit-form/route.ts — evita exponer credenciales del CMS al navegador |
| Protección antibot reCAPTCHA v3 | SF | lib/providers/recaptcha-provider.tsx, montado en el layout raíz |
9. Transversal¶
| Funcionalidad | Origen | Notas |
|---|---|---|
| Enrutamiento por región | SF + MED | middleware.ts: mapa país → región cacheado 1 h, redirección a /{countryCode}. Región por defecto py |
| Cache con tags por usuario | SF | _medusa_cache_id, tags carts-<id>, products-<id>, etc., revalidados tras cada mutación |
| Botón flotante de WhatsApp | SF | WhatsAppButton, número 595974670000 hardcodeado |
| Footer editable | SF | layout/templates/footer |
| Skeletons de carga | SF | modules/skeletons/ |
| Sitemap | SF | next-sitemap.js |
| Despliegue en Docker | SF | Dockerfile con npm sobre repo yarn — ver ADR-0003 |
Puntos a revisar¶
Detectados durante el inventario, sin acción tomada:
- Número de WhatsApp hardcodeado en el componente. Debería ser variable de entorno o venir de Payload.
- Sucursales hardcodeadas. Aviadores del Chaco y Multiplaza aparecen por nombre en el cálculo de promesas de entrega y en la disponibilidad. Agregar una tercera sucursal hoy exige tocar código.
- Código muerto de Stripe y PayPal heredado del starter, dos majors atrás. Ver Deuda con el upstream.
- Dos clientes de Algolia (
lib/algolia.tsylib/search-client.ts) y dos plantillas de tienda conviviendo (algolia-store-templateeinstantsearch-store-template) — verificar si alguna quedó sin uso.